From e7720108d1cb1ed9731f1320aade47e3ab7d3441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 2 May 2016 10:27:38 +0200 Subject: [PATCH 001/315] [i18n] Update locales --- app/locales/taiga/locale-ca.json | 1 + app/locales/taiga/locale-de.json | 117 +++++++++++++------------- app/locales/taiga/locale-es.json | 1 + app/locales/taiga/locale-fi.json | 1 + app/locales/taiga/locale-fr.json | 55 ++++++------ app/locales/taiga/locale-it.json | 1 + app/locales/taiga/locale-nl.json | 1 + app/locales/taiga/locale-pl.json | 13 +-- app/locales/taiga/locale-pt-br.json | 1 + app/locales/taiga/locale-ru.json | 1 + app/locales/taiga/locale-sv.json | 1 + app/locales/taiga/locale-tr.json | 1 + app/locales/taiga/locale-zh-hant.json | 1 + 13 files changed, 104 insertions(+), 91 deletions(-) diff --git a/app/locales/taiga/locale-ca.json b/app/locales/taiga/locale-ca.json index b9d01bc1..ddfea7e1 100644 --- a/app/locales/taiga/locale-ca.json +++ b/app/locales/taiga/locale-ca.json @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "El teu text ací...", "PREVIEW_BUTTON": "Previsualitzar", "EDIT_BUTTON": "Editar", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Ajuda de Markdown" }, "PERMISIONS_CATEGORIES": { diff --git a/app/locales/taiga/locale-de.json b/app/locales/taiga/locale-de.json index 4a9c8ab3..97663a2f 100644 --- a/app/locales/taiga/locale-de.json +++ b/app/locales/taiga/locale-de.json @@ -2,7 +2,7 @@ "COMMON": { "YES": "Ja", "NO": "Nein", - "OR": "or", + "OR": "oder", "LOADING": "Wird geladen...", "LOADING_PROJECT": "Projekt wird geladen...", "DATE": "DD MMM YYYY", @@ -41,8 +41,8 @@ "IOCAINE_TEXT": "Fühlen Sie sich von einer Aufgabe etwas erdrückt? Stellen Sie sicher, dass andere davon erfahren, indem Sie auf Locaine klicken, wenn Sie eine Aufgabe ändern. Es ist möglich, gegen dieses (fiktive) tödliche Gift immun zu werden, indem man kleine Mengen über einen Zeitraum hinweg einnimmt. Genauso, wie es möglich ist, besser in dem zu werden, was man tut, indem man gelegentlich zusätzliche Herausforderungen annimmt!", "CLIENT_REQUIREMENT": "Client requirement is new requirement that was not previously expected and it is required to be part of the project", "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", - "OWNER": "Project Owner", - "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "OWNER": "Projekteigentümer", + "CAPSLOCK_WARNING": "Achtung! Sie verwenden Großbuchstaben in einem Eingabefeld, dass Groß- und Kleinschreibung berücksichtigt.", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Dieser Wert scheint ungültig zu sein.", "TYPE_EMAIL": "Dieser Wert sollte eine gültige E-Mail Adresse enthalten.", @@ -68,7 +68,7 @@ "RANGE_CHECK": "Wählen Sie zwischen %s und %s", "EQUAL_TO": "Dieser Wert sollte der gleiche sein.", "LINEWIDTH": "One or more lines is perhaps too long. Try to keep under %s characters.", - "PIKADAY": "Invalid date format, please use DD MMM YYYY (like 23 Mar 1984)" + "PIKADAY": "Ungültiges Datumsformat. Bitte nutze DD MMM JJJJ (etwa 23 März 1984)" }, "PICKERDATE": { "FORMAT": "DD MMM YYYY", @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Text...", "PREVIEW_BUTTON": "Vorschau", "EDIT_BUTTON": "Bearbeiten", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Markdown syntax Hilfe" }, "PERMISIONS_CATEGORIES": { @@ -319,8 +320,8 @@ "PLACEHOLDER_FIELD": "Benutzername oder E-Mail-Adresse", "ACTION_RESET_PASSWORD": "Passwort zurücksetzen", "LINK_CANCEL": "Nein, bring mich zurück. Ich denke, ich erinnere mich daran.", - "SUCCESS_TITLE": "Check your inbox!", - "SUCCESS_TEXT": "We sent you an email with the instructions to set a new password", + "SUCCESS_TITLE": "Prüfen Sie bitte Ihre Emails!", + "SUCCESS_TEXT": "Wir haben Ihnen eine Email mit den Anweisungen zum ändern Ihres Passworts geschickt", "ERROR": "Laut unseren Helferlein sind Sie bislang noch nicht registriert." }, "CHANGE_PASSWORD": { @@ -356,7 +357,7 @@ "HOME": { "PAGE_TITLE": "Home - Taiga", "PAGE_DESCRIPTION": "Die Taiga Homepage mit Ihren wichtigsten Projekten und all Ihren zugeordneten und beobachteten User-Stories, Aufgaben und Tickets.", - "EMPTY_WORKING_ON": "It feels empty, doesn't it? Start working with Taiga and you'll see here the stories, tasks and issues you are working on.", + "EMPTY_WORKING_ON": "Es sieht hier noch leer aus, oder? Beginne mit Taiga zu arbeiten und du wirst hier Storys, Tasks und Issues an denen gearbeitet wird sehen.", "EMPTY_WATCHING": "Folge User Stories, Tasks, Issues in deinem Projekt und erhalte Benachrichtigungen, wenn sich etwas ändert. :)", "EMPTY_PROJECT_LIST": "Sie haben noch keine Projekte", "WORKING_ON_SECTION": "Zuletzt bearbeitet", @@ -411,8 +412,8 @@ "PAGE_TITLE": "Mitgliedschaften - {{projectName}}", "ADD_BUTTON": "+ Neues Mitglied", "ADD_BUTTON_TITLE": "Neues Mitglied hinzufügen", - "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Unfortunately, this project has reached its limit of ({{members}}) allowed members.", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "This project has reached its limit of ({{members}}) allowed members. If you would like to increase that limit please contact the administrator." + "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Leider hat dieses Projekt sein Limit von ({{members}}) Mitgliedern bereits erreicht", + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Dieses Projekt hat seine Grenze von ({{members}}) erlaubten Mitgliedern erreicht. Wenn Sie diese Grenze erhöhen möchten, kontaktieren Sie den Administrator." }, "PROJECT_EXPORT": { "TITLE": "Exportieren", @@ -434,10 +435,10 @@ "DISABLE": "Deaktivieren", "BACKLOG": "Auftragsliste", "BACKLOG_DESCRIPTION": "Verwalten Sie Ihre User-Stories, um einen organisierten Überblick der anstehenden und priorisierten Aufgaben zu erhalten.", - "NUMBER_SPRINTS": "Expected number of sprints", - "NUMBER_SPRINTS_HELP": "0 for an undetermined number", - "NUMBER_US_POINTS": "Expected total of story points", - "NUMBER_US_POINTS_HELP": "0 for an undetermined number", + "NUMBER_SPRINTS": "Erwartete Anzahl an Sprints", + "NUMBER_SPRINTS_HELP": "0 für eine unbestimmte Anzahl", + "NUMBER_US_POINTS": "Erwartete Gesamt-Story-Punkte", + "NUMBER_US_POINTS_HELP": "0 für eine unbestimmte Anzahl", "KANBAN": "Kanban", "KANBAN_DESCRIPTION": "Organisieren Sie Ihr Projekt auf übersichtliche Art.", "ISSUES": "Tickets", @@ -445,9 +446,9 @@ "WIKI": "Wiki", "WIKI_DESCRIPTION": "Fügen Sie Inhalte hinzu, ändern oder löschen Sie sie in Zusammenarbeit mit anderen. Dies ist der richtige Ort für Ihre Projektdokumentation.", "MEETUP": "Zusammentreffen", - "MEETUP_DESCRIPTION": "Choose your videoconference system.", + "MEETUP_DESCRIPTION": "Wähle Sie Ihr Videokonferenzsystem.", "SELECT_VIDEOCONFERENCE": "Wählen Sie ein Videokonferenzsystem", - "SALT_CHAT_ROOM": "Add a prefix to the chatroom name", + "SALT_CHAT_ROOM": "Fügen Sie ein Präfix für den Chatraum-Namen hinzu", "JITSI_CHAT_ROOM": "Jitsi", "APPEARIN_CHAT_ROOM": "Erscheint in", "TALKY_CHAT_ROOM": "Gesprächig", @@ -471,19 +472,19 @@ "LOGO_HELP": "Das Bild wird auf 80x80px skaliert.", "CHANGE_LOGO": "Logo ändern", "ACTION_USE_DEFAULT_LOGO": "Nutze Standardbild", - "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects allowed by your current plan", - "MAX_PRIVATE_PROJECTS_MEMBERS": "The maximum number of members for private projects has been exceeded", + "MAX_PRIVATE_PROJECTS": "Sie haben die maximale Anzahl privater Projekte erreicht, die in Ihrem derzeitigen Plan erlaubt sind", + "MAX_PRIVATE_PROJECTS_MEMBERS": "Die maximale Anzahl von Mitgliedern für privater Projekte sind erreicht", "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds your maximum number of members for public projects", - "PROJECT_OWNER": "Project owner", - "REQUEST_OWNERSHIP": "Request ownership", - "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Do you want to become the new project owner?", + "PROJECT_OWNER": "Projekteigentümer", + "REQUEST_OWNERSHIP": "Besitz beantragen", + "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Möchtest du der neue Projektleiter werden?", "REQUEST_OWNERSHIP_DESC": "Request that current project owner {{name}} transfer ownership of this project to you.", "REQUEST_OWNERSHIP_BUTTON": "Anfrage", - "REQUEST_OWNERSHIP_SUCCESS": "We'll notify the project owner", - "CHANGE_OWNER": "Change owner", - "CHANGE_OWNER_SUCCESS_TITLE": "Ok, your request has been sent!", - "CHANGE_OWNER_SUCCESS_DESC": "We will notify you by email if the project ownership request is accepted or declined" + "REQUEST_OWNERSHIP_SUCCESS": "Wir werden den Projektleiter benachrichtigen", + "CHANGE_OWNER": "Ändere Besitzer", + "CHANGE_OWNER_SUCCESS_TITLE": "Ok, deine Anfrage wurde versendet!", + "CHANGE_OWNER_SUCCESS_DESC": "Wir informieren Sie via Email, ob sie als neuer Projektleiter akzeptiert wurden oder ob die Anfrage zurückgewiesen wurde." }, "REPORTS": { "TITLE": "Berichte", @@ -562,7 +563,7 @@ "COUNT_MEMBERS": "{{ role.members_count }} Mitglieder mit dieser Rolle", "TITLE_DELETE_ROLE": "Rolle löschen", "REPLACEMENT_ROLE": "Alle Benutzer mit dieser Rolle werden verschoben nach", - "WARNING_DELETE_ROLE": "Be careful! All role estimations will be removed", + "WARNING_DELETE_ROLE": "Achtung! Alle Rollenverteilungen werden entfernt.", "ERROR_DELETE_ALL": "Sie können nicht alle Werte löschen", "EXTERNAL_USER": "Externer Benutzer" }, @@ -692,15 +693,15 @@ "TITLE": "Dienste" }, "PROJECT_TRANSFER": { - "DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "Would you like to become the new project owner?", - "PRIVATE": "Private", - "ACCEPTED_PROJECT_OWNERNSHIP": "Congratulations! You're now the new project owner.", - "REJECTED_PROJECT_OWNERNSHIP": "OK. We'll contact the current project owner", + "DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "Möchten Sie der neue Projektleiter werden?", + "PRIVATE": "Privat", + "ACCEPTED_PROJECT_OWNERNSHIP": "Herzlichen Glückwunsch. Sie sind der neue Projektleiter.", + "REJECTED_PROJECT_OWNERNSHIP": "Ok, wir kontaktieren den aktuellen Projektleiter", "ACCEPT": "Akzeptieren", - "REJECT": "Reject", + "REJECT": "Zurückweisen", "PROPOSE_OWNERSHIP": "{{owner}}, the current owner of the project {{project}} has asked that you become the new project owner.", - "ADD_COMMENT": "Would you like to add a comment for the project owner?", - "UNLIMITED_PROJECTS": "Unlimited", + "ADD_COMMENT": "Möchten Sie einen Kommentar für den Projektleiter hinzufügen?", + "UNLIMITED_PROJECTS": "Unbegrenzt", "OWNER_MESSAGE": { "PRIVATE": "Please remember that you can own up to {{maxProjects}} private projects. You currently own {{currentProjects}} private projects", "PUBLIC": "Please remember that you can own up to {{maxProjects}} public projects. You currently own {{currentProjects}} public projects" @@ -768,9 +769,9 @@ "WATCHERS_COUNTER_TITLE": "{total, plural, one{ein Beobachter} andere{# Beobachter}}", "MEMBERS_COUNTER_TITLE": "{total, plural, one{one member} andere{# members}}", "BLOCKED_PROJECT": { - "BLOCKED": "Blocked project", - "THIS_PROJECT_IS_BLOCKED": "This project is temporarily blocked", - "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "In order to unblock your projects, contact the administrator." + "BLOCKED": "Blockiertes Projekt", + "THIS_PROJECT_IS_BLOCKED": "Dieses Projekt ist vorrübergehend blockiert", + "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "Um dein Projekt zu entsperren kontaktiere bitte einen Administrator." }, "STATS": { "PROJECT": "Projekt
Punkte", @@ -887,10 +888,10 @@ "SECTION_NAME": "Dein Taiga Benutzerkonto löschen", "CONFIRM": "Sind Sie sicher, dass Sie Ihr Taiga Benutzerkonto löschen wollen?", "NEWSLETTER_LABEL_TEXT": "Ich möchte keinen Newsletter mehr erhalten", - "CANCEL": "Back to settings", - "ACCEPT": "Delete account", - "BLOCK_PROJECT": "Note that all the projects you own projects will be blocked after you delete your account. If you do want a project blocked, transfer ownership to another member of each project prior to deleting your account.", - "SUBTITLE": "Sorry to see you go. We'll be here if you should ever consider us again! :(" + "CANCEL": "Zurück zu Einstellungen", + "ACCEPT": "Benutzerkonto löschen", + "BLOCK_PROJECT": "Beachten Sie, dass alle Projekte die Sie besitzen, gesperrt werden, nachdem Sie Ihr Konto gelöscht haben. Wenn Sie möchten, dass Ihre Projekte nicht gesperrt werden, ernennen Sie ein anderes Mitglied zum Projektleiter für jedes Projekt, bevor Sie Ihr Konto löschen.", + "SUBTITLE": "Schade, dass du uns verlassen willst. Wir sind hier, falls du uns nochmal suchst. :(" }, "DELETE_PROJECT": { "TITLE": "Projekt löschen", @@ -947,24 +948,24 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Optional) Fügen Sie einen persönlichen Text zur Einladung hinzu. Erzählen Sie Ihren neuen Mitgliedern etwas Schönes. ;-)", "PLACEHOLDER_TYPE_EMAIL": "Geben Sie eine E-Mail ein", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Leider kann dieses Projekt nicht mehr als {{maxMembers}} Mitglieder haben. Wenn Sie die derzeitige Grenze erhöhen möchten, kontaktieren Sie den Administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "Leider kann dieses Projekt nicht mehr als {{maxMembers}} Mitglieder haben." }, "LEAVE_PROJECT_WARNING": { - "TITLE": "Unfortunately, this project can't be left without an owner", + "TITLE": "Das Projekt kann nicht ohne einen Projektleiter existieren.", "CURRENT_USER_OWNER": { - "DESC": "You are the current owner of this project. Before leaving, please transfer ownership to someone else.", - "BUTTON": "Change the project owner" + "DESC": "Du bist der aktuelle Besitzer dieses Projektes. Bitte übertrage den Besitzerstatus an jemand anderes bevor du das Projekt verlässt.", + "BUTTON": "Projektleiter wechseln" }, "OTHER_USER_OWNER": { - "DESC": "Unfortunately, you can't delete a member who is also the current project owner. First, please assign a new project owner.", - "BUTTON": "Request project owner change" + "DESC": "Leider können Sie das Mitglied nicht löschen, da es der derzeitige Projektleiter ist. Bitte ordnen Sie dem Projekt zuerst einen neuen Projektleiter zu.", + "BUTTON": "Antrag Projektleiter-Wechsel " } }, "CHANGE_OWNER": { - "TITLE": "Who do you want to be the new project owner?", - "ADD_COMMENT": "Add comment", - "BUTTON": "Ask this project member to become the new project owner" + "TITLE": "Wen möchtest du zum neuen Projektleiter ernennen?", + "ADD_COMMENT": "Kommentar hinzufügen", + "BUTTON": "Fragen Sie dieses Projektmitglied, um Projektleiter zu werden" } }, "US": { @@ -1394,16 +1395,16 @@ "WIZARD": { "SECTION_TITLE_CREATE_PROJECT": "Projekt erstellen", "CREATE_PROJECT_TEXT": "Frisch und sauber. Wie aufregend!", - "CHOOSE_TEMPLATE": "Which template fits your project best?", - "CHOOSE_TEMPLATE_TITLE": "More info about project templates", - "CHOOSE_TEMPLATE_INFO": "More info", - "PROJECT_DETAILS": "Project Details", - "PUBLIC_PROJECT": "Public Project", - "PRIVATE_PROJECT": "Private Project", + "CHOOSE_TEMPLATE": "Welches Template passt am besten zu Ihrem Projekt?", + "CHOOSE_TEMPLATE_TITLE": "Mehr Infos über Projekt-Templates", + "CHOOSE_TEMPLATE_INFO": "Weitere Infos", + "PROJECT_DETAILS": "Projekt Details", + "PUBLIC_PROJECT": "Öffentliches Projekt", + "PRIVATE_PROJECT": "Privates Projekt", "CREATE_PROJECT": "Projekt anlegen", - "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects", + "MAX_PRIVATE_PROJECTS": "Sie haben die maximale Anzahl privater Projekte erreicht", "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects", - "CHANGE_PLANS": "change plans" + "CHANGE_PLANS": "Änderungs-Pläne" }, "WIKI": { "PAGE_TITLE": "{{wikiPageName}} - Wiki - {{projectName}}", @@ -1412,7 +1413,7 @@ "PLACEHOLDER_PAGE": "Schreiben Sie Ihre Wiki Seite", "REMOVE": "Diese Wiki Seite entfernen", "DELETE_LIGHTBOX_TITLE": "Wiki Seite löschen", - "DELETE_LINK_TITLE": "Delete Wiki link", + "DELETE_LINK_TITLE": "Entferne Wiki Link", "NAVIGATION": { "SECTION_NAME": "Links", "ACTION_ADD_LINK": "Link hinzufügen" diff --git a/app/locales/taiga/locale-es.json b/app/locales/taiga/locale-es.json index 96f060a4..c18c59f7 100644 --- a/app/locales/taiga/locale-es.json +++ b/app/locales/taiga/locale-es.json @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Tu texto aquí...", "PREVIEW_BUTTON": "Previsualizar", "EDIT_BUTTON": "Editar", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Ayuda de sintaxis Markdown" }, "PERMISIONS_CATEGORIES": { diff --git a/app/locales/taiga/locale-fi.json b/app/locales/taiga/locale-fi.json index 5c1396e1..bf1968ec 100644 --- a/app/locales/taiga/locale-fi.json +++ b/app/locales/taiga/locale-fi.json @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Kirjoita tänne...", "PREVIEW_BUTTON": "Esikatselu", "EDIT_BUTTON": "Muokkaa", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Merkintätavan ohjeet" }, "PERMISIONS_CATEGORIES": { diff --git a/app/locales/taiga/locale-fr.json b/app/locales/taiga/locale-fr.json index 795d5bb4..6fcb67b6 100644 --- a/app/locales/taiga/locale-fr.json +++ b/app/locales/taiga/locale-fr.json @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Votre texte ici…", "PREVIEW_BUTTON": "Aperçu", "EDIT_BUTTON": "Modifier", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Aide sur la syntaxe Markdown" }, "PERMISIONS_CATEGORIES": { @@ -356,7 +357,7 @@ "HOME": { "PAGE_TITLE": "Accueil - Taiga", "PAGE_DESCRIPTION": "La page d'accueil de Taiga sur laquelle apparaissent vos projets principaux et toutes les récits utilisateur, tâches et tickets qui vont sont assignés et surveillés.", - "EMPTY_WORKING_ON": "It feels empty, doesn't it? Start working with Taiga and you'll see here the stories, tasks and issues you are working on.", + "EMPTY_WORKING_ON": "Ça fait vide, hein ?Commencez à utiliser Taiga et vous verrez apparaître ici les récits, tâches et tickets sur lesquels vous travaillez.", "EMPTY_WATCHING": "Suivez des récits utilisateur, des tâches, des tickets dans vos projets et soyez prévenu des modifications :)", "EMPTY_PROJECT_LIST": "Vous n'avez aucun projet pour l'instant", "WORKING_ON_SECTION": "Projets en cours", @@ -411,8 +412,8 @@ "PAGE_TITLE": "Membres - {{projectName}}", "ADD_BUTTON": "+ Nouveau membre", "ADD_BUTTON_TITLE": "Ajouter un membre", - "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Unfortunately, this project has reached its limit of ({{members}}) allowed members.", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "This project has reached its limit of ({{members}}) allowed members. If you would like to increase that limit please contact the administrator." + "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Désolé, ce projet a atteint sa limite de membres autorisés ({{members}}).", + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Ce projet a atteint sa limite de ({{members}}) membres autorisés. Si vous désirez augmenter cette limite, merci de contacter l'administrateur." }, "PROJECT_EXPORT": { "TITLE": "Exporter", @@ -447,7 +448,7 @@ "MEETUP": "Meet Up", "MEETUP_DESCRIPTION": "Choisissez un système de vidéoconférence", "SELECT_VIDEOCONFERENCE": "Choisissez un système de vidéoconférence", - "SALT_CHAT_ROOM": "Add a prefix to the chatroom name", + "SALT_CHAT_ROOM": "Ajouter un préfixe au nom du salon de chat", "JITSI_CHAT_ROOM": "Jitsi", "APPEARIN_CHAT_ROOM": "AppearIn", "TALKY_CHAT_ROOM": "Talky", @@ -471,10 +472,10 @@ "LOGO_HELP": "L'image va être agrandie à 80x80px", "CHANGE_LOGO": "Changer le logo", "ACTION_USE_DEFAULT_LOGO": "Utiliser l'image par défaut", - "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects allowed by your current plan", - "MAX_PRIVATE_PROJECTS_MEMBERS": "The maximum number of members for private projects has been exceeded", - "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", - "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds your maximum number of members for public projects", + "MAX_PRIVATE_PROJECTS": "Vous avez atteint le nombre maximum autorisé de projets privés accordé par votre souscription actuelle.", + "MAX_PRIVATE_PROJECTS_MEMBERS": "Le nombre maximum de membres de projets privés a été dépassé.", + "MAX_PUBLIC_PROJECTS": "Désolé, vous avez atteint le nombre maximum de projets publics accordé par votre souscription actuelle.", + "MAX_PUBLIC_PROJECTS_MEMBERS": "Ce projet dépasse le nombre maximum de membres autorisés pour un projet public", "PROJECT_OWNER": "Propriétaire du projet", "REQUEST_OWNERSHIP": "Demander la propriété", "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Voulez-vous devenir le nouveau propriétaire du projet ?", @@ -702,11 +703,11 @@ "ADD_COMMENT": "Voulez-vous ajouter un commentaire pour le propriétaire du projet ?", "UNLIMITED_PROJECTS": "Illimité", "OWNER_MESSAGE": { - "PRIVATE": "Please remember that you can own up to {{maxProjects}} private projects. You currently own {{currentProjects}} private projects", - "PUBLIC": "Please remember that you can own up to {{maxProjects}} public projects. You currently own {{currentProjects}} public projects" + "PRIVATE": "Gardez en mémoire que vous pouvez posséder jusqu'à {{maxProjects}} projets privés. Vous possédez actuellement {{currentProjects}} projets privés.", + "PUBLIC": "Gardez en mémoire que vous pouvez posséder jusqu'à {{maxProjects}} projets publics. Vous possédez actuellement {{currentProjects}} projets publics." }, "CANT_BE_OWNED": "Pour le moment, vous ne pouvez devenir propriétaire d'un projet de ce type. Si vous voulez devenir propriétaire de ce projet, merci de contacter l'administrateur afin qu'il puisse modifier les paramètres de votre compte pour autoriser la propriété de projet.", - "CHANGE_MY_PLAN": "Change my plan" + "CHANGE_MY_PLAN": "Changer ma souscription actuelle" } }, "USER": { @@ -835,28 +836,28 @@ "ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) est un peu trop lourd pour nos Oompa Loompas, réessayez avec un fichier d'une taille inférieure à ({{maxFileSize}})", "SYNC_SUCCESS": "Votre projet a été importé avec succès", "PROJECT_RESTRICTIONS": { - "PROJECT_MEMBERS_DESC": "The project you are trying to import has {{members}} members, unfortunately, your current plan allows for a maximum of {{max_memberships}} members per project. If you would like to increase that limit please contact the administrator.", + "PROJECT_MEMBERS_DESC": "Le projet que vous voulez importer a {{members}} membres. Votre souscription actuelle vous permet un maximum de {{max_memberships}} membres par projet. Si vous désirez augmenter cette limite, merci de contacter un administrateur.", "PRIVATE_PROJECTS_SPACE": { - "TITLE": "Unfortunately, your current plan does not allow for additional private projects", - "DESC": "The project you are trying to import is private. Unfortunately, your current plan does not allow for additional private projects." + "TITLE": "Malheureusement, votre souscription actuelle ne vous permet pas d'ajouter un projet privé supplémentaire.", + "DESC": "Le projet que vous voulez importer est privé. Votre souscription actuelle ne vous permet pas d'ajouter un projet privé supplémentaire." }, "PUBLIC_PROJECTS_SPACE": { - "TITLE": "Unfortunately, your current plan does not allow for additional public projects", - "DESC": "The project you are trying to import is public. Unfortunately, your current plan does not allow additional public projects." + "TITLE": "Malheureusement, votre souscription actuelle ne vous permet pas d'ajouter un projet public supplémentaire.", + "DESC": "Le projet que vous voulez importer est privé. Votre souscription actuelle ne vous permet pas d'ajouter un projet public supplémentaire." }, "PRIVATE_PROJECTS_MEMBERS": { - "TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per private project" + "TITLE": "Votre souscription actuelle permet un maximum de {{max_memberships}} par projet privé." }, "PUBLIC_PROJECTS_MEMBERS": { - "TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per public project." + "TITLE": "Votre souscription actuelle permet un maximum de {{max_memberships}} par projet public." }, "PRIVATE_PROJECTS_SPACE_MEMBERS": { - "TITLE": "Unfortunately your current plan doesn't allow additional private projects or an increase of more than {{max_memberships}} members per private project", - "DESC": "The project that you are trying to import is private and has {{members}} members." + "TITLE": "Désolé, votre souscription actuelle ne permet ni d'ajouter des projets privés supplémentaire, ni d'avoir plus de {{max_memberships}} membres par projet privé.", + "DESC": "Le projet que vous voulez importer est privé et a {{members}} membres." }, "PUBLIC_PROJECTS_SPACE_MEMBERS": { - "TITLE": "Unfortunately your current plan doesn't allow additional public projects or an increase of more than {{max_memberships}} members per public project", - "DESC": "The project that you are trying to import is public and has more than {{members}} members." + "TITLE": "Désolé, votre souscription actuelle ne permet ni d'ajouter des projets publics supplémentaire, ni d'avoir plus de {{max_memberships}} membres par projet public.", + "DESC": "Le projet que vous voulez importer est public et a {{members}} membres." } } }, @@ -890,7 +891,7 @@ "CANCEL": "Retour aux réglages", "ACCEPT": "Supprimer le compte", "BLOCK_PROJECT": "Notez que tous les projets dont vous avez la propriété seront bloqués après que vous ayez supprimé votre compte. Si vous souhaitez pas qu'un projet soit bloqué, merci d'en transférer la propriété auprès d'un autre membre avant la suppression de votre compte.", - "SUBTITLE": "Sorry to see you go. We'll be here if you should ever consider us again! :(" + "SUBTITLE": "Vous partez déjà ? Nous serons toujours là si jamais vous changez d'avis ! :(" }, "DELETE_PROJECT": { "TITLE": "Supprimer le projet", @@ -947,8 +948,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Optionnel) Ajoutez un texte personnalisé à l'invitation. Dites quelque chose de gentil à vos nouveaux membres ;-)", "PLACEHOLDER_TYPE_EMAIL": "Saisissez une adresse courriel", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Désolé, ce projet ne peut avoir plus de {{maxMembers}} membres.
Si vous désirez augmenter cette limite, merci de contacter l'administrateur.", + "LIMIT_USERS_WARNING_MESSAGE": "Désolé, ce projet ne peut avoir plus de {{maxMembers}} membres." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Malheureusement, ce projet ne peut pas être laissé sans propriétaire", @@ -1402,7 +1403,7 @@ "PRIVATE_PROJECT": "Projet privé", "CREATE_PROJECT": "Créer un projet", "MAX_PRIVATE_PROJECTS": "Vous avez atteint le nombre maximum de projets privés", - "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects", + "MAX_PUBLIC_PROJECTS": "Désolé, vous avez atteint le nombre maximal de projet public.", "CHANGE_PLANS": "changer de souscription" }, "WIKI": { @@ -1412,7 +1413,7 @@ "PLACEHOLDER_PAGE": "Ecrivez votre page wiki", "REMOVE": "Supprimer cette page wiki", "DELETE_LIGHTBOX_TITLE": "Supprimer la page Wiki", - "DELETE_LINK_TITLE": "Delete Wiki link", + "DELETE_LINK_TITLE": "Supprimer un lien Wiki", "NAVIGATION": { "SECTION_NAME": "Liens", "ACTION_ADD_LINK": "Ajouter un lien" diff --git a/app/locales/taiga/locale-it.json b/app/locales/taiga/locale-it.json index 17204bdd..66036eed 100644 --- a/app/locales/taiga/locale-it.json +++ b/app/locales/taiga/locale-it.json @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Inserire qui il testo...", "PREVIEW_BUTTON": "Anteprima", "EDIT_BUTTON": "Modifica", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Aiuto per la sintassi Markdown" }, "PERMISIONS_CATEGORIES": { diff --git a/app/locales/taiga/locale-nl.json b/app/locales/taiga/locale-nl.json index c5232f56..bb90d8c7 100644 --- a/app/locales/taiga/locale-nl.json +++ b/app/locales/taiga/locale-nl.json @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Je tekst hier...", "PREVIEW_BUTTON": "Voorbeeld", "EDIT_BUTTON": "Bewerk", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Markdown syntax help" }, "PERMISIONS_CATEGORIES": { diff --git a/app/locales/taiga/locale-pl.json b/app/locales/taiga/locale-pl.json index 264f5455..aa4db039 100644 --- a/app/locales/taiga/locale-pl.json +++ b/app/locales/taiga/locale-pl.json @@ -2,7 +2,7 @@ "COMMON": { "YES": "Tak", "NO": "Nie", - "OR": "or", + "OR": "lub", "LOADING": "Wczytywanie...", "LOADING_PROJECT": "Wczytywanie projektu...", "DATE": "DD MMM YYYY", @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Przykładowy tekst...", "PREVIEW_BUTTON": "Podgląd", "EDIT_BUTTON": "Edycja", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Składnia Markdown pomoc" }, "PERMISIONS_CATEGORIES": { @@ -286,9 +287,9 @@ "HEADER": "Mam już login do Taigi", "PLACEHOLDER_AUTH_NAME": "Login albo e-mail (uwzględnij wielkość liter)", "LINK_FORGOT_PASSWORD": "Zapomniałeś?", - "TITLE_LINK_FORGOT_PASSWORD": "Did you forget your password?", + "TITLE_LINK_FORGOT_PASSWORD": "Zapomniałeś hasło? ", "ACTION_ENTER": "Wprowadź", - "ACTION_SIGN_IN": "Login", + "ACTION_SIGN_IN": "Zaloguj", "PLACEHOLDER_AUTH_PASSWORD": "Hasło (uwzględnij wielkość liter)" }, "LOGIN_FORM": { @@ -319,8 +320,8 @@ "PLACEHOLDER_FIELD": "Login albo e-mail", "ACTION_RESET_PASSWORD": "Resetuj hasło", "LINK_CANCEL": "Nie, zabierz mnie stąd. Chyba je pamiętam.", - "SUCCESS_TITLE": "Check your inbox!", - "SUCCESS_TEXT": "We sent you an email with the instructions to set a new password", + "SUCCESS_TITLE": "Sprawdź swoją skrzynkę mailową!", + "SUCCESS_TEXT": "Sprawdź swoją skrzynkę!
Wysłaliśmy Ci wiadomość e-mail z instrukcją jak ustawić nowe hasło.", "ERROR": "Nasze Umpa Lumpy twierdzą, że nie jesteś jeszcze zarejestrowany." }, "CHANGE_PASSWORD": { @@ -349,7 +350,7 @@ "PAGE_DESCRIPTION": "Zaakceptuj zaproszenie, aby dołączyć do projektu w Taiga, platformie do zarządzania projektami dla startup'ów i zwinnych deweloperów oraz designerów, którzy chcą prostego, pięknego narzędzia sprawiającego, że praca jest przyjemna." }, "INVITATION_LOGIN_FORM": { - "NOT_FOUND": "Our Oompa Loompas can't find your invitation.", + "NOT_FOUND": "Nasze Umpa Lumpy nie znajdują Twojego zaproszenia :(", "SUCCESS": "Udało Ci się dołączyć do tego projektu. Witaj w {{project_name}}", "ERROR": "Według naszych Umpa Lump, nie jesteś jeszcze zarejestrowany. Albo wpisałeś złe hasło." }, diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index 13e637f7..147b4bde 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Seu texto aqui...", "PREVIEW_BUTTON": "Pré Visualizar", "EDIT_BUTTON": "Editar", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Ajuda de sintaxe markdown" }, "PERMISIONS_CATEGORIES": { diff --git a/app/locales/taiga/locale-ru.json b/app/locales/taiga/locale-ru.json index 250675a9..54988c7d 100644 --- a/app/locales/taiga/locale-ru.json +++ b/app/locales/taiga/locale-ru.json @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Ваш текст здесь...", "PREVIEW_BUTTON": "Предварительный просмотр", "EDIT_BUTTON": "Редактировать", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Помощь по синтаксису Markdown" }, "PERMISIONS_CATEGORIES": { diff --git a/app/locales/taiga/locale-sv.json b/app/locales/taiga/locale-sv.json index d8c9aef1..d6f136e8 100644 --- a/app/locales/taiga/locale-sv.json +++ b/app/locales/taiga/locale-sv.json @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Din text här", "PREVIEW_BUTTON": "Förhandsvisa", "EDIT_BUTTON": "Redigera", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Hjälp för markeringssyntax" }, "PERMISIONS_CATEGORIES": { diff --git a/app/locales/taiga/locale-tr.json b/app/locales/taiga/locale-tr.json index 6e5b3998..e0c2b4f0 100644 --- a/app/locales/taiga/locale-tr.json +++ b/app/locales/taiga/locale-tr.json @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Yazınız buraya..", "PREVIEW_BUTTON": "Ön izleme", "EDIT_BUTTON": "Düzenle", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Markdown yazım kılavuzu" }, "PERMISIONS_CATEGORIES": { diff --git a/app/locales/taiga/locale-zh-hant.json b/app/locales/taiga/locale-zh-hant.json index bdf6a841..e992257e 100644 --- a/app/locales/taiga/locale-zh-hant.json +++ b/app/locales/taiga/locale-zh-hant.json @@ -225,6 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "你的文字在此", "PREVIEW_BUTTON": "預視 ", "EDIT_BUTTON": "編輯", + "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Markdown 語法協助" }, "PERMISIONS_CATEGORIES": { From b3e5842de816629e885df5a06e9065417b1c0163 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 3 May 2016 12:28:07 +0200 Subject: [PATCH 002/315] upgrade libs --- bower.json | 46 +++++++++++++++++++++++----------------------- package.json | 40 ++++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/bower.json b/bower.json index 63bd0389..a1977319 100644 --- a/bower.json +++ b/bower.json @@ -49,42 +49,42 @@ "dependencies": { "emoticons": "~0.1.7", "jquery-flot": "~0.8.2", - "angular": "1.4.7", - "angular-route": "1.4.7", - "angular-animate": "1.4.7", - "angular-aria": "1.4.7", - "angular-sanitize": "1.4.7", + "angular": "1.5.5", + "angular-route": "1.5.5", + "angular-animate": "1.5.5", + "angular-aria": "1.5.5", + "angular-sanitize": "1.5.5", "checksley": "~0.6.0", - "jquery": "~2.1.1", + "jquery": "~2.2.3", "markitup-1x": "~1.1.14", "jquery-textcomplete": "yuku-t/jquery-textcomplete#~0.7", "flot-axislabels": "markrcote/flot-axislabels", "flot-orderBars": "emmerich/flot-orderBars", "flot.tooltip": "~0.8.4", - "Sortable": "~0.1.8", - "moment": "~2.10.6", + "moment": "~2.13.0", "pikaday": "~1.4.0", - "raven-js": "~1.1.16", + "raven-js": "~3.0.0", "l.js": "~0.1.0", - "angular-translate": "~2.8.1", - "angular-translate-loader-partial": "~2.8.1", - "angular-translate-loader-static-files": "~2.8.1", - "angular-translate-interpolation-messageformat": "~2.8.1", - "ngInfiniteScroll": "1.2.1", - "immutable": "~3.7.6", - "bluebird": "~2.10.2", - "intro.js": "~1.1.1", - "lodash": "~4.0.0", - "messageformat": "^0.1.8", + "angular-translate": "~2.10.0", + "angular-translate-loader-partial": "~2.10.0", + "angular-translate-loader-static-files": "~2.10.0", + "angular-translate-interpolation-messageformat": "~2.10.0", + "ngInfiniteScroll": "1.2.2", + "immutable": "~3.8.1", + "bluebird": "~3.3.5", + "intro.js": "~2.1.0", + "lodash": "~4.11.2", + "messageformat": "^0.3.1", "dragula.js": "dragula#^3.6.6", "bourbon": "^4.2.7" }, "resolutions": { - "lodash": "~4.0.0", + "lodash": "~4.11.2", "moment": "~2.10.6", - "jquery": "~2.1.1", - "angular": "1.4.7", - "messageformat": "0.1.8" + "jquery": "~2.2.3", + "angular": "1.5.5", + "messageformat": "0.3.1", + "angular-translate": "2.10.0" }, "private": true } diff --git a/package.json b/package.json index dc1abef9..f4350f8e 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "e2e": "./node_modules/.bin/babel-node run-e2e.js" }, "devDependencies": { - "angular-mocks": "1.4.7", + "angular-mocks": "1.5.5", "babel-cli": "^6.6.5", "babel-polyfill": "^6.7.4", "babel-preset-es2015": "^6.6.0", @@ -38,57 +38,57 @@ "css": "^2.2.1", "del": "^2.0.2", "express": "^4.12.0", - "glob": "^5.0.15", + "glob": "^7.0.3", "gulp": "^3.8.11", "gulp-add-src": "^0.2.0", "gulp-angular-templatecache": "^1.5.0", "gulp-autoprefixer": "^3.0.1", - "gulp-cache": "^0.3.0", + "gulp-cache": "^0.4.4", "gulp-cached": "1.1.0", "gulp-coffee": "^2.3.1", - "gulp-coffeelint": "^0.5.0", + "gulp-coffeelint": "^0.6.0", "gulp-concat": "^2.5.2", - "gulp-csslint": "^0.2.0", - "gulp-filter": "^3.0.1", + "gulp-csslint": "^0.3.0", + "gulp-filter": "^4.0.0", "gulp-flatten": "0.2.0", "gulp-if": "^2.0.0", "gulp-imagemin": "^2.2.1", "gulp-insert": "^0.5.0", "gulp-jade": "^1.0.0", - "gulp-jade-inheritance": "0.5.3", + "gulp-jade-inheritance": "0.5.5", "gulp-jsonminify": "^1.0.0", "gulp-livereload": "^3.8.1", - "gulp-minify-css": "^0.4.6", + "gulp-minify-css": "^1.2.4", "gulp-order": "^1.1.1", "gulp-plumber": "^1.0.1", "gulp-print": "^2.0.1", "gulp-rename": "^1.2.0", "gulp-replace": "^0.5.3", "gulp-sass": "^2.3.1", - "gulp-scss-lint": "0.3.6", + "gulp-scss-lint": "0.3.9", "gulp-size": "^2.0.0", - "gulp-sourcemaps": "^1.5.0", - "gulp-template": "^3.0.0", - "gulp-uglify": "~1.4.1", + "gulp-sourcemaps": "^2.0.0-alpha", + "gulp-template": "^4.0.0", + "gulp-uglify": "~1.5.3", "gulp-util": "^3.0.7", - "gulp-wrap": "^0.11.0", - "image-size": "^0.3.5", - "inquirer": "^0.10.0", + "gulp-wrap": "^0.12.0", + "image-size": "^0.5.0", + "inquirer": "^1.0.2", "jade": "^1.11.0", "karma": "^0.13.10", - "karma-chai-plugins": "^0.6.0", - "karma-chrome-launcher": "^0.2.0", + "karma-chai-plugins": "^0.7.0", + "karma-chrome-launcher": "^1.0.1", "karma-coffee-preprocessor": "^0.3.0", - "karma-mocha": "^0.2.0", + "karma-mocha": "^1.0.1", "karma-sourcemap-loader": "^0.3.4", "merge-stream": "^1.0.0", "minimist": "^1.1.1", "mocha": "^2.2.4", "node-uuid": "^1.4.3", - "node-sass": "3.4.2", + "node-sass": "3.6.0", "photoswipe": "^4.1.0", "pre-commit": "^1.0.5", - "readable-stream": "~2.0.2", + "readable-stream": "~2.1.2", "run-sequence": "^1.0.2", "sinon": "^1.14.1", "through2": "^2.0.1", From 821a9f28d9fe72937bbcf9466db3be9bf8ccec9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 3 May 2016 13:22:14 +0200 Subject: [PATCH 003/315] Fix broken tag list --- app/styles/core/elements.scss | 3 +++ app/styles/layout/ticket-detail.scss | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/styles/core/elements.scss b/app/styles/core/elements.scss index aa6e2468..a46902e2 100644 --- a/app/styles/core/elements.scss +++ b/app/styles/core/elements.scss @@ -91,6 +91,9 @@ svg { // scss-lint:disable QualifyingElement div.awesomplete { + input { + display: inline-block; + } > ul { background: rgba($black, .95); color: $primary-light; diff --git a/app/styles/layout/ticket-detail.scss b/app/styles/layout/ticket-detail.scss index 891e221a..0d0f4f70 100644 --- a/app/styles/layout/ticket-detail.scss +++ b/app/styles/layout/ticket-detail.scss @@ -182,6 +182,7 @@ } .subheader { + align-items: flex-start; display: flex; justify-content: space-between; @include breakpoint(laptop) { @@ -192,14 +193,12 @@ flex-basis: 250px; flex-shrink: 0; @include breakpoint(laptop) { - flex: 0; order: 1; } } .tags-block { flex: 1; @include breakpoint(laptop) { - flex: 0; order: 2; } } From 3de26732613c62a2ffa09ac53ac0964fa3b7eae8 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 3 May 2016 21:28:53 +0200 Subject: [PATCH 004/315] fix drag between sprints --- app/coffee/modules/backlog/sortable.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/backlog/sortable.coffee b/app/coffee/modules/backlog/sortable.coffee index 9be36c54..eeb2948a 100644 --- a/app/coffee/modules/backlog/sortable.coffee +++ b/app/coffee/modules/backlog/sortable.coffee @@ -93,7 +93,10 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm, $translate) -> parent = $(item).parent() isBacklog = parent.hasClass('backlog-table-body') || parent.hasClass('empty-backlog') - sameContainer = (initIsBacklog == isBacklog) + if initIsBacklog || isBacklog + sameContainer = (initIsBacklog == isBacklog) + else + sameContainer = $(item).scope().sprint.id == parent.scope().sprint.id dragMultipleItems = window.dragMultiple.stop() From d90d4ce0b7f31cdf8f0ee9bb570f65bb32600b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 3 May 2016 21:30:58 +0200 Subject: [PATCH 005/315] [i18n] Update locales fixin the translation of plurals --- app/locales/taiga/locale-de.json | 52 ++-- app/locales/taiga/locale-pt-br.json | 412 ++++++++++++++-------------- app/locales/taiga/locale-sv.json | 12 +- 3 files changed, 238 insertions(+), 238 deletions(-) diff --git a/app/locales/taiga/locale-de.json b/app/locales/taiga/locale-de.json index 97663a2f..3f22beae 100644 --- a/app/locales/taiga/locale-de.json +++ b/app/locales/taiga/locale-de.json @@ -50,7 +50,7 @@ "TYPE_URLSTRICT": "Dieser Wert sollte eine gültige URL enthalten.", "TYPE_NUMBER": "Dieser Wert sollte eine gültige Nummer enthalten.", "TYPE_DIGITS": "Dieser Wert sollte Ziffern enthalten.", - "TYPE_DATEISO": "Dieser Wert sollte ein gültiges Datum sein (JJJJ-MM-TT)", + "TYPE_DATEISO": "Dieser Wert sollte ein gültiges Datum sein (YYYY-MM-DD)", "TYPE_ALPHANUM": "Dieser Wert sollte alphanumersich sein.", "TYPE_PHONE": "Dieser Wert sollte eine gültige Telefonnummer enthalten.", "NOTNULL": "Dieser Wert darf nicht leer sein.", @@ -67,8 +67,8 @@ "MAX_CHECK": "Wählen Sie %s oder weniger.", "RANGE_CHECK": "Wählen Sie zwischen %s und %s", "EQUAL_TO": "Dieser Wert sollte der gleiche sein.", - "LINEWIDTH": "One or more lines is perhaps too long. Try to keep under %s characters.", - "PIKADAY": "Ungültiges Datumsformat. Bitte nutze DD MMM JJJJ (etwa 23 März 1984)" + "LINEWIDTH": "Eine oder mehrere Zeilen sind vielleicht zu lang. Versuchen Sie unter %s Zeichen zu bleiben.", + "PIKADAY": "Ungültiges Datumsformat. Bitte nutze DD MMM YYYY (etwa 23 März 1984)" }, "PICKERDATE": { "FORMAT": "DD MMM YYYY", @@ -225,7 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Text...", "PREVIEW_BUTTON": "Vorschau", "EDIT_BUTTON": "Bearbeiten", - "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP": "Dateien per Drag & Drop auf das obere Textfeld anhängen.", "MARKDOWN_HELP": "Markdown syntax Hilfe" }, "PERMISIONS_CATEGORIES": { @@ -474,12 +474,12 @@ "ACTION_USE_DEFAULT_LOGO": "Nutze Standardbild", "MAX_PRIVATE_PROJECTS": "Sie haben die maximale Anzahl privater Projekte erreicht, die in Ihrem derzeitigen Plan erlaubt sind", "MAX_PRIVATE_PROJECTS_MEMBERS": "Die maximale Anzahl von Mitgliedern für privater Projekte sind erreicht", - "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", - "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds your maximum number of members for public projects", + "MAX_PUBLIC_PROJECTS": "Leider haben Sie die maximale Anzahl öffentlicher Projekte erreicht, die in Ihrem derzeitigen Plan erlaubt sind", + "MAX_PUBLIC_PROJECTS_MEMBERS": "Die maximale Anzahl von Mitgliedern für öffentliche Projekte sind erreicht", "PROJECT_OWNER": "Projekteigentümer", "REQUEST_OWNERSHIP": "Besitz beantragen", "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Möchtest du der neue Projektleiter werden?", - "REQUEST_OWNERSHIP_DESC": "Request that current project owner {{name}} transfer ownership of this project to you.", + "REQUEST_OWNERSHIP_DESC": "Anfrage vom derzeitigen Projektleiter {{name}}, die Leitung für dieses Projekt zu übernehmen.", "REQUEST_OWNERSHIP_BUTTON": "Anfrage", "REQUEST_OWNERSHIP_SUCCESS": "Wir werden den Projektleiter benachrichtigen", "CHANGE_OWNER": "Ändere Besitzer", @@ -699,15 +699,15 @@ "REJECTED_PROJECT_OWNERNSHIP": "Ok, wir kontaktieren den aktuellen Projektleiter", "ACCEPT": "Akzeptieren", "REJECT": "Zurückweisen", - "PROPOSE_OWNERSHIP": "{{owner}}, the current owner of the project {{project}} has asked that you become the new project owner.", + "PROPOSE_OWNERSHIP": "{{owner}}, der derzeitige Leiter des Projekts {{project}} hat Sie gefragt, ob Sie der neue Projektleiter werden möchten.", "ADD_COMMENT": "Möchten Sie einen Kommentar für den Projektleiter hinzufügen?", "UNLIMITED_PROJECTS": "Unbegrenzt", "OWNER_MESSAGE": { - "PRIVATE": "Please remember that you can own up to {{maxProjects}} private projects. You currently own {{currentProjects}} private projects", - "PUBLIC": "Please remember that you can own up to {{maxProjects}} public projects. You currently own {{currentProjects}} public projects" + "PRIVATE": "Bitte denken Sie daran, dass Sie bis zu {{maxProjects}} private Projekte besitzen können. Dezeit besitzen Sie {{currentProjects}} private Projekte", + "PUBLIC": "Bitte denken Sie daran, dass Sie bis zu {{maxProjects}} öffentliche Projekte besitzen können. Dezeit besitzen Sie {{currentProjects}} öffentliche Projekte" }, - "CANT_BE_OWNED": "At the moment you cannot become an owner of a project of this type. If you would like to become the owner of this project, please contact the administrator so they change your account settings to enable project ownership.", - "CHANGE_MY_PLAN": "Change my plan" + "CANT_BE_OWNED": "Zur Zeit können Sie kein Projektleiter für diesen Projekt-Typ werden werden. Wenn Sie Projektleiter für dieses Projekt werden möchten, kontaktieren Sie bitte den Administrator, damit er Ihre Benutzerkonto-Einstellungen anpassen kann, um Projektleiter werden zu können.", + "CHANGE_MY_PLAN": "Meinen Plan ändern" } }, "USER": { @@ -836,28 +836,28 @@ "ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) ist zu schwierig für unsere Helferlein, versuchen Sie es bitte mit einer kleineren Datei als ({{maxFileSize}})", "SYNC_SUCCESS": "Ihr Projekt wurde erfolgreich importiert", "PROJECT_RESTRICTIONS": { - "PROJECT_MEMBERS_DESC": "The project you are trying to import has {{members}} members, unfortunately, your current plan allows for a maximum of {{max_memberships}} members per project. If you would like to increase that limit please contact the administrator.", + "PROJECT_MEMBERS_DESC": "Das Projekt, dass Sie importieren möchten, hat {{members}} Mitglieder. Leider erlaubt ihr derzeitiger Plan nicht mehr als {{max_memberships}} Mitglieder pro Projekt. Wenn Sie diese Grenze erhöhen möchten, kontaktieren Sie bitte den Administrator.", "PRIVATE_PROJECTS_SPACE": { - "TITLE": "Unfortunately, your current plan does not allow for additional private projects", - "DESC": "The project you are trying to import is private. Unfortunately, your current plan does not allow for additional private projects." + "TITLE": "Leider erlaubt Ihr derzeitiger Plan keine weiteren privaten Projekte anzulegen.", + "DESC": "Das Projekt, das Sie versuchen zu importieren, ist privat. Leider erlaubt Ihr derzeitiger Plan keine weiteren privaten Projekte hinzuzufügen." }, "PUBLIC_PROJECTS_SPACE": { - "TITLE": "Unfortunately, your current plan does not allow for additional public projects", - "DESC": "The project you are trying to import is public. Unfortunately, your current plan does not allow additional public projects." + "TITLE": "Leider erlaubt Ihr derzeitiger Plan keine weiteren öffentlichen Projekte anzulegen.", + "DESC": "Das Projekt, das Sie versuchen zu importieren, ist öffentlich. Leider erlaubt Ihr derzeitiger Plan keine weiteren öffentlichen Projekte hinzuzufügen." }, "PRIVATE_PROJECTS_MEMBERS": { - "TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per private project" + "TITLE": "Ihr derzeitiger Plan erlaubt maximal {{max_memberships}} Mitglieder pro privatem Projekt" }, "PUBLIC_PROJECTS_MEMBERS": { - "TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per public project." + "TITLE": "Ihr derzeitiger Plan erlaubt maximal {{max_memberships}} Mitglieder pro öffentlichem Projekt" }, "PRIVATE_PROJECTS_SPACE_MEMBERS": { - "TITLE": "Unfortunately your current plan doesn't allow additional private projects or an increase of more than {{max_memberships}} members per private project", - "DESC": "The project that you are trying to import is private and has {{members}} members." + "TITLE": "Leider erlaubt Ihr derzeitiger Plan keine weiteren privaten Projekte anzulegen oder eine Erhöhung von mehr als {{max_memberships}} Mitglieder pro privatem Projekt", + "DESC": "Das Projekt, dass Sie importieren möchten, ist privat und hat {{members}} Mitglieder." }, "PUBLIC_PROJECTS_SPACE_MEMBERS": { - "TITLE": "Unfortunately your current plan doesn't allow additional public projects or an increase of more than {{max_memberships}} members per public project", - "DESC": "The project that you are trying to import is public and has more than {{members}} members." + "TITLE": "Leider erlaubt Ihr derzeitiger Plan keine weiteren öffentlichen Projekte anzulegen oder eine Erhöhung von mehr als {{max_memberships}} Mitglieder pro öffentlichem Projekt", + "DESC": "Das Projekt, dass Sie importieren möchten, ist öffentlich und hat mehr als {{members}} Mitglieder." } } }, @@ -1126,7 +1126,7 @@ }, "SPRINTS": { "TITLE": "SPRINTS", - "DATE": "TT MMM JJJJ", + "DATE": "DD MMM YYYY", "LINK_TASKBOARD": "Sprint Taskboard", "TITLE_LINK_TASKBOARD": "Gehe zu Taskboard von \"{{name}}\"", "NUMBER_SPRINTS": "
Sprints", @@ -1403,13 +1403,13 @@ "PRIVATE_PROJECT": "Privates Projekt", "CREATE_PROJECT": "Projekt anlegen", "MAX_PRIVATE_PROJECTS": "Sie haben die maximale Anzahl privater Projekte erreicht", - "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects", + "MAX_PUBLIC_PROJECTS": "Leider haben Sie die maximale Anzahl öffentlicher Projekte erreicht", "CHANGE_PLANS": "Änderungs-Pläne" }, "WIKI": { "PAGE_TITLE": "{{wikiPageName}} - Wiki - {{projectName}}", "PAGE_DESCRIPTION": "Letzte Bearbeitung am {{lastModifiedDate}} ({{totalEditions}} Gesamtzahl der Bearbeitungen) Inhalt: {{ wikiPageContent }}", - "DATETIME": "TT MMM JJJJ hh:mm", + "DATETIME": "DD MMM YYYY HH:mm", "PLACEHOLDER_PAGE": "Schreiben Sie Ihre Wiki Seite", "REMOVE": "Diese Wiki Seite entfernen", "DELETE_LIGHTBOX_TITLE": "Wiki Seite löschen", diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index 147b4bde..d14cea26 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -39,10 +39,10 @@ "EXTERNAL_USER": "um usuário externo", "GENERIC_ERROR": "Um Oompa Loompas disse {{error}}.", "IOCAINE_TEXT": "Se sentindo sobrecarregado por uma tarefa? Assegure-se de que os outros saibam disso clicando em Iocaine quando estiver editando a tarefa. É possível se tornar imune a essse veneno mortal (fictício) consumindo pequenas quantidades ao longo do tempo, assim como é possível ficar melhor no que faz, ocasionalmente, por assumir desafios extras!", - "CLIENT_REQUIREMENT": "Client requirement is new requirement that was not previously expected and it is required to be part of the project", - "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", + "CLIENT_REQUIREMENT": "Requisito de cliente é um novo requisito que não foi anteriormente previsto e que necessita se tornar uma parte do projeto.", + "TEAM_REQUIREMENT": "Requisito de time é um requisito que deve existir no projeto, mas que não deve ter nenhum custo para o cliente.", "OWNER": "Dono do Projeto", - "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CAPSLOCK_WARNING": "Seja cuidadoso! Você está escrevendo em letras maiúsculas e esse campo é case sensitive, ou seja, trata com distinção as letras maiúsculas das minúsculas.", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Este valor parece ser inválido.", "TYPE_EMAIL": "Este valor deve ser um e-mail válido.", @@ -68,7 +68,7 @@ "RANGE_CHECK": "Você deve selecionar entre %s e %s escolhas.", "EQUAL_TO": "Esse valor deveria ser o mesmo.", "LINEWIDTH": "One or more lines is perhaps too long. Try to keep under %s characters.", - "PIKADAY": "Invalid date format, please use DD MMM YYYY (like 23 Mar 1984)" + "PIKADAY": "Formato de data inválido, por favor, use DD MMM YYYY (exemplo: 23 Mar 1984)" }, "PICKERDATE": { "FORMAT": "DD MMM YYYY", @@ -110,16 +110,16 @@ } }, "SEE_USER_PROFILE": "Ver o perfil de {{username }}", - "USER_STORY": "User Story", + "USER_STORY": "História de usuário", "TASK": "Tarefa", - "ISSUE": "Caso", + "ISSUE": "Problema", "TAGS": { - "PLACEHOLDER": "To dentro! Me tagueie....", + "PLACEHOLDER": "Adicionar tags...", "DELETE": "Apagar tag", "ADD": "Adicionar tag" }, "DESCRIPTION": { - "EMPTY": "Espaço vazio é tãããooo monótono... escreva algo, vai...", + "EMPTY": "Espaço vazio é tão monótono... escreva algo, vai...", "NO_DESCRIPTION": "Sem descrição ainda" }, "FIELDS": { @@ -131,9 +131,9 @@ "SLUG": "Rótulo", "COLOR": "Cor", "IS_CLOSED": "Está fechado?", - "STATUS": "Situação", + "STATUS": "Status", "TYPE": "Tipo", - "SEVERITY": "Severidade", + "SEVERITY": "Gravidade", "PRIORITY": "Prioridade", "ASSIGNED_TO": "Atribuído a", "POINTS": "Pontos", @@ -173,7 +173,7 @@ "UNWATCH": "Deixar de Observar", "WATCHERS": "Observadores", "BUTTON_TITLE": "Observar/Deixar de observar este item", - "COUNTER_TITLE": "{total, plural, um{um observador} outro{#watchers}}" + "COUNTER_TITLE": "{total, plural, one{um observador} other{#watchers}}" }, "VOTE_BUTTON": { "UPVOTE": "Aprovar", @@ -181,7 +181,7 @@ "DOWNVOTE": "Reprovar", "VOTERS": "Eleitores", "BUTTON_TITLE": "Aprovar/Reprovar este item", - "COUNTER_TITLE": "{total, plural, um{um voto} outro{# votos}}" + "COUNTER_TITLE": "{total, plural, one{um voto} other{# votos}}" }, "CUSTOM_ATTRIBUTES": { "CUSTOM_FIELDS": "Campos Personalizados", @@ -196,7 +196,7 @@ "TITLE_ACTION_FILTER_BUTTON": "procurar", "BREADCRUMB_TITLE": "voltar para categorias", "BREADCRUMB_FILTERS": "Filtros", - "BREADCRUMB_STATUS": "Situação" + "BREADCRUMB_STATUS": "status" }, "WYSIWYG": { "H1_BUTTON": "Primeira caixa de cabeçalho", @@ -225,7 +225,7 @@ "CODE_BLOCK_SAMPLE_TEXT": "Seu texto aqui...", "PREVIEW_BUTTON": "Pré Visualizar", "EDIT_BUTTON": "Editar", - "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP": "Anexe arquivos arrastando e soltando na área de texto acima.", "MARKDOWN_HELP": "Ajuda de sintaxe markdown" }, "PERMISIONS_CATEGORIES": { @@ -237,11 +237,11 @@ "DELETE_SPRINTS": "Apagar sprints" }, "USER_STORIES": { - "NAME": "User Stories", - "VIEW_USER_STORIES": "Ver user stories", - "ADD_USER_STORIES": "Adicionar user stories", - "MODIFY_USER_STORIES": "Modificar estórias de usuário", - "DELETE_USER_STORIES": "Apagar user stories" + "NAME": "Histórias de Usuários", + "VIEW_USER_STORIES": "Ver histórias de usuários", + "ADD_USER_STORIES": "Adicionar histórias de usuários", + "MODIFY_USER_STORIES": "Modificar histórias de usuários", + "DELETE_USER_STORIES": "Apagar histórias de usuários" }, "TASKS": { "NAME": "Tarefas", @@ -251,11 +251,11 @@ "DELETE_TASKS": "Apagar tarefas" }, "ISSUES": { - "NAME": "Casos", - "VIEW_ISSUES": "Ver casos", - "ADD_ISSUES": "Adicionar casos", - "MODIFY_ISSUES": "Modificar casos", - "DELETE_ISSUES": "Apagar casos" + "NAME": "Problemas", + "VIEW_ISSUES": "Ver problemas", + "ADD_ISSUES": "Adicionar problemas", + "MODIFY_ISSUES": "Modificar problemas", + "DELETE_ISSUES": "Apagar problemas" }, "WIKI": { "NAME": "Wiki", @@ -356,9 +356,9 @@ }, "HOME": { "PAGE_TITLE": "Início - Taiga", - "PAGE_DESCRIPTION": "A página inicial do Taiga com seus principais projetos e todas as estórias atribuídas ou observadas por você, tarefas e problemas", - "EMPTY_WORKING_ON": "It feels empty, doesn't it? Start working with Taiga and you'll see here the stories, tasks and issues you are working on.", - "EMPTY_WATCHING": "Siga Estórias, Tarefas e Casos nos seus projetos e seja notificado das mudanças :)", + "PAGE_DESCRIPTION": "A página inicial do Taiga com seus principais projetos e todas as histórias de usuários atribuídas ou observadas por você, tarefas e problemas", + "EMPTY_WORKING_ON": "Parece vazio, não acha? Comece a trabalhar com Taiga e você verá aqui as histórias, tarefas e problemas em que está trabalhando.", + "EMPTY_WATCHING": "Siga Histórias de Usuários, Tarefas e Problemas nos seus projetos e seja notificado das mudanças :)", "EMPTY_PROJECT_LIST": "Você ainda não tem projetos", "WORKING_ON_SECTION": "Trabalhando em", "WATCHING_SECTION": "Observando", @@ -434,20 +434,20 @@ "ENABLE": "Habilitar", "DISABLE": "Desabilitar", "BACKLOG": "Backlog", - "BACKLOG_DESCRIPTION": "Gerencie suas user stories para manter uma visualização organizada de trabalhos futuros e priorizados.", - "NUMBER_SPRINTS": "Expected number of sprints", - "NUMBER_SPRINTS_HELP": "0 for an undetermined number", - "NUMBER_US_POINTS": "Expected total of story points", - "NUMBER_US_POINTS_HELP": "0 for an undetermined number", + "BACKLOG_DESCRIPTION": "Gerencie suas histórias de usuários para manter uma visualização organizada de trabalhos futuros e priorizados.", + "NUMBER_SPRINTS": "Número de sprints esperadas", + "NUMBER_SPRINTS_HELP": "0 para um número indeterminado", + "NUMBER_US_POINTS": "Total esperado de pontos de história", + "NUMBER_US_POINTS_HELP": "0 para um número indeterminado", "KANBAN": "Kanban", "KANBAN_DESCRIPTION": "Organize seu projeto de um jeito \"lean\" com esse mural", "ISSUES": "Problemas", - "ISSUES_DESCRIPTION": "Acompanhe os bugs, questões e melhorias relacionadas ao seu projeto. Não perca nada!", + "ISSUES_DESCRIPTION": "Acompanhe os bugs, problemas e melhorias relacionados ao seu projeto. Não perca nada!", "WIKI": "Wiki", "WIKI_DESCRIPTION": "Adicione, modifique ou apague conteúdo em colaboração com outras pessoas. Este é o local certo pra documentação do seu projeto.", "MEETUP": "Reunião", - "MEETUP_DESCRIPTION": "Choose your videoconference system.", - "SELECT_VIDEOCONFERENCE": "Selecione um sistema de video conferência", + "MEETUP_DESCRIPTION": "Selecione seu sistema de videoconferência.", + "SELECT_VIDEOCONFERENCE": "Selecione um sistema de videoconferência", "SALT_CHAT_ROOM": "Add a prefix to the chatroom name", "JITSI_CHAT_ROOM": "Jitsi", "APPEARIN_CHAT_ROOM": "AppearIn", @@ -476,10 +476,10 @@ "MAX_PRIVATE_PROJECTS_MEMBERS": "The maximum number of members for private projects has been exceeded", "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds your maximum number of members for public projects", - "PROJECT_OWNER": "Project owner", - "REQUEST_OWNERSHIP": "Request ownership", + "PROJECT_OWNER": "Dono do projeto", + "REQUEST_OWNERSHIP": "Solicitar propriedade", "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Gostaria de se tornar o novo dono do projeto?", - "REQUEST_OWNERSHIP_DESC": "Request that current project owner {{name}} transfer ownership of this project to you.", + "REQUEST_OWNERSHIP_DESC": "Solicitar ao atual dono de projeto {{name}} a transferência da propriedade deste projeto para você.", "REQUEST_OWNERSHIP_BUTTON": "Solicitação", "REQUEST_OWNERSHIP_SUCCESS": "We'll notify the project owner", "CHANGE_OWNER": "Change owner", @@ -495,7 +495,7 @@ "REGENERATE_SUBTITLE": "Você está prestes a alterar a url de acesso a dados do CSV. A URL anterior será desabilitada. Você está certo disso?" }, "CSV": { - "SECTION_TITLE_US": "Relatórios de user stories", + "SECTION_TITLE_US": "Relatórios de histórias de usuários", "SECTION_TITLE_TASK": "relatórios de tarefas", "SECTION_TITLE_ISSUE": "relatórios de problemas", "DOWNLOAD": "Baixar CSV", @@ -506,13 +506,13 @@ }, "CUSTOM_FIELDS": { "TITLE": "Campos Personalizados", - "SUBTITLE": "Especificar campos personalizados para estórias, tarefas e problemas", - "US_DESCRIPTION": "Campos personalizados das user stories", - "US_ADD": "Adicionar campo personalizado nas user stories", - "TASK_DESCRIPTION": "Campos personalizados das Tarefas\n", + "SUBTITLE": "Especificar campos personalizados para histórias de usuários, tarefas e problemas", + "US_DESCRIPTION": "Campos personalizados das histórias de usuários", + "US_ADD": "Adicionar campo personalizado nas histórias de usuários", + "TASK_DESCRIPTION": "Campos personalizados das Tarefas", "TASK_ADD": "Adicionar campos personalizados na tarefa", "ISSUE_DESCRIPTION": "Campos personalizados dos problemas", - "ISSUE_ADD": "Adicionar um campo personalizado no problema", + "ISSUE_ADD": "Adicionar um campo personalizado em problemas ", "FIELD_TYPE_TEXT": "Texto", "FIELD_TYPE_MULTI": "Multi-linha", "FIELD_TYPE_DATE": "Data", @@ -525,8 +525,8 @@ }, "PROJECT_VALUES_POINTS": { "TITLE": "Pontos", - "SUBTITLE": "Especifique os pontos de suas user stories poderiam ser estimados ", - "US_TITLE": "Pontos de User Stories", + "SUBTITLE": "Especifique os pontos em que suas histórias de usuários poderiam ser estimados ", + "US_TITLE": "Pontos de Histórias de Usuários", "ACTION_ADD": "Adicionar novo ponto" }, "PROJECT_VALUES_PRIORITIES": { @@ -536,28 +536,28 @@ "ACTION_ADD": "Adicionar nova prioridade" }, "PROJECT_VALUES_SEVERITIES": { - "TITLE": "Seriedades", - "SUBTITLE": "Especifique as severidades que seus problemas terão", - "ISSUE_TITLE": "Seriedades do problema", - "ACTION_ADD": "Adicionar nova severidade" + "TITLE": "Gravidades", + "SUBTITLE": "Especifique as gravidades que seus problemas terão", + "ISSUE_TITLE": "Gravidades do problema", + "ACTION_ADD": "Adicionar nova gravidade" }, "PROJECT_VALUES_STATUS": { - "TITLE": "Situação", - "SUBTITLE": "Especifique os status pelos quais suas estórias, tarefas e problemas passarão", - "US_TITLE": "Estados das Users Strories", - "TASK_TITLE": "Estados da tarefa", - "ISSUE_TITLE": "Estados do caso" + "TITLE": "Status", + "SUBTITLE": "Especifique os status pelos quais suas histórias de usuários, tarefas e problemas passarão", + "US_TITLE": "Estados das Histórias de Usuários", + "TASK_TITLE": "Estados da Tarefa", + "ISSUE_TITLE": "Estados do problema" }, "PROJECT_VALUES_TYPES": { "TITLE": "Tipos", - "SUBTITLE": "Especifique os tipos que seu problema pode ser", - "ISSUE_TITLE": "Tipos de casos", + "SUBTITLE": "Especifique de que tipos seus problemas poderão ser", + "ISSUE_TITLE": "Tipos de problemas", "ACTION_ADD": "Adicionar novo {{objName}}" }, "ROLES": { "PAGE_TITLE": "Funções - {{projectName}}", - "WARNING_NO_ROLE": "Seja cuidadoso, nenhuma função em seu projeto será capaz de estimar o valor dos pontos para as user stories", - "HELP_ROLE_ENABLED": "Quando habilitado, membros atribuídos a esta função serão capazes de estimar valores para user stories", + "WARNING_NO_ROLE": "Seja cuidadoso, nenhuma função em seu projeto será capaz de estimar o valor dos pontos para as histórias de usuários", + "HELP_ROLE_ENABLED": "Quando habilitado, membros atribuídos a esta função serão capazes de estimar valores para histórias de usuários", "DISABLE_COMPUTABLE_ALERT_TITLE": "Você tem certeza que quer desativar esta permissão de estimativas?", "DISABLE_COMPUTABLE_ALERT_SUBTITLE": "Se você desativar a permissão de estimativas para esta regra {{roleName}} todas estimativas feitas anteriormente por esta regra serão removidas.", "COUNT_MEMBERS": "{{ role.members_count }} membros com a mesma função", @@ -589,7 +589,7 @@ "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhooks", - "SUBTITLE": "Webhooks notificam serviços externos sobre eventos no Taiga, como comentários, user stories....", + "SUBTITLE": "Webhooks notificam serviços externos sobre eventos no Taiga, como comentários, histórias de usuários...", "ADD_NEW": "Adicionar um Novo Webhook", "TYPE_NAME": "Digite o nome do serviço", "TYPE_PAYLOAD_URL": "Digite a url do serviço Payload", @@ -606,9 +606,9 @@ "PAYLOAD": "Payload", "RESPONSE": "Resposta", "DATE": "DD MMM YYYY [at] hh:mm:ss", - "ACTION_HIDE_HISTORY": "(Esconder história)", + "ACTION_HIDE_HISTORY": "(Esconder histórico)", "ACTION_HIDE_HISTORY_TITLE": "Esconder os detalhes da história", - "ACTION_SHOW_HISTORY": "(Mostrar história)", + "ACTION_SHOW_HISTORY": "(Mostrar histórico)", "ACTION_SHOW_HISTORY_TITLE": "Mostrar detalhes da história", "WEBHOOK_NAME": "Webhook '{{name}}'" }, @@ -629,7 +629,7 @@ "COLUMN_MEMBER": "Membro", "COLUMN_ADMIN": "Administrador", "COLUMN_ROLE": "Função", - "COLUMN_STATUS": "Situação", + "COLUMN_STATUS": "Status", "STATUS_ACTIVE": "Ativo", "STATUS_PENDING": "Pendente", "DELETE_MEMBER": "Apagar membro", @@ -642,12 +642,12 @@ }, "DEFAULT_VALUES": { "LABEL_POINTS": "Valores padrões para o seletor de pontos", - "LABEL_US": "Valor padrão para seletor de status da US", + "LABEL_US": "Valor padrão para seletor de status da História de Usuário", "LABEL_TASK_STATUS": "Valor padrão para seletor de status de tarefa", "LABEL_PRIORITY": "Valor padão para seletor de prioridade", - "LABEL_SEVERITY": "Valor padrão para seletor de severidade", - "LABEL_ISSUE_TYPE": "Valor padrão para seletor de tipo de caso", - "LABEL_ISSUE_STATUS": "Valor padrão para seletor de status de caso" + "LABEL_SEVERITY": "Valor padrão para seletor de gravidade", + "LABEL_ISSUE_TYPE": "Valor padrão para seletor de tipo de problema ", + "LABEL_ISSUE_STATUS": "Valor padrão para seletor de status de problema" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Digite um nome para o novo status" @@ -674,10 +674,10 @@ "TITLE": "Atributos" }, "SUBMENU_PROJECT_VALUES": { - "STATUS": "Situação", + "STATUS": "Status", "POINTS": "Pontos", "PRIORITIES": "Prioridades", - "SEVERITIES": "Seriedades", + "SEVERITIES": "Gravidades", "TYPES": "Tipos", "CUSTOM_FIELDS": "Campos personalizados" }, @@ -715,7 +715,7 @@ "PAGE_TITLE": "{{userFullName}} (@{{userUsername}})", "EDIT": "Editar Perfil", "FOLLOW": "Seguir", - "CLOSED_US": "US Fechadas", + "CLOSED_US": "Histórias Fechadas", "PROJECTS": "Projetos", "PROJECTS_EMPTY": "{{username}} ainda não tem projetos", "CONTACTS": "Contatos", @@ -749,8 +749,8 @@ "FILTER_TYPE_ALL_TITLE": "Mostrar tudo", "FILTER_TYPE_PROJECTS": "Projetos", "FILTER_TYPE_PROJECT_TITLES": "Mostrar somente projetos", - "FILTER_TYPE_USER_STORIES": "Stories", - "FILTER_TYPE_USER_STORIES_TITLES": "Mostrar apenas user stories", + "FILTER_TYPE_USER_STORIES": "Histórias", + "FILTER_TYPE_USER_STORIES_TITLES": "Mostrar apenas histórias de usuários", "FILTER_TYPE_TASKS": "Tarefas", "FILTER_TYPE_TASK_TITLES": "Mostrar apenas tarefas", "FILTER_TYPE_ISSUES": "Problemas", @@ -765,11 +765,11 @@ "HELP": "Reordene seus projetos para colocar no topo os mais usados.
Os 10 primeiros projetos aparecerão na lista de projetos da barra de navegação superior.", "PRIVATE": "Projeto Privado", "LOOKING_FOR_PEOPLE": "Este projeto esta a procura de colaboradores", - "FANS_COUNTER_TITLE": "{total, plural, um{um fã} outro{# fãs}}", - "WATCHERS_COUNTER_TITLE": "{total, plural, um{um observador} outro{#watchers}}", - "MEMBERS_COUNTER_TITLE": "{total, plural, um{one member} outro{# members}}", + "FANS_COUNTER_TITLE": "{total, plural, one{um fã} other{# fãs}}", + "WATCHERS_COUNTER_TITLE": "{total, plural, one{um observador} other{#watchers}}", + "MEMBERS_COUNTER_TITLE": "{total, plural, one{one member} other{# members}}", "BLOCKED_PROJECT": { - "BLOCKED": "Blocked project", + "BLOCKED": "Projeto bloqueado", "THIS_PROJECT_IS_BLOCKED": "This project is temporarily blocked", "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "In order to unblock your projects, contact the administrator." }, @@ -866,13 +866,13 @@ "LIKED": "Curtiu", "UNLIKE": "Descurtir", "BUTTON_TITLE": "Curtir ou descurtir este projeto", - "COUNTER_TITLE": "{total, plural, um{um fã} outro{# fãs}}" + "COUNTER_TITLE": "{total, plural, one{um fã} other{# fãs}}" }, "WATCH_BUTTON": { "BUTTON_TITLE": "Observar este projeto e definir política de notificação", "WATCH": "Observar", "WATCHING": "Observando", - "COUNTER_TITLE": "{total, plural, um{um observador} outro{#watchers}}", + "COUNTER_TITLE": "{total, plural, one{um observador} other{#watchers}}", "OPTIONS": { "NOTIFY_ALL": "receber todas notificações", "NOTIFY_ALL_TITLE": "Receber todas notificações para este projeto", @@ -891,12 +891,12 @@ "CANCEL": "Voltar para configurações", "ACCEPT": "Remover conta", "BLOCK_PROJECT": "Note that all the projects you own projects will be blocked after you delete your account. If you do want a project blocked, transfer ownership to another member of each project prior to deleting your account.", - "SUBTITLE": "Sorry to see you go. We'll be here if you should ever consider us again! :(" + "SUBTITLE": "É uma pena vê-lo partir. Estaremos aqui se você algum dia considerar-nos novamente! :(" }, "DELETE_PROJECT": { "TITLE": "Apagar projeto", "QUESTION": "Você tem certeza que quer apagar este projeto?", - "SUBTITLE": "Todas as informações do projeto (estórias, tarefas, problemas, sprints e páginas de wiki) serão perdidos! :-(", + "SUBTITLE": "Todas as informações do projeto (histórias, tarefas, problemas, sprints e páginas de wiki) serão perdidas! :-(", "CONFIRM": "Sim, eu tenho certeza" }, "ASSIGNED_TO": { @@ -920,27 +920,27 @@ "PLACEHOLDER_SEARCH": "O que você está procurando?" }, "ADD_EDIT_SPRINT": { - "TITLE": "Novo Sprint", - "PLACEHOLDER_SPRINT_NAME": "nome do sprint", + "TITLE": "Nova Sprint", + "PLACEHOLDER_SPRINT_NAME": "nome da sprint", "PLACEHOLDER_SPRINT_START": "Inicio Estimado", "PLACEHOLDER_SPRINT_END": "Estimativa de Termino", - "ACTION_DELETE_SPRINT": "Você quer apagar este sprint?", + "ACTION_DELETE_SPRINT": "Você quer apagar esta sprint?", "TITLE_ACTION_DELETE_SPRINT": "apagar sprint", - "LAST_SPRINT_NAME": "último sprint é {{lastSprint}} ;-) " + "LAST_SPRINT_NAME": "última sprint é {{lastSprint}} ;-) " }, "CREATE_EDIT_TASK": { "TITLE": "Nova tarefa", "PLACEHOLDER_SUBJECT": "Um assunto da tarefa", - "PLACEHOLDER_STATUS": "Situação da Tarefa", + "PLACEHOLDER_STATUS": "Status da Tarefa", "OPTION_UNASSIGNED": "Não assinalado", "PLACEHOLDER_SHORT_DESCRIPTION": "Escreva uma curta descrição", "ACTION_EDIT": "Editar tarefa" }, "CREATE_EDIT_US": { - "TITLE": "Nova EU", - "PLACEHOLDER_DESCRIPTION": "Por favor, adicione um texto descritivo para ajudar outras pessoas a entenderem melhor esta EU", - "NEW_US": "Nova user story", - "EDIT_US": "Editar user story" + "TITLE": "Nova História de Usuário", + "PLACEHOLDER_DESCRIPTION": "Por favor, adicione um texto descritivo para ajudar outras pessoas a entenderem melhor esta história de usuário", + "NEW_US": "Nova história de usuário", + "EDIT_US": "Editar história de usuário" }, "DELETE_SPRINT": { "TITLE": "Apagar sprint" @@ -963,33 +963,33 @@ } }, "CHANGE_OWNER": { - "TITLE": "Who do you want to be the new project owner?", - "ADD_COMMENT": "Add comment", - "BUTTON": "Ask this project member to become the new project owner" + "TITLE": "Quem você quer que seja o novo dono do projeto?", + "ADD_COMMENT": "Adicionar comentário", + "BUTTON": "Pedir a este membro do projeto para se tornar o novo dono do projeto" } }, "US": { - "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", + "PAGE_TITLE": "{{userStorySubject}} - História de Usuário {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{userStoryStatus }}. Completos {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tarefas encerradas). Pontos: {{userStoryPoints}}. Descrição: {{userStoryDescription}}", - "SECTION_NAME": "Detalhes de User Story", + "SECTION_NAME": "Detalhes da História de Usuário", "LINK_TASKBOARD": "Quadro de Tarefas", "TITLE_LINK_TASKBOARD": "Ir para o quadro de tarefas", "TOTAL_POINTS": "total de pontos", - "ADD": "+ Adicionar uma nova User Story", - "ADD_BULK": "Adicionar User Stories em lote", - "PROMOTED": "Esta estória de uso foi promovida do problema:", + "ADD": "+ Adicionar uma nova História de Usuário", + "ADD_BULK": "Adicionar Histórias de Usuários em lote", + "PROMOTED": "Esta história de usuário foi promovida do problema:", "TITLE_LINK_GO_TO_ISSUE": "Ir para problema", - "EXTERNAL_REFERENCE": "Esta EU foi criada de", + "EXTERNAL_REFERENCE": "Esta História de Usuário foi criada de", "GO_TO_EXTERNAL_REFERENCE": "Ir para a origem", - "BLOCKED": "Esta user story está bloqueada", - "PREVIOUS": "user story anterior", - "NEXT": "proxima user story", - "TITLE_DELETE_ACTION": "Apagar user story", - "LIGHTBOX_TITLE_BLOKING_US": "Bloqueando user story", + "BLOCKED": "Esta história de usuário está bloqueada", + "PREVIOUS": "história de usuário anterior", + "NEXT": "proxima história de usuário", + "TITLE_DELETE_ACTION": "Apagar história de usuário", + "LIGHTBOX_TITLE_BLOKING_US": "História de usuário bloqueadora", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tarefas completas", - "ASSIGN": "Atribuir User Story", + "ASSIGN": "Atribuir História de Usuário", "NOT_ESTIMATED": "Não estimado", - "TOTAL_US_POINTS": "Pontos totais de US", + "TOTAL_US_POINTS": "Total de pontos de histórias", "FIELDS": { "TEAM_REQUIREMENT": "Requisitos da Equipe", "CLIENT_REQUIREMENT": "Requisitos do Cliente", @@ -1013,7 +1013,7 @@ "TITLE": "Atividade", "REMOVED": "removido", "ADDED": "adicionado", - "US_POINTS": "pontos EU ({{name}})", + "US_POINTS": "pontos de história ({{name}})", "NEW_ATTACHMENT": "novo anexo", "DELETED_ATTACHMENT": "apagar anexo", "UPDATED_ATTACHMENT": "anexo atualizado {{filename}}", @@ -1030,16 +1030,16 @@ "NAME": "nome", "DESCRIPTION": "descrição", "CONTENT": "conteúdo", - "STATUS": "Situação", + "STATUS": "status", "IS_CLOSED": "está fechado", "FINISH_DATE": "Data de término", "TYPE": "Digite", "PRIORITY": "prioridade", - "SEVERITY": "Severidade", + "SEVERITY": "gravidade", "ASSIGNED_TO": "Assinado à", "WATCHERS": "Observadores", "MILESTONE": "sprint", - "USER_STORY": "user story", + "USER_STORY": "história de usuário", "PROJECT": "projeto", "IS_BLOCKED": "está bloqueada", "BLOCKED_NOTE": "Nota de bloqueio", @@ -1055,41 +1055,41 @@ "SPRINT_ORDER": "ordem de sprint ", "KANBAN_ORDER": "pedido kanban", "TASKBOARD_ORDER": "Ordem de quadro de tarefa", - "US_ORDER": "requisição US " + "US_ORDER": "ordem da história de usuário" } }, "BACKLOG": { "PAGE_TITLE": "Backlog - {{projectName}}", - "PAGE_DESCRIPTION": "O painel de backlog, com user stories e sprints do projeto {{projectName}}: {{projectDescription}}", + "PAGE_DESCRIPTION": "O painel de backlog, com histórias de usuário e sprints do projeto {{projectName}}: {{projectDescription}}", "SECTION_NAME": "Backlog", "CUSTOMIZE_GRAPH": "Personalize seu gráficos de backlog", - "CUSTOMIZE_GRAPH_TEXT": "To have a nice graph that helps you follow the evolution of the project you have to set up the points and sprints through the", + "CUSTOMIZE_GRAPH_TEXT": "Para ter um gráfico que te ajuda a seguir a evolução do projeto você deve configurar os pontos e as sprints via", "CUSTOMIZE_GRAPH_ADMIN": "Administrador", "CUSTOMIZE_GRAPH_TITLE": "Configure os pontos e sprints pelo Administrador", - "MOVE_US_TO_CURRENT_SPRINT": "Mover para sprint corrente", - "MOVE_US_TO_LATEST_SPRINT": "ir para o último sprint", + "MOVE_US_TO_CURRENT_SPRINT": "Mover para a Sprint Atual", + "MOVE_US_TO_LATEST_SPRINT": "Mover para a última Sprint", "SHOW_FILTERS": "Mostrar filtros", "SHOW_TAGS": "Exibir tags", "EMPTY": "O backlog está vazio!", - "CREATE_NEW_US": "Criar uma nova EU", - "CREATE_NEW_US_EMPTY_HELP": "Você talvez queira criar uma nova user story", + "CREATE_NEW_US": "Criar uma nova História de Usuário", + "CREATE_NEW_US_EMPTY_HELP": "Você talvez queira criar uma nova história de usuário", "EXCESS_OF_POINTS": "Excesso de pontos", "PENDING_POINTS": "Pontos Pendentes", "CLOSED_POINTS": "fechado", - "COMPACT_SPRINT": "Sprint Resumido", + "COMPACT_SPRINT": "Sprint Resumida", "GO_TO_TASKBOARD": "Ir ao quadro de tarefas de {{::name}}", "EDIT_SPRINT": "Editar sprint", "TOTAL_POINTS": "total", - "STATUS_NAME": "Nome de Situação", + "STATUS_NAME": "Nome de Status", "SORTABLE_FILTER_ERROR": "Você não pode jogar sobre o backlog quando filtros estão abertos", "DOOMLINE": "Escopo do projeto [Doomline]", "CHART": { - "XAXIS_LABEL": "Sprintes", + "XAXIS_LABEL": "Sprints", "YAXIS_LABEL": "Pontos", - "OPTIMAL": "Ideal de pontos pendentes para o sprint \"{{sprintName}}\" deve ser {{value}}", - "REAL": "Pontos realmente pendentes para o sprint \"{{sprintName}}\" é {{value}}", - "INCREMENT_TEAM": "Pontos incrementados pelos requisitos do time para o sprint \"{{sprintName}}\" é {{value}}", - "INCREMENT_CLIENT": "Pontos incrementados pelos requisitos do cliente para o sprint \"{{sprintName}}\" é {{value}}" + "OPTIMAL": "Ideal de pontos pendentes para a sprint \"{{sprintName}}\" deve ser {{value}}", + "REAL": "Pontos realmente pendentes para a sprint \"{{sprintName}}\" é {{value}}", + "INCREMENT_TEAM": "Pontos acrescentados pelos requisitos do time para a sprint \"{{sprintName}}\" é {{value}}", + "INCREMENT_CLIENT": "Pontos acrescentados pelos requisitos do cliente para a sprint \"{{sprintName}}\" é {{value}}" }, "TAGS": { "TOGGLE": "Alternar visibilidade das tags", @@ -1097,7 +1097,7 @@ "HIDE": "Esconder tags" }, "TABLE": { - "COLUMN_US": "User Stories", + "COLUMN_US": "Histórias de Usuários", "TITLE_COLUMN_POINTS": "Selecionar exibição por função" }, "SPRINT_SUMMARY": { @@ -1107,7 +1107,7 @@ "CLOSED_TASKS": "tarefas
fechadas", "IOCAINE_DOSES": "iocaine
doses", "SHOW_STATISTICS_TITLE": "Mostrar estatísticas", - "TOGGLE_BAKLOG_GRAPH": "Mostrar/Esconder gráfico burndown" + "TOGGLE_BAKLOG_GRAPH": "Mostrar/Esconder gráfico de burndown" }, "SUMMARY": { "PROJECT_POINTS": "pontos do
projeto", @@ -1121,22 +1121,22 @@ "REMOVE": "Remover filtros", "HIDE": "Esconder Filtros", "SHOW": "Mostrar Filtros", - "FILTER_CATEGORY_STATUS": "Situação", + "FILTER_CATEGORY_STATUS": "Status", "FILTER_CATEGORY_TAGS": "Tags" }, "SPRINTS": { "TITLE": "SPRINTS", "DATE": "DD MMM YYYY", - "LINK_TASKBOARD": "Quadro de tarefas do sprint", + "LINK_TASKBOARD": "Quadro de tarefas da Sprint", "TITLE_LINK_TASKBOARD": "ir para quadro de tarefas de \"{{name}}\"", "NUMBER_SPRINTS": "
sprints", "EMPTY": "Ainda não temos sprints.", - "WARNING_EMPTY_SPRINT_ANONYMOUS": "Esse sprint não tem estórias de usuário", - "WARNING_EMPTY_SPRINT": "Solte aqui estórias do seu backlog para iniciar uma nova sprint", + "WARNING_EMPTY_SPRINT_ANONYMOUS": "Esta sprint não tem Histórias de Usuário", + "WARNING_EMPTY_SPRINT": "Solte aqui Histórias do seu backlog para iniciar uma nova sprint", "TITLE_ACTION_NEW_SPRINT": "Adicionar nova sprint", "TEXT_ACTION_NEW_SPRINT": "Você poderá querer criar uma nova sprint em seu projeto", - "ACTION_SHOW_CLOSED_SPRINTS": "Mostre os sprints fechados", - "ACTION_HIDE_CLOSED_SPRINTS": "Esconder sprints fechados" + "ACTION_SHOW_CLOSED_SPRINTS": "Mostrar sprints fechadas", + "ACTION_HIDE_CLOSED_SPRINTS": "Esconder sprints fechadas" } }, "ERROR": { @@ -1148,7 +1148,7 @@ "VERSION_ERROR": "Alguém dentro da Taiga mudou isso antes e nosso Oompa Loompa não podem aplicar suas alterações. Recarregue a página e aplique-as novamente (elas serão perdidas, agora)." }, "TASKBOARD": { - "PAGE_TITLE": "{{sprintName}} - Quadro de Tarefas do Sprint - {{projectName}}", + "PAGE_TITLE": "{{sprintName}} - Quadro de Tarefas da Sprint - {{projectName}}", "PAGE_DESCRIPTION": "Sprint {{sprintName}} (de{{startDate}} até {{endDate}}) para {{projectName}}. Completos {{completedPercentage}}% ({{completedPoints}} de {{totalPoints}} pontos). {{openTasks}} tarefas abertas de {{totalTasks}}.", "SECTION_NAME": "Quadro de Tarefas", "TITLE_ACTION_ADD": "Adicionar uma nova Tarefa", @@ -1156,9 +1156,9 @@ "TITLE_ACTION_ASSIGN": "Assinalar tarefa", "TITLE_ACTION_EDIT": "Editar tarefa", "PLACEHOLDER_CARD_TITLE": "Isto pode ser uma atividade", - "PLACEHOLDER_CARD_TEXT": "Separe Estórias em atividades para rastrear separadamente.", + "PLACEHOLDER_CARD_TEXT": "Separe Histórias de Usuários em atividades para rastreá-las separadamente.", "TABLE": { - "COLUMN": "user story", + "COLUMN": "História de usuário", "TITLE_ACTION_FOLD": "Recolher coluna", "TITLE_ACTION_UNFOLD": "Abrir coluna", "TITLE_ACTION_FOLD_ROW": "Guardar Linha", @@ -1181,11 +1181,11 @@ "LINK_TASKBOARD": "Quadro de Tarefas", "TITLE_LINK_TASKBOARD": "Ir para o quadro de tarefas", "PLACEHOLDER_SUBJECT": "Digite um novo titulo para tarefa", - "TITLE_SELECT_STATUS": "Nome de Situação", + "TITLE_SELECT_STATUS": "Nome de Status", "OWNER_US": "Esta tarefa pertence a ", - "TITLE_LINK_GO_OWNER": "Ir para user story", + "TITLE_LINK_GO_OWNER": "Ir para história de usuário", "ORIGIN_US": "Essa tarefa foi criada a partir de", - "TITLE_LINK_GO_ORIGIN": "Ir para user story", + "TITLE_LINK_GO_ORIGIN": "Ir para história de usuário", "BLOCKED": "Esta tarefa está bloqueada", "PREVIOUS": "tarefa anterior", "NEXT": "nova tarefa", @@ -1193,7 +1193,7 @@ "LIGHTBOX_TITLE_BLOKING_TASK": "Tarefa bloqueadora", "FIELDS": { "MILESTONE": "Sprint", - "USER_STORY": "User story", + "USER_STORY": "História de usuário", "IS_IOCAINE": "É Iocaine" }, "ACTION_IOCAINE": "Iocaine", @@ -1216,7 +1216,7 @@ "SUCCESS": "Nossos Oompa Loompas removeram sua conta" }, "CHANGE_EMAIL_FORM": { - "TITLE": "Mudar seu email", + "TITLE": "Mudar seu e-mail", "SUBTITLE": "Só mais um clique e seu email será atualizado!", "PLACEHOLDER_INPUT_TOKEN": "mudar token do email", "ACTION_CHANGE_EMAIL": "Mudar email", @@ -1226,26 +1226,26 @@ "PAGE_TITLE": "Problemas - {{projectName}}", "PAGE_DESCRIPTION": "O painel de problemas do projeto {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Problemas", - "SECTION_NAME": "Detalhes do caso", - "ACTION_NEW_ISSUE": "+ NOVO CASO", - "ACTION_PROMOTE_TO_US": "Promover para User Story", + "SECTION_NAME": "Detalhes do problema", + "ACTION_NEW_ISSUE": "+ NOVO PROBLEMA", + "ACTION_PROMOTE_TO_US": "Promover para História de Usuário", "PLACEHOLDER_FILTER_NAME": "Digite o nome do filtro e pressione Enter", - "PROMOTED": "Esse caso foi promovido para user strory", - "EXTERNAL_REFERENCE": "Esse caso foi criado a partir de", + "PROMOTED": "Esse problema foi promovido para história de usuário", + "EXTERNAL_REFERENCE": "Esse problema foi criado a partir de", "GO_TO_EXTERNAL_REFERENCE": "Ir para a origem", - "BLOCKED": "Esse caso está bloqueado", - "TITLE_PREVIOUS_ISSUE": "caso anterior", - "TITLE_NEXT_ISSUE": "próximo caso", - "ACTION_DELETE": "Caso apagado", - "LIGHTBOX_TITLE_BLOKING_ISSUE": "Caso que está bloqueando", + "BLOCKED": "Esse problema está bloqueado", + "TITLE_PREVIOUS_ISSUE": "problema anterior", + "TITLE_NEXT_ISSUE": "próximo problema", + "ACTION_DELETE": "Problema apagado", + "LIGHTBOX_TITLE_BLOKING_ISSUE": "Problema que está bloqueando", "FIELDS": { "PRIORITY": "Prioridade", - "SEVERITY": "Severidade", + "SEVERITY": "Gravidade", "TYPE": "Tipo" }, "CONFIRM_PROMOTE": { - "TITLE": "Promover esse caso para nova user story", - "MESSAGE": "Você tem certeza que deseja criar uma nova HU para esse caso?" + "TITLE": "Promover esse problema para nova história de usuário", + "MESSAGE": "Você tem certeza que deseja criar uma nova História de Usuário a partir desse problema?" }, "FILTERS": { "TITLE": "Filtros", @@ -1256,8 +1256,8 @@ "TITLE_BREADCRUMB": "Filtros", "CATEGORIES": { "TYPE": "Tipo", - "STATUS": "Situação", - "SEVERITY": "Severidade", + "STATUS": "Status", + "SEVERITY": "Gravidade", "PRIORITIES": "Prioridades", "TAGS": "Tags", "ASSIGNED_TO": "Atribuído a", @@ -1272,11 +1272,11 @@ "TABLE": { "COLUMNS": { "TYPE": "Tipo", - "SEVERITY": "Severidade", + "SEVERITY": "Gravidade", "PRIORITY": "Prioridade", "SUBJECT": "Assunto", "VOTES": "Votos", - "STATUS": "Situação", + "STATUS": "Status", "CREATED": "Criado", "ASSIGNED_TO": "Atribuído a" }, @@ -1284,38 +1284,38 @@ "TITLE_ACTION_ASSIGNED_TO": "Atribuído a", "BLOCKED": "Bloqueado", "EMPTY": { - "TITLE": "Não há casos para reportar :-)", - "SUBTITLE": "Você encontrou um caso ?" + "TITLE": "Não há problemas para reportar :-)", + "SUBTITLE": "Você encontrou um problema?" } } }, "ISSUE": { "PAGE_TITLE": "{{issueSubject}} - problema {{issueRef}} - {{projectName}}", - "PAGE_DESCRIPTION": "Estado: {{issueStatus }}. Tipo: {{issueType}}, Prioridade: {{issuePriority}}. severidade: {{issueSeverity}}. Descrição: {{issueDescription}}" + "PAGE_DESCRIPTION": "Estado: {{issueStatus }}. Tipo: {{issueType}}, Prioridade: {{issuePriority}}. gravidade: {{issueSeverity}}. Descrição: {{issueDescription}}" }, "KANBAN": { "PAGE_TITLE": "Kanban - {{projectName}}", - "PAGE_DESCRIPTION": "O painel do kanban, contendo user stories do projeto {{projectName}}: {{projectDescription}}", + "PAGE_DESCRIPTION": "O painel do kanban, contendo histórias de usuários do projeto {{projectName}}: {{projectDescription}}", "SECTION_NAME": "Kanban", "TITLE_ACTION_FOLD": "Recolher coluna", "TITLE_ACTION_UNFOLD": "Abrir coluna", "TITLE_ACTION_FOLD_CARDS": "Guardar cartões", "TITLE_ACTION_UNFOLD_CARDS": "Mostrar cartões", - "TITLE_ACTION_ADD_US": "Adicionar Nova User Story", + "TITLE_ACTION_ADD_US": "Adicionar Nova História de Usuário", "TITLE_ACTION_ADD_BULK": "Adicionar novo lote", "ACTION_SHOW_ARCHIVED": "Mostrar arquivados", "ACTION_HIDE_ARCHIVED": "esconder arquivados", - "HIDDEN_USER_STORIES": "As user stories nesse status estão escondidas por padrão", + "HIDDEN_USER_STORIES": "As histórias de usuários nesse status são escondidas por padrão", "ARCHIVED": "Você arquivou", "UNDO_ARCHIVED": "Pegue e arraste novamente para desfazer", - "PLACEHOLDER_CARD_TITLE": "Estas são suas Estórias de Usuário ", - "PLACEHOLDER_CARD_TEXT": "Estórias deverá também ter sub-atividades para separar os requerimentos" + "PLACEHOLDER_CARD_TITLE": "Estas são suas Histórias de Usuários", + "PLACEHOLDER_CARD_TEXT": "Histórias poderão também ter sub-atividades para separar os requisitos" }, "SEARCH": { "PAGE_TITLE": "Buscar - {{projectName}}", - "PAGE_DESCRIPTION": "Busque qualquer coisa, user stories, casos, tarefas, ou páginas da wiki, no projeto {{projectName}}: {{projectDescription}}", - "FILTER_USER_STORIES": "User Stories", - "FILTER_ISSUES": "casos", + "PAGE_DESCRIPTION": "Busque qualquer coisa, histórias de usuários, problemas, tarefas, ou páginas da wiki, no projeto {{projectName}}: {{projectDescription}}", + "FILTER_USER_STORIES": "Histórias de Usuários", + "FILTER_ISSUES": "Problemas", "FILTER_TASKS": "Tarefas", "FILTER_WIKI": "Paginas Wiki", "PLACEHOLDER_SEARCH": "Procurar em...", @@ -1330,13 +1330,13 @@ "APP_TITLE": "EQUIPE - {{projectName}}", "PLACEHOLDER_INPUT_SEARCH": "Procurar pelo nome completo...", "COLUMN_MR_WOLF": "Sr. Wolf", - "EXPLANATION_COLUMN_MR_WOLF": "Fechar caso", + "EXPLANATION_COLUMN_MR_WOLF": "Problemas fechados", "COLUMN_IOCAINE": "Bebedor de Iocaine", "EXPLANATION_COLUMN_IOCAINE": "Doses de Iocaine ingeridas", "COLUMN_CERVANTES": "Pero Vaz de Caminha", "EXPLANATION_COLUMN_CERVANTES": "Páginas wiki editadas", "COLUMN_BUG_HUNTER": "Caçador de bugs", - "EXPLANATION_COLUMN_BUG_HUNTER": "Caso reportado", + "EXPLANATION_COLUMN_BUG_HUNTER": "Problemas reportados", "COLUMN_NIGHT_SHIFT": "Periodo Noturno", "EXPLANATION_COLUMN_NIGHT_SHIFT": "Tarefa fechada", "COLUMN_TOTAL_POWER": "Total de poder", @@ -1389,16 +1389,16 @@ "LANGUAGE": "Idioma", "LANGUAGE_DEFAULT": "-- usar idioma padrão --", "THEME": "Tema", - "THEME_DEFAULT": "-- user tema padrão --" + "THEME_DEFAULT": "-- usar tema padrão --" } }, "WIZARD": { "SECTION_TITLE_CREATE_PROJECT": "Criar Projeto", "CREATE_PROJECT_TEXT": "Novo em folha. Tão excitante!", "CHOOSE_TEMPLATE": "Which template fits your project best?", - "CHOOSE_TEMPLATE_TITLE": "More info about project templates", - "CHOOSE_TEMPLATE_INFO": "More info", - "PROJECT_DETAILS": "Project Details", + "CHOOSE_TEMPLATE_TITLE": "Mais informações sobre templates de projeto", + "CHOOSE_TEMPLATE_INFO": "Mais informações", + "PROJECT_DETAILS": "Detalhes do Projeto", "PUBLIC_PROJECT": "Projeto Público", "PRIVATE_PROJECT": "Projeto Privado", "CREATE_PROJECT": "Criar projeto", @@ -1413,7 +1413,7 @@ "PLACEHOLDER_PAGE": "Escreve sua página wiki", "REMOVE": "Remover essa página wiki", "DELETE_LIGHTBOX_TITLE": "Apagar página Wiki", - "DELETE_LINK_TITLE": "Delete Wiki link", + "DELETE_LINK_TITLE": "Remover link de Wiki", "NAVIGATION": { "SECTION_NAME": "Links", "ACTION_ADD_LINK": "Adicionar link" @@ -1432,44 +1432,44 @@ "HINT1_TEXT": "Isso permite você extrair todo o seu conteúdo de um Taiga e mover para outro.", "HINT2_TITLE": "Você sabia que pode criar campos personalizados?", "HINT2_TEXT": "Equipes agora podem personalizar campos, um jeito flexível de inserir dados específicos para uso em seu próprio fluxo de trabalho.", - "HINT3_TITLE": "Reorder your projects to feature those most relevant to you.", + "HINT3_TITLE": "Reordene seus projetos para evidenciar os mais relevantes para você.", "HINT3_TEXT": "The 10 projects are listed in the direct access bar at the top.", "HINT4_TITLE": "Você esqueceu onde está trabalhando?", - "HINT4_TEXT": "Não se preocupe, no seu painel você vai encontrar suas tarefas abertas, casos, e user stories na ordem que você trabalha neles." + "HINT4_TEXT": "Não se preocupe, no seu painel você vai encontrar suas tarefas abertas, problemas, e histórias de usuários na ordem em que você trabalhou nelas." }, "TIMELINE": { "UPLOAD_ATTACHMENT": "{{username}} adicionou um novo anexo em {{obj_name}}", - "US_CREATED": "{{username}} criou uma nova US {{obj_name}} em {{project_name}}", - "ISSUE_CREATED": "{{username}} criou um novo caso {{obj_name}} em {{project_name}}", + "US_CREATED": "{{username}} criou uma nova História de Usuário {{obj_name}} em {{project_name}}", + "ISSUE_CREATED": "{{username}} criou um novo problema {{obj_name}} em {{project_name}}", "TASK_CREATED": "{{username}} criou uma nova tarefa {{obj_name}} em {{project_name}}", - "TASK_CREATED_WITH_US": "{{username}} criou nova tarefa {{obj_name}} em {{project_name}} que pertence a US {{us_name}}", + "TASK_CREATED_WITH_US": "{{username}} criou nova tarefa {{obj_name}} em {{project_name}} que pertence a História de Usuário {{us_name}}", "WIKI_CREATED": "{{username}} criou uma página wiki {{obj_name}} em {{project_name}}", - "MILESTONE_CREATED": "{{username}} criou um novo sprint {{obj_name}} em {{project_name}}", + "MILESTONE_CREATED": "{{username}} criou uma nova sprint {{obj_name}} em {{project_name}}", "NEW_PROJECT": "{{username}} criou o projeto {{project_name}}", - "MILESTONE_UPDATED": "{{username}} atualizou o sprint {{obj_name}}", - "US_UPDATED": "{{username}} atualizou o atributo \"{{field_name}}\" da US {{obj_name}}", + "MILESTONE_UPDATED": "{{username}} atualizou a sprint {{obj_name}}", + "US_UPDATED": "{{username}} atualizou o atributo \"{{field_name}}\" da História de Usuário {{obj_name}}", "US_UPDATED_WITH_NEW_VALUE": "{{username}} atualizou o trabalho \"{{field_name}}\" da US {{obj_name}} para {{new_value}}", - "US_UPDATED_POINTS": "{{username}} atualizou pontos de '{{role_name}}' da US {{obj_name}} para {{new_value}}", - "ISSUE_UPDATED": "{{username}} atualizou o atributo \"{{field_name}}\" do caso {{obj_name}}", - "ISSUE_UPDATED_WITH_NEW_VALUE": "{{username}} atualizou o atributo \"{{field_name}}\" do caso {{obj_name}} para {{new_value}}", + "US_UPDATED_POINTS": "{{username}} atualizou pontos de '{{role_name}}' da História de Usuário {{obj_name}} para {{new_value}}", + "ISSUE_UPDATED": "{{username}} atualizou o atributo \"{{field_name}}\" do problema {{obj_name}}", + "ISSUE_UPDATED_WITH_NEW_VALUE": "{{username}} atualizou o atributo \"{{field_name}}\" do problema {{obj_name}} para {{new_value}}", "TASK_UPDATED": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} para {{new_value}}", "TASK_UPDATED_WITH_NEW_VALUE": "{{username}} Atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} para {{new_value}}", - "TASK_UPDATED_WITH_US": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa{{obj_name}} que pertence a US {{us_name}}", - "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} que pertence a US {{us_name}} para {{new_value}}", + "TASK_UPDATED_WITH_US": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} que pertence à História de Usuário {{us_name}}", + "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} que pertence à História de Usuário {{us_name}} para {{new_value}}", "WIKI_UPDATED": "{{username}} atualizou a página wiki {{obj_name}}", - "NEW_COMMENT_US": "{{username}} comentou na US {{obj_name}}", - "NEW_COMMENT_ISSUE": "{{username}} comentou no issue {{obj_name}}", + "NEW_COMMENT_US": "{{username}} comentou na História de Usuário {{obj_name}}", + "NEW_COMMENT_ISSUE": "{{username}} comentou no problema {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} comentou na tarefa {{obj_name}}", "NEW_MEMBER": "{{project_name}} tem um membro novo", - "US_ADDED_MILESTONE": "{{username}} adicionou a estória {{obj_name}} a {{sprint_name}}", - "US_MOVED": "{{username}} moveu a US {{obj_name}}", - "US_REMOVED_FROM_MILESTONE": "{{username}} adicionou a US {{obj_name}} ao backlog", + "US_ADDED_MILESTONE": "{{username}} adicionou a História de Usuário {{obj_name}} a {{sprint_name}}", + "US_MOVED": "{{username}} moveu a História de Usuário {{obj_name}}", + "US_REMOVED_FROM_MILESTONE": "{{username}} adicionou a História de Usuário {{obj_name}} ao backlog", "BLOCKED": "{{username}} bloqueou {{obj_name}}", "UNBLOCKED": "{{username}} desbloqueou {{obj_name}}", "NEW_USER": "{{username}} ingressou no Taiga" }, "LEGAL": { - "TERMS_OF_SERVICE_AND_PRIVACY_POLICY_AD": "When creating a new account, you agree to our
terms of service and privacy policy." + "TERMS_OF_SERVICE_AND_PRIVACY_POLICY_AD": "Ao criar uma nova conta, você concorda com nossos
termos de serviço e política de privacidade." }, "EXTERNAL_APP": { "PAGE_TITLE": "Um app externo requer autenticação", @@ -1493,7 +1493,7 @@ }, "STEP2": { "TITLE": "Trabalhando em", - "TEXT": "Aqui você irá encontrar as Estórias do Usuário, Atividades e Problemas no qual você está trabalhando." + "TEXT": "Aqui você irá encontrar as Histórias de Usuários, Atividades e Problemas em que você está trabalhando." }, "STEP3": { "TITLE": "Observando", @@ -1514,15 +1514,15 @@ }, "STEP2": { "TITLE": "Backlog do Produto", - "TEXT": "The backlog is the list of requirements (User Stories) for the project. Here is where you will plan your sprints." + "TEXT": "O backlog é a lista de requisitos (Histórias de Usuário) do projeto. Aqui é onde você planejará suas sprints." }, "STEP3": { "TITLE": "Sprints", - "TEXT": "Sprints são períodos curtos de tempo (geralmente 2 semanas) durante o qual trabalho especificado tem que ser completado e entregue." + "TEXT": "Sprints são períodos curtos de tempo (geralmente 2 semanas) durante as quais o trabalho especificado tem que ser concluído e entregue." }, "STEP4": { - "TITLE": "User Stories", - "TEXT": "Estes são os requisitos em alto nível. Você pode adicioná-los ao backlog e arrastá-los ao sprint em que deve ser entregue. " + "TITLE": "Histórias de Usuários", + "TEXT": "Estes são os requisitos em alto nível. Você pode adicioná-los ao backlog e arrastá-los à sprint em que devem ser entregues. " } }, "KANBAN": { @@ -1531,21 +1531,21 @@ "TEXT": "Set up the columns you need to map your workflow statuses through the admin." }, "STEP2": { - "TITLE": "Estórias do Usuário & Atividades", - "TEXT": "Estórias do Usuário são os requerimentos de alto nível. Você pode arrastar eles para diferentes colunas." + "TITLE": "Histórias de Usuários & Atividades", + "TEXT": "Histórias de Usuários são os requisitos de alto nível. Você pode arrastá-los para diferentes colunas." }, "STEP3": { - "TITLE": "Adicionando User Stories", - "TEXT1": "Você poderá adicionar uma única Estória do Usuário (Ícone add US) ou um grupo delas (ícone de add em massa)", + "TITLE": "Adicionando Histórias de Usuários", + "TEXT1": "Você poderá adicionar uma única História do Usuário (Ícone add US) ou um grupo delas (ícone de add em massa)", "TEXT2": "Boa Sorte!" } } }, "DISCOVER": { - "PAGE_TITLE": "Discover projects - Taiga", - "PAGE_DESCRIPTION": "Searchable directory of Public Projects in Taiga. Explore backlogs, timelines, issues, and teams. Check out the most liked or most active projects. Filter by Kanban or Scrum.", + "PAGE_TITLE": "Descubra projetos - Taiga", + "PAGE_DESCRIPTION": "Diretório pesquisável de projetos públicos no Taiga. Explore backlogs, linhas do tempo, problemas e times. Encontre os projetos mais curtidos ou mais ativos. Filtre por Kanban ou Scrum.", "DISCOVER_TITLE": "Descobrir projetos", - "DISCOVER_SUBTITLE": "{projects, plural, um{One public project to discover} outro{# public projects to discover}}", + "DISCOVER_SUBTITLE": "{projects, plural, one{One public project to discover} other{# public projects to discover}}", "MOST_ACTIVE": "Mais ativo", "MOST_ACTIVE_EMPTY": "Não tem projetos ativos ainda", "MOST_LIKED": "Mais curtidas", @@ -1567,7 +1567,7 @@ }, "SEARCH": { "PAGE_TITLE": "Procurar - Descobrir projetos - Taiga", - "PAGE_DESCRIPTION": "Searchable directory of Public Projects in Taiga. Explore backlogs, timelines, issues, and teams. Check out the most liked or most active projects. Filter by Kanban or Scrum.", + "PAGE_DESCRIPTION": "Diretório pesquisável de projetos públicos no Taiga. Explore backlogs, linhas do tempo, problemas e times. Encontre os projetos mais curtidos ou mais ativos. Filtre por Kanban ou Scrum.", "INPUT_PLACEHOLDER": "Digite algo...", "ACTION_TITLE": "Procurar", "RESULTS": "Resultado de pesquisa." diff --git a/app/locales/taiga/locale-sv.json b/app/locales/taiga/locale-sv.json index d6f136e8..71624c10 100644 --- a/app/locales/taiga/locale-sv.json +++ b/app/locales/taiga/locale-sv.json @@ -173,7 +173,7 @@ "UNWATCH": "Frånkoppla visning", "WATCHERS": "Observatörer", "BUTTON_TITLE": "Visa/Visa inte den här posten", - "COUNTER_TITLE": "{total, plural, en{one watcher} andra{# watchers}}" + "COUNTER_TITLE": "{total, plural, one{one watcher} other{# watchers}}" }, "VOTE_BUTTON": { "UPVOTE": "Rösta för", @@ -765,8 +765,8 @@ "HELP": "Organisera dina projekt och sätt in de mest använda här.
De första 10 toppprojekten vill visas i toppnavigeringens projektlista. ", "PRIVATE": "Privata projekt", "LOOKING_FOR_PEOPLE": "This project is looking for people", - "FANS_COUNTER_TITLE": "{total, plural, one{one fan} andra{# fans}}", - "WATCHERS_COUNTER_TITLE": "{total, plural, en{one watcher} andra{# watchers}}", + "FANS_COUNTER_TITLE": "{total, plural, one{one fan} other{# fans}}", + "WATCHERS_COUNTER_TITLE": "{total, plural, en{one watcher} other{# watchers}}", "MEMBERS_COUNTER_TITLE": "{total, plural, one{one member} other{# members}}", "BLOCKED_PROJECT": { "BLOCKED": "Blocked project", @@ -866,13 +866,13 @@ "LIKED": "Likte", "UNLIKE": "Ogillar", "BUTTON_TITLE": "Gilla eller ogilla projektet", - "COUNTER_TITLE": "{total, plural, one{one fan} andra{# fans}}" + "COUNTER_TITLE": "{total, plural, one{one fan} other{# fans}}" }, "WATCH_BUTTON": { "BUTTON_TITLE": "Visa projektet och lägg till egenskaper för avisering", "WATCH": "Visa", "WATCHING": "Bevakar", - "COUNTER_TITLE": "{total, plural, en{one watcher} andra{# watchers}}", + "COUNTER_TITLE": "{total, plural, en{one watcher} other{# watchers}}", "OPTIONS": { "NOTIFY_ALL": "Motta alla notifieringar", "NOTIFY_ALL_TITLE": "Motta alla notifieringar för det här projektet", @@ -1018,7 +1018,7 @@ "DELETED_ATTACHMENT": "ta bort bifogad fil", "UPDATED_ATTACHMENT": "uppdaterad bilaga {{filename}}", "DELETED_CUSTOM_ATTRIBUTE": "raderad anpassad atribut", - "SIZE_CHANGE": "Gjorde {size, plural, one{one change} annan{# changes}}", + "SIZE_CHANGE": "Gjorde {size, plural, one{one change} other{# changes}}", "VALUES": { "YES": "ja", "NO": "nej", From 6e9f27a3dc30f1a652995e2bf13f957729a0dc8a Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 4 May 2016 00:07:51 +0200 Subject: [PATCH 006/315] reload backlog with taiga-events fix --- app/coffee/modules/backlog/main.coffee | 12 +++++++++--- app/coffee/modules/resources/userstories.coffee | 7 +++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index 0b6765ef..b5a626b8 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -142,7 +142,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F initializeSubscription: -> routingKey1 = "changes.project.#{@scope.projectId}.userstories" @events.subscribe @scope, routingKey1, (message) => - @.loadUserstories() + @.loadAllPaginatedUserstories() @.loadSprints() routingKey2 = "changes.project.#{@scope.projectId}.milestones" @@ -257,7 +257,13 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F @.loadUserstories() - loadUserstories: (resetPagination = false)-> + loadAllPaginatedUserstories: () -> + page = @.page + + @.loadUserstories(true, @scope.userstories.length).then () => + @.page = page + + loadUserstories: (resetPagination = false, pageSize) -> @.loadingUserstories = true @.disablePagination = true @scope.httpParams = @.getUrlFilters() @@ -268,7 +274,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F @scope.httpParams.page = @.page - promise = @rs.userstories.listUnassigned(@scope.projectId, @scope.httpParams) + promise = @rs.userstories.listUnassigned(@scope.projectId, @scope.httpParams, pageSize) return promise.then (result) => userstories = result[0] diff --git a/app/coffee/modules/resources/userstories.coffee b/app/coffee/modules/resources/userstories.coffee index 5e98ca72..935f6d3a 100644 --- a/app/coffee/modules/resources/userstories.coffee +++ b/app/coffee/modules/resources/userstories.coffee @@ -47,11 +47,14 @@ resourceProvider = ($repo, $http, $urls, $storage) -> service.filtersData = (params) -> return $repo.queryOneRaw("userstories-filters", null, params) - service.listUnassigned = (projectId, filters) -> + service.listUnassigned = (projectId, filters, pageSize) -> params = {"project": projectId, "milestone": "null"} params = _.extend({}, params, filters or {}) service.storeQueryParams(projectId, params) - return $repo.queryMany("userstories", params, { + + return $repo.queryMany("userstories", _.extend(params, { + page_size: pageSize + }), { enablePagination: true }, true) From f58dc55bbcf665e927b990878e523502cc666dd1 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 4 May 2016 21:24:20 +0200 Subject: [PATCH 007/315] prevent loading backlog without projectId --- app/coffee/modules/backlog/main.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index b5a626b8..a9ceefeb 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -63,7 +63,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F bindMethods(@) @.page = 1 - @.disablePagination = false + @.disablePagination = true @scope.userstories = [] @scope.sectionName = @translate.instant("BACKLOG.SECTION_NAME") @@ -77,6 +77,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F # On Success promise.then => + @.disablePagination = false + title = @translate.instant("BACKLOG.PAGE_TITLE", {projectName: @scope.project.name}) description = @translate.instant("BACKLOG.PAGE_DESCRIPTION", { projectName: @scope.project.name, @@ -264,6 +266,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F @.page = page loadUserstories: (resetPagination = false, pageSize) -> + return null if !@scope.projectId + @.loadingUserstories = true @.disablePagination = true @scope.httpParams = @.getUrlFilters() From abf48bd879f4dfae3c52c261d050e1e18b4a72ad Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 4 May 2016 21:51:01 +0200 Subject: [PATCH 008/315] prevent pagination during the loadInitialData --- app/coffee/modules/backlog/main.coffee | 7 ++++--- app/partials/includes/modules/backlog-table.jade | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index a9ceefeb..987cffd1 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -63,7 +63,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F bindMethods(@) @.page = 1 - @.disablePagination = true + @.disablePagination = false + @.firstLoadComplete = false @scope.userstories = [] @scope.sectionName = @translate.instant("BACKLOG.SECTION_NAME") @@ -77,7 +78,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F # On Success promise.then => - @.disablePagination = false + @.firstLoadComplete = true title = @translate.instant("BACKLOG.PAGE_TITLE", {projectName: @scope.project.name}) description = @translate.instant("BACKLOG.PAGE_DESCRIPTION", { @@ -267,7 +268,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F loadUserstories: (resetPagination = false, pageSize) -> return null if !@scope.projectId - + @.loadingUserstories = true @.disablePagination = true @scope.httpParams = @.getUrlFilters() diff --git a/app/partials/includes/modules/backlog-table.jade b/app/partials/includes/modules/backlog-table.jade index 63c7268c..8ee5707b 100644 --- a/app/partials/includes/modules/backlog-table.jade +++ b/app/partials/includes/modules/backlog-table.jade @@ -12,7 +12,7 @@ div.backlog-table-body( tg-backlog-sortable, ng-class="{'show-tags': ctrl.showTags, 'active-filters': ctrl.activeFilters}" infinite-scroll="ctrl.loadUserstories()" - infinite-scroll-disabled="ctrl.disablePagination" + infinite-scroll-disabled="ctrl.disablePagination || !ctrl.firstLoadComplete" infinite-scroll-immediate-check='false' ) include ../components/backlog-row From 8c56c1ffd5bd32fd3c1b4abea0f4132064f7e135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 5 May 2016 12:40:13 +0200 Subject: [PATCH 009/315] Minor Style changes --- app/styles/components/editor-help.scss | 2 +- app/styles/components/filter.scss | 1 - app/styles/components/wysiwyg.scss | 2 +- app/styles/layout/ticket-detail.scss | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/styles/components/editor-help.scss b/app/styles/components/editor-help.scss index 304e9a00..5336a078 100644 --- a/app/styles/components/editor-help.scss +++ b/app/styles/components/editor-help.scss @@ -10,7 +10,7 @@ } .drag-drop-help { - @include font-size(x-small); + @include font-size(xsmall); color: $gray; } diff --git a/app/styles/components/filter.scss b/app/styles/components/filter.scss index acf758dc..c681c70d 100644 --- a/app/styles/components/filter.scss +++ b/app/styles/components/filter.scss @@ -1,5 +1,4 @@ .single-filter { - @include font-size(large); @include font-type(text); @include clearfix; align-items: center; diff --git a/app/styles/components/wysiwyg.scss b/app/styles/components/wysiwyg.scss index e93f5277..ad26118e 100644 --- a/app/styles/components/wysiwyg.scss +++ b/app/styles/components/wysiwyg.scss @@ -63,7 +63,7 @@ margin-bottom: 1rem; overflow: auto; unicode-bidi: embed; - white-space: pre; + white-space: pre-wrap; } pre { line-height: 1.4rem; diff --git a/app/styles/layout/ticket-detail.scss b/app/styles/layout/ticket-detail.scss index 0d0f4f70..93017208 100644 --- a/app/styles/layout/ticket-detail.scss +++ b/app/styles/layout/ticket-detail.scss @@ -193,6 +193,7 @@ flex-basis: 250px; flex-shrink: 0; @include breakpoint(laptop) { + flex-basis: auto; order: 1; } } From e3791d9b8535f572407e0e0291383c23c7fc354c Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 5 May 2016 08:18:01 +0200 Subject: [PATCH 010/315] confirm close edit mode with ESC --- CHANGELOG.md | 9 ++++++++ app/coffee/modules/common/components.coffee | 25 +++++++++++++++------ app/coffee/modules/common/confirm.coffee | 6 ++--- app/coffee/modules/wiki/main.coffee | 16 ++++++++----- app/locales/taiga/locale-ca.json | 2 ++ app/locales/taiga/locale-de.json | 2 ++ app/locales/taiga/locale-en.json | 2 ++ app/locales/taiga/locale-es.json | 2 ++ app/locales/taiga/locale-fi.json | 2 ++ app/locales/taiga/locale-fr.json | 2 ++ app/locales/taiga/locale-it.json | 2 ++ app/locales/taiga/locale-nl.json | 2 ++ app/locales/taiga/locale-pl.json | 2 ++ app/locales/taiga/locale-pt-br.json | 2 ++ app/locales/taiga/locale-ru.json | 2 ++ app/locales/taiga/locale-sv.json | 2 ++ app/locales/taiga/locale-tr.json | 2 ++ app/locales/taiga/locale-zh-hant.json | 2 ++ 18 files changed, 68 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36b6173e..ad2e8f5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Changelog # +## 2.2.0 ???? (Unreleased) + +### Features +- Show a confirmation notice when you exit edit mode by pressing ESC in the markdown inputs. + +### Misc +- Lots of small and not so small bugfixes. + + ## 2.1.0 Ursus Americanus (2016-05-03) ### Features diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index 325c2524..f6598fa8 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -529,7 +529,7 @@ module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$ ## Editable description directive ############################################################################# -EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, $selectedText, $qqueue, $template) -> +EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, $selectedText, $qqueue, $template, $translate) -> template = $template.get("common/components/editable-description.html") noDescriptionMegEditMode = $template.get("common/components/editable-description-msg-edit-mode.html") noDescriptionMegReadMode = $template.get("common/components/editable-description-msg-read-mode.html") @@ -563,6 +563,11 @@ EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, promise.finally -> currentLoading.finish() + cancelEdition = () -> + $scope.item.revert() + $el.find('.edit-description').hide() + $el.find('.view-description').show() + $el.on "mouseup", ".view-description", (event) -> # We want to dettect the a inside the div so we use the target and # not the currentTarget @@ -589,15 +594,19 @@ EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, save(description) $el.on "keydown", "textarea", (event) -> - if event.keyCode == 27 - $scope.$apply () => $scope.item.revert() - $el.find('.edit-description').hide() - $el.find('.view-description').show() + return if event.keyCode != 27 + + $scope.$applyAsync () -> + title = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_TITLE") + message = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_MESSAGE") + $confirm.ask(title, null, message).then (askResponse) -> + cancelEdition() + askResponse.finish() $scope.$watch $attrs.ngModel, (value) -> return if not value - $scope.item = value + $scope.item = value if isEditable() $el.find('.view-description .edit').show() $el.find('.view-description .us-content').addClass('editable') @@ -623,7 +632,9 @@ module.directive("tgEditableDescription", [ "$tgLoading", "$selectedText", "$tgQqueue", - "$tgTemplate", EditableDescriptionDirective]) + "$tgTemplate", + "$translate", + EditableDescriptionDirective]) diff --git a/app/coffee/modules/common/confirm.coffee b/app/coffee/modules/common/confirm.coffee index 4d55b4e8..04e80a62 100644 --- a/app/coffee/modules/common/confirm.coffee +++ b/app/coffee/modules/common/confirm.coffee @@ -59,9 +59,9 @@ class ConfirmService extends taiga.Service el = angular.element(lightboxSelector) # Render content - el.find(".title").text(title) - el.find(".subtitle").text(subtitle) - el.find(".message").text(message) + el.find(".title").text(title) if title + el.find(".subtitle").text(subtitle) if subtitle + el.find(".message").text(message) if message # Assign event handlers el.on "click.confirm-dialog", ".button-green", debounce 2000, (event) => diff --git a/app/coffee/modules/wiki/main.coffee b/app/coffee/modules/wiki/main.coffee index 3713d232..de4b3a0c 100644 --- a/app/coffee/modules/wiki/main.coffee +++ b/app/coffee/modules/wiki/main.coffee @@ -206,7 +206,7 @@ module.directive("tgWikiSummary", ["$log", "$tgTemplate", "$compile", "$translat ## Editable Wiki Content Directive ############################################################################# -EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $analytics, $qqueue) -> +EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $analytics, $qqueue, $translate) -> link = ($scope, $el, $attrs, $model) -> isEditable = -> return $scope.project.my_permissions.indexOf("modify_wiki_page") != -1 @@ -227,8 +227,8 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $ cancelEdition = -> return if not $model.$modelValue.id - $scope.$apply () => - $model.$modelValue.revert() + $model.$modelValue.revert() + switchToReadMode() getSelectedText = -> @@ -290,11 +290,15 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $ save($scope.wiki) $el.on "click", ".cancel", -> - cancelEdition() + $scope.$apply(cancelEdition) $el.on "keydown", "textarea", (event) -> if event.keyCode == 27 - cancelEdition() + $scope.$applyAsync () -> + confirmTitle = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE") + $confirm.ask(confirmTitle).then (askResponse) -> + cancelEdition() + askResponse.finish() $scope.$watch $attrs.ngModel, (wikiPage) -> return if not wikiPage @@ -317,4 +321,4 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $ } module.directive("tgEditableWikiContent", ["$window", "$document", "$tgRepo", "$tgConfirm", "$tgLoading", - "$tgAnalytics", "$tgQqueue", EditableWikiContentDirective]) + "$tgAnalytics", "$tgQqueue", "$translate", EditableWikiContentDirective]) diff --git a/app/locales/taiga/locale-ca.json b/app/locales/taiga/locale-ca.json index ddfea7e1..5645a9b8 100644 --- a/app/locales/taiga/locale-ca.json +++ b/app/locales/taiga/locale-ca.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", "OWNER": "Project Owner", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Aquest valor pareix invàlid.", "TYPE_EMAIL": "Deu ser un correu vàlid.", diff --git a/app/locales/taiga/locale-de.json b/app/locales/taiga/locale-de.json index 3f22beae..a4e87b4b 100644 --- a/app/locales/taiga/locale-de.json +++ b/app/locales/taiga/locale-de.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", "OWNER": "Projekteigentümer", "CAPSLOCK_WARNING": "Achtung! Sie verwenden Großbuchstaben in einem Eingabefeld, dass Groß- und Kleinschreibung berücksichtigt.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Dieser Wert scheint ungültig zu sein.", "TYPE_EMAIL": "Dieser Wert sollte eine gültige E-Mail Adresse enthalten.", diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 194cf93e..c727005f 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", "OWNER": "Project Owner", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "This value seems to be invalid.", "TYPE_EMAIL": "This value should be a valid email.", diff --git a/app/locales/taiga/locale-es.json b/app/locales/taiga/locale-es.json index c18c59f7..08b5a14a 100644 --- a/app/locales/taiga/locale-es.json +++ b/app/locales/taiga/locale-es.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Requerimiento del equipo es un nuevo requisito que debe existir en el proyecto pero que no conllevará ningún coste para el cliente.", "OWNER": "Project Owner", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Este valor parece inválido.", "TYPE_EMAIL": "El valor debe ser un email.", diff --git a/app/locales/taiga/locale-fi.json b/app/locales/taiga/locale-fi.json index bf1968ec..319aa4e4 100644 --- a/app/locales/taiga/locale-fi.json +++ b/app/locales/taiga/locale-fi.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", "OWNER": "Project Owner", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Tämä arvo vaikuttaa virheelliseltä.", "TYPE_EMAIL": "Tämän pitäisi olla toimiva sähköpostiosoite.", diff --git a/app/locales/taiga/locale-fr.json b/app/locales/taiga/locale-fr.json index 6fcb67b6..114da4b1 100644 --- a/app/locales/taiga/locale-fr.json +++ b/app/locales/taiga/locale-fr.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Un besoin projet est un besoin qui est nécessaire au projet mais qui ne doit avoir aucun impact pour le client", "OWNER": "Propriétaire du Projet", "CAPSLOCK_WARNING": "Attention ! Vous utilisez des majuscules dans un champ qui est sensible à la casse.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Cette valeur semble être invalide.", "TYPE_EMAIL": "Cette valeur devrait être une adresse courriel valide.", diff --git a/app/locales/taiga/locale-it.json b/app/locales/taiga/locale-it.json index 66036eed..11b45bd6 100644 --- a/app/locales/taiga/locale-it.json +++ b/app/locales/taiga/locale-it.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Requisito del Team è un requisito che deve esistere nel progetto ma non deve avere costi per il cliente", "OWNER": "Project Owner", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Questo valore non è valido.", "TYPE_EMAIL": "Questo valore dovrebbe corrispondere ad una mail valida", diff --git a/app/locales/taiga/locale-nl.json b/app/locales/taiga/locale-nl.json index bb90d8c7..750107c5 100644 --- a/app/locales/taiga/locale-nl.json +++ b/app/locales/taiga/locale-nl.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", "OWNER": "Project Owner", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Deze waarde lijkt ongeldig te zijn", "TYPE_EMAIL": "Deze waarde moet een geldig emailadres bevatten", diff --git a/app/locales/taiga/locale-pl.json b/app/locales/taiga/locale-pl.json index aa4db039..377fc4db 100644 --- a/app/locales/taiga/locale-pl.json +++ b/app/locales/taiga/locale-pl.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", "OWNER": "Project Owner", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Nieprawidłowa wartość", "TYPE_EMAIL": "Podaj prawidłowy adres email.", diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index d14cea26..b888bba8 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Requisito de time é um requisito que deve existir no projeto, mas que não deve ter nenhum custo para o cliente.", "OWNER": "Dono do Projeto", "CAPSLOCK_WARNING": "Seja cuidadoso! Você está escrevendo em letras maiúsculas e esse campo é case sensitive, ou seja, trata com distinção as letras maiúsculas das minúsculas.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Este valor parece ser inválido.", "TYPE_EMAIL": "Este valor deve ser um e-mail válido.", diff --git a/app/locales/taiga/locale-ru.json b/app/locales/taiga/locale-ru.json index 54988c7d..89e9c995 100644 --- a/app/locales/taiga/locale-ru.json +++ b/app/locales/taiga/locale-ru.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", "OWNER": "Project Owner", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Кажется, это значение некорректно.", "TYPE_EMAIL": "Это значение должно быть корректным email-адресом.", diff --git a/app/locales/taiga/locale-sv.json b/app/locales/taiga/locale-sv.json index 71624c10..1d63b9da 100644 --- a/app/locales/taiga/locale-sv.json +++ b/app/locales/taiga/locale-sv.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", "OWNER": "Project Owner", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Det här värdet är felaktigt. ", "TYPE_EMAIL": "Värdet måste vara en giltig e-postadress", diff --git a/app/locales/taiga/locale-tr.json b/app/locales/taiga/locale-tr.json index e0c2b4f0..97ce06a5 100644 --- a/app/locales/taiga/locale-tr.json +++ b/app/locales/taiga/locale-tr.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Takım gereksinimi, müsteriyi ilgilendirmeyen, ama projede yer alması zorunlu olan bir gereksinimdir.", "OWNER": "Project Owner", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Bu değer geçersiz gözüküyor", "TYPE_EMAIL": "Bu değer geçerli bir e-posta adresi olmalı.", diff --git a/app/locales/taiga/locale-zh-hant.json b/app/locales/taiga/locale-zh-hant.json index e992257e..2abcfee7 100644 --- a/app/locales/taiga/locale-zh-hant.json +++ b/app/locales/taiga/locale-zh-hant.json @@ -43,6 +43,8 @@ "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", "OWNER": "Project Owner", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "該數值似乎為無效", "TYPE_EMAIL": "該電子郵件應為有效地址", From 65088f7b547059549b0886ca30d4f3ab38a27c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 5 May 2016 17:02:04 +0200 Subject: [PATCH 011/315] Fix issue #4164: See the owner badget in the Team panel --- app/partials/team/team-members.jade | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/partials/team/team-members.jade b/app/partials/team/team-members.jade index 795388bb..e8c9ad83 100644 --- a/app/partials/team/team-members.jade +++ b/app/partials/team/team-members.jade @@ -7,9 +7,13 @@ a.name( tg-nav="user-profile:username=user.username", title="{{::user.full_name_display}}" - ) {{::user.full_name_display}} - svg.icon.icon-badge(ng-if="user.id == owner") - tg-svg(svg-icon="icon-badge", svg-title-translate="COMMON.OWNER") + ) + {{::user.full_name_display}} + tg-svg( + ng-if="user.id == owner" + svg-icon="icon-badge" + svg-title-translate="COMMON.OWNER" + ) span.position {{::user.role_name}} From bc453a9f7207d65db92604f62444aadcf0e186cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 5 May 2016 17:14:16 +0200 Subject: [PATCH 012/315] Fix a warning message --- app/partials/team/team-members.jade | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/partials/team/team-members.jade b/app/partials/team/team-members.jade index e8c9ad83..2155d134 100644 --- a/app/partials/team/team-members.jade +++ b/app/partials/team/team-members.jade @@ -7,8 +7,7 @@ a.name( tg-nav="user-profile:username=user.username", title="{{::user.full_name_display}}" - ) - {{::user.full_name_display}} + ) {{::user.full_name_display}} tg-svg( ng-if="user.id == owner" svg-icon="icon-badge" From d9333661c9b4c2727628807c2b9b5bfb2a0e1ed2 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 6 May 2016 10:56:43 +0200 Subject: [PATCH 013/315] =?UTF-8?q?Issue=204165:=20I=C3=B1igo=20Montoya=20?= =?UTF-8?q?can't=20view=20their=20work=20in=20progress=20and=20their=20wat?= =?UTF-8?q?ched=20items=20in=20his=20dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/modules/home/home.service.coffee | 45 +++++++++++----------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee index 47aa069f..fe068447 100644 --- a/app/modules/home/home.service.coffee +++ b/app/modules/home/home.service.coffee @@ -46,53 +46,44 @@ class HomeService extends taiga.Service return duty + _getValidDutiesAndAttachProjectInfo = (duties, dutyType)-> + # Exclude duties where I'm not member of the project + duties = duties.filter((duty) -> + return projectsById.get(String(duty.get('project')))) + + duties = duties.map (duty) -> + return _attachProjectInfoToDuty(duty, dutyType) + + return duties + assignedTo = workInProgress.get("assignedTo") if assignedTo.get("userStories") - _duties = assignedTo.get("userStories").map (duty) -> - return _attachProjectInfoToDuty(duty, "userstories") - + _duties = _getValidDutiesAndAttachProjectInfo(assignedTo.get("userStories"), "userstories") assignedTo = assignedTo.set("userStories", _duties) if assignedTo.get("tasks") - _duties = assignedTo.get("tasks").map (duty) -> - return _attachProjectInfoToDuty(duty, "tasks") - + _duties = _getValidDutiesAndAttachProjectInfo(assignedTo.get("tasks"), "tasks") assignedTo = assignedTo.set("tasks", _duties) - if assignedTo.get("issues") - _duties = assignedTo.get("issues").map (duty) -> - return _attachProjectInfoToDuty(duty, "issues") + if assignedTo.get("issues") + _duties = _getValidDutiesAndAttachProjectInfo(assignedTo.get("issues"), "issues") assignedTo = assignedTo.set("issues", _duties) + watching = workInProgress.get("watching") if watching.get("userStories") - _duties = watching.get("userStories").filter (duty) -> - return !!projectsById.get(String(duty.get('project'))) - - _duties = _duties.map (duty) -> - return _attachProjectInfoToDuty(duty, "userstories") - + _duties = _getValidDutiesAndAttachProjectInfo(watching.get("userStories"), "userstories") watching = watching.set("userStories", _duties) if watching.get("tasks") - _duties = watching.get("tasks").filter (duty) -> - return !!projectsById.get(String(duty.get('project'))) - - _duties = _duties.map (duty) -> - return _attachProjectInfoToDuty(duty, "tasks") - + _duties = _getValidDutiesAndAttachProjectInfo(watching.get("tasks"), "tasks") watching = watching.set("tasks", _duties) if watching.get("issues") - _duties = watching.get("issues").filter (duty) -> - return !!projectsById.get(String(duty.get('project'))) - - _duties = _duties.map (duty) -> - return _attachProjectInfoToDuty(duty, "issues") - + _duties = _getValidDutiesAndAttachProjectInfo(watching.get("issues"), "issues") watching = watching.set("issues", _duties) workInProgress = workInProgress.set("assignedTo", assignedTo) From 8dcef424cea0465cad35cee9a701274363a4d3a2 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 9 May 2016 13:42:09 +0200 Subject: [PATCH 014/315] json e2e reports --- .gitignore | 1 + conf.e2e.js | 24 +++++++++++++++++++++--- package.json | 3 ++- run-e2e.js | 8 +++++++- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index ec0af232..e2d57d5c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ tmp/ app/config/main.coffee scss-lint.log e2e/screenshots/ +e2e/reports/ app/modules/compile-modules/ diff --git a/conf.e2e.js b/conf.e2e.js index c3151a09..d61df045 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -2,8 +2,9 @@ require("babel-register"); require("babel-polyfill"); var utils = require('./e2e/utils'); +var argv = require('minimist')(process.argv.slice(2)); -exports.config = { +var config = { seleniumAddress: 'http://localhost:4444/wd/hub', framework: 'mocha', params: { @@ -101,8 +102,6 @@ exports.config = { // }; // browser.addMockModule('trackMouse', trackMouse); - var argv = require('minimist')(process.argv.slice(2)); - browser.params.glob.back = argv.back; require('./e2e/capabilities.js'); @@ -140,4 +139,23 @@ exports.config = { return browser.get(browser.params.glob.host); }); } +}; + + +if (argv.json) { + var fs = require('fs'); + var dir = './e2e/reports'; + + if (!fs.existsSync(dir)){ + fs.mkdirSync(dir); + } + + var suites = argv.suite.split(',').join('-'); + + process.env['MOCHA_REPORTER'] = 'JSON'; + process.env['MOCHA_REPORTER_FILE'] = 'e2e/reports/report-' + suites +'.json'; + + config.mochaOpts.reporter = 'reporter-file'; } + +exports.config = config; diff --git a/package.json b/package.json index f4350f8e..b904bb30 100644 --- a/package.json +++ b/package.json @@ -84,11 +84,12 @@ "merge-stream": "^1.0.0", "minimist": "^1.1.1", "mocha": "^2.2.4", - "node-uuid": "^1.4.3", "node-sass": "3.6.0", + "node-uuid": "^1.4.3", "photoswipe": "^4.1.0", "pre-commit": "^1.0.5", "readable-stream": "~2.1.2", + "reporter-file": "^1.0.0", "run-sequence": "^1.0.2", "sinon": "^1.14.1", "through2": "^2.0.1", diff --git a/run-e2e.js b/run-e2e.js index ffb1b9c8..f50e6a5f 100644 --- a/run-e2e.js +++ b/run-e2e.js @@ -35,7 +35,13 @@ function backup() { } function launchProtractor(suit) { - child_process.spawnSync('protractor', ['conf.e2e.js', '--suite=' + suit, '--back=' + taigaBackPath], {stdio: "inherit"}); + let protractorParams = ['conf.e2e.js', '--suite=' + suit, '--back=' + taigaBackPath]; + + if (argv.json) { + protractorParams.push('--json'); + } + + child_process.spawnSync('protractor', protractorParams, {stdio: "inherit"}); } function restoreBackup() { From 17f78ce2cbf98d8814f3094ec278a0449c4d2715 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 10 May 2016 09:38:09 +0200 Subject: [PATCH 015/315] add loading in the wiki save --- app/coffee/modules/wiki/main.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/coffee/modules/wiki/main.coffee b/app/coffee/modules/wiki/main.coffee index de4b3a0c..db1c25fa 100644 --- a/app/coffee/modules/wiki/main.coffee +++ b/app/coffee/modules/wiki/main.coffee @@ -252,8 +252,7 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $ $confirm.notify("error") currentLoading = $loading() - .removeClasses("icon-floppy") - .target($el.find('.icon-floppy')) + .target($el.find('.save')) .start() if wiki.id? From c20ff9d368315e2aadae1815fc9f0680c97a68af Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 10 May 2016 14:55:35 +0200 Subject: [PATCH 016/315] test e2e confirmation close wysiwyg --- e2e/helpers/detail-helper.js | 4 +- e2e/helpers/wiki-helper.js | 11 +++- e2e/shared/detail.js | 51 ++++++++++++++----- e2e/suites/issues/issue-detail.e2e.js | 2 +- e2e/suites/tasks/task-detail.e2e.js | 2 +- .../user-stories/user-story-detail.e2e.js | 2 +- e2e/suites/wiki.e2e.js | 23 +++++++++ e2e/utils/lightbox.js | 10 ++++ 8 files changed, 87 insertions(+), 18 deletions(-) diff --git a/e2e/helpers/detail-helper.js b/e2e/helpers/detail-helper.js index b97358ca..ebff2cf8 100644 --- a/e2e/helpers/detail-helper.js +++ b/e2e/helpers/detail-helper.js @@ -29,7 +29,9 @@ helper.description = function(){ let obj = { el: el, - + focus: function() { + el.$('textarea').click(); + }, enabledEditionMode: async function(){ await el.$(".view-description").click(); }, diff --git a/e2e/helpers/wiki-helper.js b/e2e/helpers/wiki-helper.js index f883e63d..214bf9b6 100644 --- a/e2e/helpers/wiki-helper.js +++ b/e2e/helpers/wiki-helper.js @@ -38,6 +38,10 @@ helper.editor = function(){ let obj = { el: el, + focus: function() { + el.$("textarea").click(); + }, + enabledEditionMode: async function(){ await el.$("section[tg-editable-wiki-content] .view-wiki-content").click(); }, @@ -58,7 +62,7 @@ helper.editor = function(){ }, getInnerHtml: async function(text){ - let wikiText = await el.$(".content").getInnerHtml(); + let wikiText = await el.$(".view-wiki-content .wysiwyg").getInnerHtml(); return wikiText; }, @@ -75,7 +79,10 @@ helper.editor = function(){ await el.$(".preview-icon a").click(); await browser.waitForAngular(); }, - + closePreview: async function(){ + await el.$(".actions .wysiwyg").click(); + await browser.waitForAngular(); + }, save: async function(){ await el.$(".save").click(); await browser.waitForAngular(); diff --git a/e2e/shared/detail.js b/e2e/shared/detail.js index f1e95ae8..fc59b856 100644 --- a/e2e/shared/detail.js +++ b/e2e/shared/detail.js @@ -3,6 +3,7 @@ var detailHelper = require('../helpers').detail; var commonHelper = require('../helpers').common; var customFieldsHelper = require('../helpers/custom-fields-helper'); var commonUtil = require('../utils/common'); +var lightbox = require('../utils/lightbox'); var notifications = require('../utils/notifications'); var chai = require('chai'); @@ -46,21 +47,47 @@ shared.tagsTesting = async function() { expect(newtagsText).to.be.not.eql(tagsText); } -shared.descriptionTesting = async function() { - let descriptionHelper = detailHelper.description(); - let description = await descriptionHelper.getInnerHtml(); - let date = Date.now(); - descriptionHelper.enabledEditionMode(); - descriptionHelper.setText("New description " + date); - descriptionHelper.save(); +shared.descriptionTesting = function() { + it('confirm close with ESC', async function() { + let descriptionHelper = detailHelper.description(); - let newDescription = await descriptionHelper.getInnerHtml(); - let notificationOpen = await notifications.success.open(); + descriptionHelper.enabledEditionMode(); - expect(notificationOpen).to.be.equal.true; - expect(newDescription).to.be.not.equal(description); + browser.actions().sendKeys(protractor.Key.ESCAPE).perform(); - await notifications.success.close(); + await lightbox.confirm.cancel(); + + let descriptionVisibility = await $('.edit-description').isDisplayed(); + + expect(descriptionVisibility).to.be.true; + + descriptionHelper.focus(); + + browser.actions().sendKeys(protractor.Key.ESCAPE).perform(); + + await lightbox.confirm.ok(); + + descriptionVisibility = await $('.edit-description').isDisplayed(); + + expect(descriptionVisibility).to.be.false; + }); + + it('edit', async function() { + let descriptionHelper = detailHelper.description(); + let description = await descriptionHelper.getInnerHtml(); + let date = Date.now(); + descriptionHelper.enabledEditionMode(); + descriptionHelper.setText("New description " + date); + descriptionHelper.save(); + + let newDescription = await descriptionHelper.getInnerHtml(); + let notificationOpen = await notifications.success.open(); + + expect(notificationOpen).to.be.equal.true; + expect(newDescription).to.be.not.equal(description); + + await notifications.success.close(); + }); } shared.statusTesting = async function(status1 , status2) { diff --git a/e2e/suites/issues/issue-detail.e2e.js b/e2e/suites/issues/issue-detail.e2e.js index b713dbaa..af466e1c 100644 --- a/e2e/suites/issues/issue-detail.e2e.js +++ b/e2e/suites/issues/issue-detail.e2e.js @@ -29,7 +29,7 @@ describe('Issue detail', async function(){ it('tags edition', sharedDetail.tagsTesting); - it('description edition', sharedDetail.descriptionTesting); + describe('description', sharedDetail.descriptionTesting); it('status edition', sharedDetail.statusTesting.bind(this, 'In progress', 'Ready for test')); diff --git a/e2e/suites/tasks/task-detail.e2e.js b/e2e/suites/tasks/task-detail.e2e.js index 850cd3e1..74fab211 100644 --- a/e2e/suites/tasks/task-detail.e2e.js +++ b/e2e/suites/tasks/task-detail.e2e.js @@ -31,7 +31,7 @@ describe('Task detail', function(){ it('tags edition', sharedDetail.tagsTesting); - it('description edition', sharedDetail.descriptionTesting); + describe('description', sharedDetail.descriptionTesting); it('status edition', sharedDetail.statusTesting.bind(this, 'In progress', 'Ready for test')); diff --git a/e2e/suites/user-stories/user-story-detail.e2e.js b/e2e/suites/user-stories/user-story-detail.e2e.js index e63d3294..e3e21339 100644 --- a/e2e/suites/user-stories/user-story-detail.e2e.js +++ b/e2e/suites/user-stories/user-story-detail.e2e.js @@ -30,7 +30,7 @@ describe('User story detail', function(){ it('tags edition', sharedDetail.tagsTesting); - it('description edition', sharedDetail.descriptionTesting); + describe('description', sharedDetail.descriptionTesting); it('status edition', sharedDetail.statusTesting.bind(this, 'Ready', 'In progress')); diff --git a/e2e/suites/wiki.e2e.js b/e2e/suites/wiki.e2e.js index f0c37d72..0429e0be 100644 --- a/e2e/suites/wiki.e2e.js +++ b/e2e/suites/wiki.e2e.js @@ -60,6 +60,7 @@ describe('wiki', function() { //preview wikiHelper.editor().preview(); await utils.common.takeScreenshot("wiki", "home-edition-preview"); + wikiHelper.editor().closePreview(); //save wikiHelper.editor().save(); @@ -74,6 +75,28 @@ describe('wiki', function() { await utils.common.takeScreenshot("wiki", "home-edition"); }); + it('confirm close with ESC in lightbox', async function() { + wikiHelper.editor().enabledEditionMode(); + + browser.actions().sendKeys(protractor.Key.ESCAPE).perform(); + + await utils.lightbox.confirm.cancel(); + + let descriptionVisibility = await $('.view-wiki-content').isDisplayed(); + + expect(descriptionVisibility).to.be.false; + + wikiHelper.editor().focus(); + + browser.actions().sendKeys(protractor.Key.ESCAPE).perform(); + + await utils.lightbox.confirm.ok(); + + descriptionVisibility = await $('.view-wiki-content').isDisplayed(); + + expect(descriptionVisibility).to.be.true; + }); + it('attachments', sharedDetail.attachmentTesting); it('delete', async function() { diff --git a/e2e/utils/lightbox.js b/e2e/utils/lightbox.js index 471bb098..9b8fe6d8 100644 --- a/e2e/utils/lightbox.js +++ b/e2e/utils/lightbox.js @@ -71,3 +71,13 @@ lightbox.confirm.ok = async function() { await lightbox.close(lb); }; + + +lightbox.confirm.cancel = async function() { + let lb = $('.lightbox-generic-ask'); + await lightbox.open(lb); + + lb.$('.button-red').click(); + + await lightbox.close(lb); +}; From bdb4eab44a7bd4c818dce36ce8ce540cfa8cd580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 10 May 2016 09:36:02 +0200 Subject: [PATCH 017/315] Fix confirm on disabled edit mode lighbox in wiki pages --- app/coffee/modules/wiki/main.coffee | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/coffee/modules/wiki/main.coffee b/app/coffee/modules/wiki/main.coffee index db1c25fa..09b4e16d 100644 --- a/app/coffee/modules/wiki/main.coffee +++ b/app/coffee/modules/wiki/main.coffee @@ -292,12 +292,13 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $ $scope.$apply(cancelEdition) $el.on "keydown", "textarea", (event) -> - if event.keyCode == 27 - $scope.$applyAsync () -> - confirmTitle = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE") - $confirm.ask(confirmTitle).then (askResponse) -> - cancelEdition() - askResponse.finish() + return if event.keyCode != 27 + $scope.$applyAsync () -> + title = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_TITLE") + message = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_MESSAGE") + $confirm.ask(title, null, message).then (askResponse) -> + cancelEdition() + askResponse.finish() $scope.$watch $attrs.ngModel, (wikiPage) -> return if not wikiPage From 3466bf8333e70fbf92d3cd794eabc6bff4cb7085 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 11 May 2016 08:42:20 +0200 Subject: [PATCH 018/315] fix watchers close problems --- app/coffee/modules/common/lightboxes.coffee | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index b1dd0925..9bc744b8 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -75,13 +75,16 @@ class LightboxService extends taiga.Service docEl = angular.element(document) docEl.off(".lightbox") docEl.off(".keyboard-navigation") # Hack: to fix problems in the WYSIWYG textareas when press ENTER - $el.one "transitionend", => - $el.removeAttr('style') - $el.removeClass("open").removeClass('close') @animationFrame.add -> $el.addClass('close') + $el.one "transitionend", => + $el.removeAttr('style') + $el.removeClass("open").removeClass('close') + + + if $el.hasClass("remove-on-close") scope = $el.data("scope") scope.$destroy() if scope @@ -658,7 +661,7 @@ WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationS render(users) $el.find("input").focus() - $el.on "click", ".user-list-single", debounce 2000, (event) -> + $el.on "click", ".user-list-single", debounce 200, (event) -> closeLightbox() event.preventDefault() From 163fb4ab526d96958961a73ccc6293e5fac6139d Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 12 May 2016 14:43:17 +0200 Subject: [PATCH 019/315] detail model transformation to prevent collisions --- app/coffee/modules/common.coffee | 45 +++++++ app/coffee/modules/common/components.coffee | 106 +++++++++------- app/coffee/modules/common/estimation.coffee | 43 ++++--- app/coffee/modules/common/lightboxes.coffee | 54 ++++---- app/coffee/modules/common/tags.coffee | 50 ++++---- app/coffee/modules/issues/detail.coffee | 115 ++++++++++-------- app/coffee/modules/tasks/detail.coffee | 57 +++++---- app/coffee/modules/userstories/detail.coffee | 88 ++++++++------ .../common/components/status-button.jade | 19 +++ app/partials/issue/issues-status-button.jade | 20 --- app/partials/us/us-detail.jade | 2 +- app/partials/us/us-status-button.jade | 19 --- app/styles/modules/common/ticket-data.scss | 8 +- e2e/helpers/detail-helper.js | 2 +- 14 files changed, 357 insertions(+), 271 deletions(-) create mode 100644 app/partials/common/components/status-button.jade delete mode 100644 app/partials/issue/issues-status-button.jade delete mode 100644 app/partials/us/us-status-button.jade diff --git a/app/coffee/modules/common.coffee b/app/coffee/modules/common.coffee index a08eeca1..e6b47dbe 100644 --- a/app/coffee/modules/common.coffee +++ b/app/coffee/modules/common.coffee @@ -263,6 +263,51 @@ Qqueue = ($q) -> module.factory("$tgQqueue", ["$q", Qqueue]) + +############################################################################# +## Queue model transformation +############################################################################# + +class QueueModelTransformation extends taiga.Service + @.$inject = [ + "$tgQqueue", + "$tgRepo", + "$q", + "$tgModel" + ] + + constructor: (@qqueue, @repo, @q, @model) -> + + setObject: (@scope, @prop) -> + + clone: () -> + attrs = _.cloneDeep(@.scope[@.prop]._attrs) + model = @model.make_model(@.scope[@.prop]._name, attrs) + + return model + + getObj: () -> + return @.scope[@.prop] + + save: (transformation) -> + defered = @q.defer() + + @qqueue.add () => + clone = @.clone() + + transformation(clone) + + success = () => + @.scope[@.prop] = clone + + defered.resolve.apply(null, arguments) + + @repo.save(clone).then(success, defered.reject) + + return defered.promise + +module.service("$tgQueueModelTransformation", QueueModelTransformation) + ############################################################################# ## Templates ############################################################################# diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index f6598fa8..cde71cb0 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -169,7 +169,7 @@ module.directive("tgCreatedByDisplay", ["$tgTemplate", "$compile", "$translate", ## Watchers directive ############################################################################# -WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template, $compile, $translate) -> +WatchersDirective = ($rootscope, $confirm, $repo, $modelTransform, $template, $compile, $translate) -> # You have to include a div with the tg-lb-watchers directive in the page # where use this directive template = $template.get("common/components/watchers.html", true) @@ -178,32 +178,33 @@ WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template, $compile, isEditable = -> return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1 - save = $qqueue.bindAdd (watchers) => - item = $model.$modelValue.clone() - item.watchers = watchers - $model.$setViewValue(item) + save = (watchers) -> + transform = $modelTransform.save (item) -> + item.watchers = watchers - promise = $repo.save($model.$modelValue) - promise.then -> + return item + + transform.then -> watchers = _.map(watchers, (watcherId) -> $scope.usersById[watcherId]) renderWatchers(watchers) $rootscope.$broadcast("object:updated") - promise.then null, -> - $model.$modelValue.revert() + transform.then null, -> $confirm.notify("error") - deleteWatcher = $qqueue.bindAdd (watcherIds) => - item = $model.$modelValue.clone() - item.watchers = watcherIds - $model.$setViewValue(item) + deleteWatcher = (watcherIds) -> + transform = $modelTransform.save (item) -> + item.watchers = watcherIds - promise = $repo.save($model.$modelValue) - promise.then -> + return item + + transform.then () -> + item = $modelTransform.getObj() watchers = _.map(item.watchers, (watcherId) -> $scope.usersById[watcherId]) renderWatchers(watchers) $rootscope.$broadcast("object:updated") - promise.then null, -> + + transform.then null, -> item.revert() $confirm.notify("error") @@ -250,7 +251,7 @@ WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template, $compile, return {link:link, require:"ngModel"} -module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQqueue", "$tgTemplate", "$compile", +module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQueueModelTransformation", "$tgTemplate", "$compile", "$translate", WatchersDirective]) @@ -258,7 +259,7 @@ module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQqueu ## Assigned to directive ############################################################################# -AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $qqueue, $template, $translate, $compile, $currentUserService) -> +AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $template, $translate, $compile, $currentUserService) -> # You have to include a div with the tg-lb-assignedto directive in the page # where use this directive template = $template.get("common/components/assigned-to.html", true) @@ -267,24 +268,29 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $qqueue, $template isEditable = -> return $scope.project?.my_permissions?.indexOf($attrs.requiredPerm) != -1 - save = $qqueue.bindAdd (userId) => - $model.$modelValue.assigned_to = userId + save = (userId) -> + item = $model.$modelValue.clone() + item.assigned_to = userId currentLoading = $loading() .target($el) .start() - promise = $repo.save($model.$modelValue) - promise.then -> + transform = $modelTransform.save (item) -> + item.assigned_to = userId + + return item + + transform.then -> currentLoading.finish() - renderAssignedTo($model.$modelValue) + renderAssignedTo($modelTransform.getObj()) $rootscope.$broadcast("object:updated") - promise.then null, -> - $model.$modelValue.revert() + + transform.then null, -> $confirm.notify("error") currentLoading.finish() - return promise + return transform renderAssignedTo = (assignedObject) -> if assignedObject?.assigned_to? @@ -347,7 +353,7 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $qqueue, $template require:"ngModel" } -module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoading", "$tgQqueue", "$tgTemplate", "$translate", "$compile","tgCurrentUserService", +module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$translate", "$compile","tgCurrentUserService", AssignedToDirective]) @@ -447,7 +453,7 @@ module.directive("tgDeleteButton", ["$log", "$tgRepo", "$tgConfirm", "$tgLocatio ## Editable subject directive ############################################################################# -EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $qqueue, $template) -> +EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $modelTransform, $template) -> template = $template.get("common/components/editable-subject.html") link = ($scope, $el, $attrs, $model) -> @@ -459,25 +465,29 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $qqueue, $tem isEditable = -> return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 - save = $qqueue.bindAdd (subject) => - $model.$modelValue.subject = subject - + save = (subject) -> currentLoading = $loading() .target($el.find('.save-container')) .start() - promise = $repo.save($model.$modelValue) - promise.then -> + transform = $modelTransform.save (item) -> + item.subject = subject + + return item + + transform.then => $confirm.notify("success") $rootscope.$broadcast("object:updated") $el.find('.edit-subject').hide() $el.find('.view-subject').show() - promise.then null, -> + + transform.then null, -> $confirm.notify("error") - promise.finally -> + + transform.finally -> currentLoading.finish() - return promise + return transform $el.click -> return if not isEditable() @@ -521,7 +531,7 @@ EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $qqueue, $tem template: template } -module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", +module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", EditableSubjectDirective]) @@ -529,7 +539,7 @@ module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$ ## Editable description directive ############################################################################# -EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, $selectedText, $qqueue, $template, $translate) -> +EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, $selectedText, $modelTransform, $template, $translate) -> template = $template.get("common/components/editable-description.html") noDescriptionMegEditMode = $template.get("common/components/editable-description-msg-edit-mode.html") noDescriptionMegReadMode = $template.get("common/components/editable-description-msg-read-mode.html") @@ -545,22 +555,26 @@ EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, isEditable = -> return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 - save = $qqueue.bindAdd (description) => - $model.$modelValue.description = description - + save = (description) -> currentLoading = $loading() .target($el.find('.save-container')) .start() - promise = $repo.save($model.$modelValue) - promise.then -> + transform = $modelTransform.save (item) -> + item.description = description + + return item + + transform.then -> $confirm.notify("success") $rootscope.$broadcast("object:updated") $el.find('.edit-description').hide() $el.find('.view-description').show() - promise.then null, -> + + transform.then null, -> $confirm.notify("error") - promise.finally -> + + transform.finally -> currentLoading.finish() cancelEdition = () -> @@ -631,7 +645,7 @@ module.directive("tgEditableDescription", [ "$compile", "$tgLoading", "$selectedText", - "$tgQqueue", + "$tgQueueModelTransformation", "$tgTemplate", "$translate", EditableDescriptionDirective]) diff --git a/app/coffee/modules/common/estimation.coffee b/app/coffee/modules/common/estimation.coffee index 0db8bf62..0e274ca4 100644 --- a/app/coffee/modules/common/estimation.coffee +++ b/app/coffee/modules/common/estimation.coffee @@ -80,7 +80,7 @@ module.directive("tgLbUsEstimation", ["$tgEstimationsService", "$rootScope", "$t ## User story estimation directive ############################################################################# -UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $qqueue, $template, $compile) -> +UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $template, $compile) -> # Display the points of a US and you can edit it. # # Example: @@ -91,11 +91,14 @@ UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $qqueue, $tem # - scope.project object link = ($scope, $el, $attrs, $model) -> - $scope.$watch $attrs.ngModel, (us) -> + $scope.$watchCollection () -> + return $model.$modelValue && $model.$modelValue.points + , () -> + us = $model.$modelValue if us estimationProcess = $tgEstimationsService.create($el, us, $scope.project) estimationProcess.onSelectedPointForRole = (roleId, pointId) -> - @save(roleId, pointId).then -> + @save(roleId, pointId).then () -> $rootScope.$broadcast("object:updated") estimationProcess.render = () -> @@ -121,7 +124,7 @@ UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $qqueue, $tem require: "ngModel" } -module.directive("tgUsEstimation", ["$tgEstimationsService", "$rootScope", "$tgRepo", "$tgQqueue", +module.directive("tgUsEstimation", ["$tgEstimationsService", "$rootScope", "$tgRepo", "$tgTemplate", "$compile", UsEstimationDirective]) @@ -129,7 +132,7 @@ module.directive("tgUsEstimation", ["$tgEstimationsService", "$rootScope", "$tgR ## Estimations service ############################################################################# -EstimationsService = ($template, $qqueue, $repo, $confirm, $q) -> +EstimationsService = ($template, $modelTransform, $repo, $confirm, $q) -> pointsTemplate = $template.get("common/estimation/us-estimation-points.html", true) class EstimationProcess @@ -143,17 +146,23 @@ EstimationsService = ($template, $qqueue, $repo, $confirm, $q) -> save: (roleId, pointId) -> deferred = $q.defer() - $qqueue.add () => - onSuccess = => - deferred.resolve() - onError = => - $confirm.notify("error") - @us.revert() - @render() - deferred.reject() + transform = $modelTransform.save (us) => + points = _.clone(@us.points, true) + points[roleId] = pointId - $repo.save(@us).then(onSuccess, onError) + us.points = points + + return us + + onSuccess = => + deferred.resolve() + + onError = => + $confirm.notify("error") + deferred.reject() + + transform.then(onSuccess, onError) return deferred.promise @@ -197,10 +206,6 @@ EstimationsService = ($template, $qqueue, $repo, $confirm, $q) -> roleId = target.data("role-id") pointId = target.data("point-id") @$el.find(".popover").popover().close() - points = _.clone(@us.points, true) - points[roleId] = pointId - @us.points = points - @render() @onSelectedPointForRole(roleId, pointId) renderPointsSelector: (roleId, target) -> @@ -247,5 +252,5 @@ EstimationsService = ($template, $qqueue, $repo, $confirm, $q) -> create: create } -module.factory("$tgEstimationsService", ["$tgTemplate", "$tgQqueue", "$tgRepo", "$tgConfirm", +module.factory("$tgEstimationsService", ["$tgTemplate", "$tgQueueModelTransformation", "$tgRepo", "$tgConfirm", "$q", EstimationsService]) diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index 9bc744b8..c658a710 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -169,47 +169,51 @@ module.directive("lightbox", ["lightboxService", LightboxDirective]) # Issue/Userstory blocking message lightbox directive. -BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading, $qqueue, $translate) -> +BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading, $modelTransform, $translate) -> link = ($scope, $el, $attrs, $model) -> title = $translate.instant($attrs.title) $el.find("h2.title").text(title) - unblock = $qqueue.bindAdd (item, finishCallback) => - promise = $tgrepo.save(item) - promise.then -> + unblock = (finishCallback) => + transform = $modelTransform.save (item) -> + item.is_blocked = false + item.blocked_note = "" + + return item + + transform.then -> $confirm.notify("success") $rootscope.$broadcast("object:updated") - $model.$setViewValue(item) finishCallback() - promise.then null, -> + transform.then null, -> $confirm.notify("error") item.revert() - $model.$setViewValue(item) - promise.finally -> + transform.finally -> finishCallback() - return promise - - block = $qqueue.bindAdd (item) => - $model.$setViewValue(item) + return transform + block = () -> currentLoading = $loading() .target($el.find(".button-green")) .start() - promise = $tgrepo.save($model.$modelValue) - promise.then -> + transform = $modelTransform.save (item) -> + item.is_blocked = true + item.blocked_note = $el.find(".reason").val() + + return item + + transform.then -> $confirm.notify("success") $rootscope.$broadcast("object:updated") - promise.then null, -> + transform.then null, -> $confirm.notify("error") - item.revert() - $model.$setViewValue(item) - promise.finally -> + transform.finally -> currentLoading.finish() lightboxService.close($el) @@ -218,11 +222,7 @@ BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loadi lightboxService.open($el) $scope.$on "unblock", (event, model, finishCallback) => - item = $model.$modelValue.clone() - item.is_blocked = false - item.blocked_note = "" - - unblock(item, finishCallback) + unblock(finishCallback) $scope.$on "$destroy", -> $el.off() @@ -230,11 +230,7 @@ BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loadi $el.on "click", ".button-green", (event) -> event.preventDefault() - item = $model.$modelValue.clone() - item.is_blocked = true - item.blocked_note = $el.find(".reason").val() - - block(item) + block() return { templateUrl: "common/lightbox/lightbox-block.html" @@ -242,7 +238,7 @@ BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loadi require: "ngModel" } -module.directive("tgLbBlock", ["$rootScope", "$tgRepo", "$tgConfirm", "lightboxService", "$tgLoading", "$tgQqueue", "$translate", BlockLightboxDirective]) +module.directive("tgLbBlock", ["$rootScope", "$tgRepo", "$tgConfirm", "lightboxService", "$tgLoading", "$tgQueueModelTransformation", "$translate", BlockLightboxDirective]) ############################################################################# diff --git a/app/coffee/modules/common/tags.coffee b/app/coffee/modules/common/tags.coffee index 706fa4a4..0bd42dfa 100644 --- a/app/coffee/modules/common/tags.coffee +++ b/app/coffee/modules/common/tags.coffee @@ -222,7 +222,7 @@ module.directive("tgLbTagLine", ["$tgResources", "$tgTemplate", "$compile", LbTa ## TagLine Directive (for detail pages) ############################################################################# -TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template, $compile) -> +TagLineDirective = ($rootScope, $repo, $rs, $confirm, $modelTransform, $template, $compile) -> ENTER_KEY = 13 ESC_KEY = 27 COMMA_KEY = 188 @@ -269,48 +269,49 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template, $compi autocomplete.close() ## Aux methods - addValue = $qqueue.bindAdd (value) -> + addValue = (value) -> value = trim(value.toLowerCase()) return if value.length == 0 - tags = _.clone($model.$modelValue.tags, false) - tags = [] if not tags? - tags.push(value) if value not in tags + transform = $modelTransform.save (item) -> + if not item.tags + item.tags = [] - model = $model.$modelValue.clone() - model.tags = tags - $model.$setViewValue(model) + tags = _.clone(item.tags) + + tags.push(value) if value not in tags + + item.tags = tags + + return item onSuccess = -> $rootScope.$broadcast("object:updated") + onError = -> $confirm.notify("error") - model.revert() - $model.$setViewValue(model) hideSaveButton() - return $repo.save(model).then(onSuccess, onError) + return transform.then(onSuccess, onError) - deleteValue = $qqueue.bindAdd (value) -> + deleteValue = (value) -> value = trim(value.toLowerCase()) return if value.length == 0 - tags = _.clone($model.$modelValue.tags, false) - tags = _.pull(tags, value) + transform = $modelTransform.save (item) -> + tags = _.clone(item.tags, false) + item.tags = _.pull(tags, value) - model = $model.$modelValue.clone() - model.tags = tags - $model.$setViewValue(model) + return item onSuccess = -> $rootScope.$broadcast("object:updated") + onError = -> $confirm.notify("error") - model.revert() - $model.$setViewValue(model) - return $repo.save(model).then(onSuccess, onError) + return transform.then(onSuccess, onError) saveInputTag = () -> value = $el.find("input").val() @@ -374,7 +375,12 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template, $compi addValue(input.val()) input.val("") - $scope.$watch $attrs.ngModel, (model) -> + + $scope.$watchCollection () -> + return $model.$modelValue?.tags + , () -> + model = $model.$modelValue + return if not model if model.tags?.length @@ -394,5 +400,5 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $qqueue, $template, $compi templateUrl: "common/tag/tag-line.html" } -module.directive("tgTagLine", ["$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$tgQqueue", +module.directive("tgTagLine", ["$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$tgQueueModelTransformation", "$tgTemplate", "$compile", TagLineDirective]) diff --git a/app/coffee/modules/issues/detail.coffee b/app/coffee/modules/issues/detail.coffee index a4146c7a..1448c28a 100644 --- a/app/coffee/modules/issues/detail.coffee +++ b/app/coffee/modules/issues/detail.coffee @@ -51,11 +51,12 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin) "tgAppMetaService", "$tgAnalytics", "$tgNavUrls", - "$translate" + "$translate", + "$tgQueueModelTransformation" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, - @log, @appMetaService, @analytics, @navUrls, @translate) -> + @log, @appMetaService, @analytics, @navUrls, @translate, @modelTransform) -> bindMethods(@) @scope.issueRef = @params.issueref @@ -130,6 +131,8 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin) @scope.issueId = issue.id @scope.commentModel = issue + @modelTransform.setObject(@scope, 'issue') + if @scope.issue.neighbors.previous?.ref? ctx = { project: @scope.project.slug @@ -245,7 +248,7 @@ module.directive("tgIssueStatusDisplay", ["$tgTemplate", "$compile", IssueStatus ## Issue status button directive ############################################################################# -IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template, $compile) -> +IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTransform, $template, $compile) -> # Display the status of Issue and you can edit it. # # Example: @@ -256,7 +259,7 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $t # - scope.statusById object # - $scope.project.my_permissions - template = $template.get("issue/issues-status-button.html", true) + template = $template.get("common/components/status-button.html", true) link = ($scope, $el, $attrs, $model) -> isEditable = -> @@ -275,28 +278,27 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $t $el.html(html) - save = $qqueue.bindAdd (statusId) => + save = (statusId) -> $.fn.popover().closeAll() - issue = $model.$modelValue.clone() - issue.status = statusId - currentLoading = $loading() .target($el) .start() + transform = $modelTransform.save (issue) -> + issue.status = statusId + + return issue + onSuccess = -> - $model.$setViewValue(issue) $rootScope.$broadcast("object:updated") currentLoading.finish() + onError = -> $confirm.notify("error") - issue.revert() - $model.$setViewValue(issue) currentLoading.finish() - - $repo.save(issue).then(onSuccess, onError) + transform.then(onSuccess, onError) $el.on "click", ".js-edit-status", (event) -> event.preventDefault() @@ -314,7 +316,10 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $t save(target.data("status-id")) - $scope.$watch $attrs.ngModel, (issue) -> + $scope.$watch () -> + return $model.$modelValue?.status + , () -> + issue = $model.$modelValue render(issue) if issue $scope.$on "$destroy", -> @@ -326,13 +331,13 @@ IssueStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $t require: "ngModel" } -module.directive("tgIssueStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile", IssueStatusButtonDirective]) +module.directive("tgIssueStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$compile", IssueStatusButtonDirective]) ############################################################################# ## Issue type button directive ############################################################################# -IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template, $compile) -> +IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTransform, $template, $compile) -> # Display the type of Issue and you can edit it. # # Example: @@ -362,27 +367,27 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $tem $el.html(html) - save = $qqueue.bindAdd (type) => + save = (type) -> $.fn.popover().closeAll() - issue = $model.$modelValue.clone() - issue.type = type currentLoading = $loading() .target($el.find(".level-name")) .start() + transform = $modelTransform.save (issue) -> + issue.type = type + + return issue + onSuccess = -> - $model.$setViewValue(issue) $rootScope.$broadcast("object:updated") currentLoading.finish() onError = -> $confirm.notify("error") - issue.revert() - $model.$setViewValue(issue) currentLoading.finish() - $repo.save(issue).then(onSuccess, onError) + transform.then(onSuccess, onError) $el.on "click", ".type-data", (event) -> event.preventDefault() @@ -400,7 +405,10 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $tem type = target.data("type-id") save(type) - $scope.$watch $attrs.ngModel, (issue) -> + $scope.$watch () -> + return $model.$modelValue?.type + , () -> + issue = $model.$modelValue render(issue) if issue $scope.$on "$destroy", -> @@ -412,14 +420,14 @@ IssueTypeButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $tem require: "ngModel" } -module.directive("tgIssueTypeButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile", IssueTypeButtonDirective]) +module.directive("tgIssueTypeButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$compile", IssueTypeButtonDirective]) ############################################################################# ## Issue severity button directive ############################################################################# -IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template, $compile) -> +IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTransform, $template, $compile) -> # Display the severity of Issue and you can edit it. # # Example: @@ -449,27 +457,27 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $el.html(html) - save = $qqueue.bindAdd (severity) => + save = (severity) -> $.fn.popover().closeAll() - issue = $model.$modelValue.clone() - issue.severity = severity - currentLoading = $loading() .target($el.find(".level-name")) .start() + transform = $modelTransform.save (issue) -> + issue.severity = severity + + return issue + onSuccess = -> - $model.$setViewValue(issue) $rootScope.$broadcast("object:updated") currentLoading.finish() + onError = -> $confirm.notify("error") - issue.revert() - $model.$setViewValue(issue) currentLoading.finish() - $repo.save(issue).then(onSuccess, onError) + transform.then(onSuccess, onError) $el.on "click", ".severity-data", (event) -> event.preventDefault() @@ -488,7 +496,10 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, save(severity) - $scope.$watch $attrs.ngModel, (issue) -> + $scope.$watch () -> + return $model.$modelValue?.severity + , () -> + issue = $model.$modelValue render(issue) if issue $scope.$on "$destroy", -> @@ -500,14 +511,14 @@ IssueSeverityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, require: "ngModel" } -module.directive("tgIssueSeverityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile", IssueSeverityButtonDirective]) +module.directive("tgIssueSeverityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$compile", IssueSeverityButtonDirective]) ############################################################################# ## Issue priority button directive ############################################################################# -IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template, $compile) -> +IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTransform, $template, $compile) -> # Display the priority of Issue and you can edit it. # # Example: @@ -537,27 +548,27 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $el.html(html) - save = $qqueue.bindAdd (priority) => + save = (priority) -> $.fn.popover().closeAll() - issue = $model.$modelValue.clone() - issue.priority = priority - currentLoading = $loading() .target($el.find(".level-name")) .start() + transform = $modelTransform.save (issue) -> + issue.priority = priority + + return issue + onSuccess = -> - $model.$setViewValue(issue) $rootScope.$broadcast("object:updated") currentLoading.finish() + onError = -> $confirm.notify("error") - issue.revert() - $model.$setViewValue(issue) currentLoading.finish() - $repo.save(issue).then(onSuccess, onError) + transform.then(onSuccess, onError) $el.on "click", ".priority-data", (event) -> event.preventDefault() @@ -576,7 +587,10 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, save(priority) - $scope.$watch $attrs.ngModel, (issue) -> + $scope.$watch () -> + return $model.$modelValue?.priority + , () -> + issue = $model.$modelValue render(issue) if issue $scope.$on "$destroy", -> @@ -588,17 +602,17 @@ IssuePriorityButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, require: "ngModel" } -module.directive("tgIssuePriorityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile", IssuePriorityButtonDirective]) +module.directive("tgIssuePriorityButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$compile", IssuePriorityButtonDirective]) ############################################################################# ## Promote Issue to US button directive ############################################################################# -PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue, $translate) -> +PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $translate) -> link = ($scope, $el, $attrs, $model) -> - save = $qqueue.bindAdd (issue, askResponse) => + save = (issue, askResponse) => data = { generated_from_issue: issue.id project: issue.project, @@ -620,7 +634,6 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue, $transl $repo.create("userstories", data).then(onSuccess, onError) - $el.on "click", "a", (event) -> event.preventDefault() issue = $model.$modelValue @@ -642,5 +655,5 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue, $transl link: link } -module.directive("tgPromoteIssueToUsButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgQqueue", "$translate" +module.directive("tgPromoteIssueToUsButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$translate" PromoteIssueToUsButtonDirective]) diff --git a/app/coffee/modules/tasks/detail.coffee b/app/coffee/modules/tasks/detail.coffee index b0c8f688..05e6aff4 100644 --- a/app/coffee/modules/tasks/detail.coffee +++ b/app/coffee/modules/tasks/detail.coffee @@ -49,11 +49,12 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin) "tgAppMetaService", "$tgNavUrls", "$tgAnalytics", - "$translate" + "$translate", + "$tgQueueModelTransformation" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, - @log, @appMetaService, @navUrls, @analytics, @translate) -> + @log, @appMetaService, @navUrls, @analytics, @translate, @modelTransform) -> bindMethods(@) @scope.taskRef = @params.taskref @@ -118,6 +119,8 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin) @scope.taskId = task.id @scope.commentModel = task + @modelTransform.setObject(@scope, 'task') + if @scope.task.neighbors.previous?.ref? ctx = { project: @scope.project.slug @@ -245,7 +248,7 @@ module.directive("tgTaskStatusDisplay", ["$tgTemplate", "$compile", TaskStatusDi ## Task status button directive ############################################################################# -TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $compile, $translate, $template) -> +TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTransform, $compile, $translate, $template) -> # Display the status of Task and you can edit it. # # Example: @@ -256,7 +259,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co # - scope.statusById object # - $scope.project.my_permissions - template = $template.get("us/us-status-button.html", true) + template = $template.get("common/components/status-button.html", true) link = ($scope, $el, $attrs, $model) -> isEditable = -> @@ -273,16 +276,17 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co $el.html(html) - save = $qqueue.bindAdd (status) => - task = $model.$modelValue.clone() - task.status = status - + save = (status) -> currentLoading = $loading() .target($el) .start() + transform = $modelTransform.save (task) -> + task.status = status + + return task + onSuccess = -> - $model.$setViewValue(task) $rootScope.$broadcast("object:updated") currentLoading.finish() @@ -290,7 +294,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co $confirm.notify("error") currentLoading.finish() - $repo.save(task).then(onSuccess, onError) + transform.then(onSuccess, onError) $el.on "click", ".js-edit-status", (event) -> event.preventDefault() @@ -310,7 +314,10 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co save(target.data("status-id")) - $scope.$watch $attrs.ngModel, (task) -> + $scope.$watch () -> + return $model.$modelValue?.status + , () -> + task = $model.$modelValue render(task) if task $scope.$on "$destroy", -> @@ -322,11 +329,11 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co require: "ngModel" } -module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", +module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", "$compile", "$translate", "$tgTemplate", TaskStatusButtonDirective]) -TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue, $compile, $template) -> +TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $modelTransform, $compile, $template) -> template = $template.get("issue/iocaine-button.html", true) link = ($scope, $el, $attrs, $model) -> @@ -345,24 +352,23 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue html = $compile(template(ctx))($scope) $el.html(html) - save = $qqueue.bindAdd (is_iocaine) => - task = $model.$modelValue.clone() - task.is_iocaine = is_iocaine - + save = (is_iocaine) -> currentLoading = $loading() .target($el.find('label')) .start() - promise = $tgrepo.save(task) + transform = $modelTransform.save (task) -> + task.is_iocaine = is_iocaine - promise.then -> - $model.$setViewValue(task) + return task + + transform.then -> $rootscope.$broadcast("object:updated") - promise.then null, -> + transform.then null, -> $confirm.notify("error") - promise.finally -> + transform.finally -> currentLoading.finish() $el.on "click", ".is-iocaine", (event) -> @@ -371,7 +377,10 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue is_iocaine = not $model.$modelValue.is_iocaine save(is_iocaine) - $scope.$watch $attrs.ngModel, (task) -> + $scope.$watch () -> + return $model.$modelValue?.is_iocaine + , () -> + task = $model.$modelValue render(task) if task $scope.$on "$destroy", -> @@ -383,5 +392,5 @@ TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue require: "ngModel" } -module.directive("tgTaskIsIocaineButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", +module.directive("tgTaskIsIocaineButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", "$compile", "$tgTemplate", TaskIsIocaineButtonDirective]) diff --git a/app/coffee/modules/userstories/detail.coffee b/app/coffee/modules/userstories/detail.coffee index 997ca0bb..8836c8e7 100644 --- a/app/coffee/modules/userstories/detail.coffee +++ b/app/coffee/modules/userstories/detail.coffee @@ -49,11 +49,12 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin) "tgAppMetaService", "$tgNavUrls", "$tgAnalytics", - "$translate" + "$translate", + "$tgQueueModelTransformation" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, - @log, @appMetaService, @navUrls, @analytics, @translate) -> + @log, @appMetaService, @navUrls, @analytics, @translate, @modelTransform) -> bindMethods(@) @scope.usRef = @params.usref @@ -159,6 +160,8 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin) @scope.usId = us.id @scope.commentModel = us + @modelTransform.setObject(@scope, 'us') + if @scope.us.neighbors.previous?.ref? ctx = { project: @scope.project.slug @@ -285,7 +288,7 @@ module.directive("tgUsStatusDisplay", ["$tgTemplate", "$compile", UsStatusDispla ## User story status button directive ############################################################################# -UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $template) -> +UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTransform, $template, $compile) -> # Display the status of a US and you can edit it. # # Example: @@ -296,7 +299,7 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $temp # - scope.statusById object # - $scope.project.my_permissions - template = $template.get("us/us-status-button.html", true) + template = $template.get("common/components/status-button.html", true) link = ($scope, $el, $attrs, $model) -> isEditable = -> @@ -313,19 +316,21 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $temp $el.html(html) - save = $qqueue.bindAdd (status) => - us = $model.$modelValue.clone() + $compile($el.contents())($scope); - us.status = status - - $.fn.popover().closeAll() + save = (status) => + $el.find(".pop-status").popover().close() currentLoading = $loading() - .target($el) + .target($el.find('.js-edit-status')) .start() + transform = $modelTransform.save (us) -> + us.status = status + + return us + onSuccess = -> - $model.$setViewValue(us) $rootScope.$broadcast("object:updated") currentLoading.finish() @@ -333,7 +338,7 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $temp $confirm.notify("error") currentLoading.finish() - $repo.save(us).then(onSuccess, onError) + transform.then(onSuccess, onError) $el.on "click", ".js-edit-status", (event) -> event.preventDefault() @@ -352,7 +357,11 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $temp save(status) - $scope.$watch $attrs.ngModel, (us) -> + $scope.$watch () -> + return $model.$modelValue?.status + , () -> + us = $model.$modelValue + render(us) if us $scope.$on "$destroy", -> @@ -364,7 +373,7 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $temp require: "ngModel" } -module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading","$tgQqueue", "$tgTemplate", +module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading","$tgQueueModelTransformation", "$tgTemplate", "$compile", UsStatusButtonDirective]) @@ -372,7 +381,7 @@ module.directive("tgUsStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$t ## User story team requirements button directive ############################################################################# -UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue, $template, $compile) -> +UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $modelTransform, $template, $compile) -> template = $template.get("us/us-team-requirement-button.html", true) link = ($scope, $el, $attrs, $model) -> @@ -389,21 +398,21 @@ UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qq $el.html(html) - save = $qqueue.bindAdd (team_requirement) => - us = $model.$modelValue.clone() - us.team_requirement = team_requirement - + save = (team_requirement) -> currentLoading = $loading() .target($el.find("label")) .start() - promise = $tgrepo.save(us) - promise.then => - $model.$setViewValue(us) + transform = $modelTransform.save (us) -> + us.team_requirement = team_requirement + + return us + + transform.then => currentLoading.finish() $rootscope.$broadcast("object:updated") - promise.then null, -> + transform.then null, -> currentLoading.finish() $confirm.notify("error") @@ -414,7 +423,11 @@ UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qq save(team_requirement) - $scope.$watch $attrs.ngModel, (us) -> + $scope.$watch () -> + return $model.$modelValue?.team_requirement + , () -> + us = $model.$modelValue + render(us) if us $scope.$on "$destroy", -> @@ -426,13 +439,13 @@ UsTeamRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qq require: "ngModel" } -module.directive("tgUsTeamRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile", UsTeamRequirementButtonDirective]) +module.directive("tgUsTeamRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$compile", UsTeamRequirementButtonDirective]) ############################################################################# ## User story client requirements button directive ############################################################################# -UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue, $template, $compile) -> +UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $modelTransform, $template, $compile) -> template = $template.get("us/us-client-requirement-button.html", true) link = ($scope, $el, $attrs, $model) -> @@ -447,21 +460,21 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $ html = $compile(template(ctx))($scope) $el.html(html) - save = $qqueue.bindAdd (client_requirement) => - us = $model.$modelValue.clone() - us.client_requirement = client_requirement - + save = (client_requirement) -> currentLoading = $loading() .target($el.find("label")) .start() - promise = $tgrepo.save(us) - promise.then => - $model.$setViewValue(us) + transform = $modelTransform.save (us) -> + us.client_requirement = client_requirement + + return us + + transform.then => currentLoading.finish() $rootscope.$broadcast("object:updated") - promise.then null, -> + transform.then null, -> $confirm.notify("error") $el.on "click", ".client-requirement", (event) -> @@ -470,7 +483,10 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $ client_requirement = not $model.$modelValue.client_requirement save(client_requirement) - $scope.$watch $attrs.ngModel, (us) -> + $scope.$watch () -> + return $model.$modelValue?.client_requirement + , () -> + us = $model.$modelValue render(us) if us $scope.$on "$destroy", -> @@ -482,5 +498,5 @@ UsClientRequirementButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $ require: "ngModel" } -module.directive("tgUsClientRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue", "$tgTemplate", "$compile", +module.directive("tgUsClientRequirementButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$compile", UsClientRequirementButtonDirective]) diff --git a/app/partials/common/components/status-button.jade b/app/partials/common/components/status-button.jade new file mode 100644 index 00000000..b1faae5e --- /dev/null +++ b/app/partials/common/components/status-button.jade @@ -0,0 +1,19 @@ +span.detail-status-inner.js-edit-status( + class!="<% if(editable){ %>clickable<% }%>" +) + span(style!="background-color:<%- status.color %>") + span.e2e-status <%- status.name %> + <% if(editable){ %> + tg-svg(svg-icon="icon-arrow-down") + <% }%> + +ul.pop-status.popover + <% _.each(statuses, function(st) { %> + li + a.status( + href="" + title!="<%- st.name %>" + data-status-id!="<%- st.id %>" + ) + | <%- st.name %> + <% }); %> diff --git a/app/partials/issue/issues-status-button.jade b/app/partials/issue/issues-status-button.jade deleted file mode 100644 index 593ded89..00000000 --- a/app/partials/issue/issues-status-button.jade +++ /dev/null @@ -1,20 +0,0 @@ -span.detail-status-inner.js-edit-status( - class!="<% if(editable){ %>clickable<% }%>" - style!="background-color:<%- status.color %>" - ng-click="editStatus()" -) - span <%- status.name %> - <% if(editable){ %> - tg-svg(svg-icon="icon-arrow-down") - <% }%> - - ul.popover.pop-status - <% _.each(statuses, function(st) { %> - li - a.status( - href="" - title!="<%- st.name %>" - data-status-id!="<%- st.id %>" - ) - | <%- st.name %> - <% }); %> diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index 193615e0..20a72a44 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -96,7 +96,7 @@ div.wrapper( ) sidebar.menu-secondary.sidebar.ticket-data - + section.ticket-header span.ticket-title( tg-us-status-display diff --git a/app/partials/us/us-status-button.jade b/app/partials/us/us-status-button.jade deleted file mode 100644 index 7bcf995f..00000000 --- a/app/partials/us/us-status-button.jade +++ /dev/null @@ -1,19 +0,0 @@ -span.detail-status-inner.js-edit-status( - class!="<% if(editable){ %>clickable<% }%>" - style!="background-color:<%- status.color %>" -) - span <%- status.name %> - <% if(editable){ %> - tg-svg(svg-icon="icon-arrow-down") - <% }%> - - ul.pop-status.popover - <% _.each(statuses, function(st) { %> - li - a.status( - href="" - title!="<%- st.name %>" - data-status-id!="<%- st.id %>" - ) - | <%- st.name %> - <% }); %> diff --git a/app/styles/modules/common/ticket-data.scss b/app/styles/modules/common/ticket-data.scss index 91415fe2..1f18c223 100644 --- a/app/styles/modules/common/ticket-data.scss +++ b/app/styles/modules/common/ticket-data.scss @@ -22,11 +22,13 @@ } .detail-status-inner { align-items: center; - color: $white; display: flex; justify-content: flex-start; - padding: .15rem .25rem; - text-transform: uppercase; + > span { + color: $white; + padding: .15rem .25rem; + text-transform: uppercase; + } } .pop-status { @include popover(150px, 1.25rem, 0, '', ''); diff --git a/e2e/helpers/detail-helper.js b/e2e/helpers/detail-helper.js index ebff2cf8..280a141d 100644 --- a/e2e/helpers/detail-helper.js +++ b/e2e/helpers/detail-helper.js @@ -107,7 +107,7 @@ helper.statusSelector = function() { return this.getSelectedStatus(); }, getSelectedStatus: async function(){ - return el.$$('.detail-status-inner span').first().getInnerHtml(); + return el.$$('.detail-status-inner .e2e-status').first().getInnerHtml(); } }; From 78782fd51fc6968b873f1469ddfb9906613d208b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 10 May 2016 08:32:32 +0200 Subject: [PATCH 020/315] Disabling drag and drop of attachments on non existing wiki pages --- app/coffee/modules/common/components.coffee | 2 +- app/locales/taiga/locale-en.json | 1 + app/partials/common/components/wysiwyg.jade | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index cde71cb0..6f769312 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -656,7 +656,7 @@ EditableWysiwyg = (attachmentsService, attachmentsFullService) -> link = ($scope, $el, $attrs, $model) -> isInEditMode = -> - return $el.find('textarea').is(':visible') + return $el.find('textarea').is(':visible') and $model.$modelValue.id uploadFile = (file, type) -> diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index c727005f..379c312b 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Preview", "EDIT_BUTTON": "Edit", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Markdown syntax help" }, "PERMISIONS_CATEGORIES": { diff --git a/app/partials/common/components/wysiwyg.jade b/app/partials/common/components/wysiwyg.jade index 3ed85550..97a62681 100644 --- a/app/partials/common/components/wysiwyg.jade +++ b/app/partials/common/components/wysiwyg.jade @@ -1,6 +1,7 @@ mixin wysihelp div.wysiwyg-help - span.drag-drop-help(translate="COMMON.WYSIWYG.ATTACH_FILE_HELP") + span.drag-drop-help(ng-if="wiki.id", translate="COMMON.WYSIWYG.ATTACH_FILE_HELP") + span.drag-drop-help(ng-if="!wiki.id", translate="COMMON.WYSIWYG.ATTACH_FILE_HELP_SAVE_FIRST") a.help-markdown( href="https://tree.taiga.io/support/misc/taiga-markdown-syntax/" target="_blank" From 190d0287d14edc84b8dd4fc3d0fd23cb3515c3ca Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 13 May 2016 13:01:28 +0200 Subject: [PATCH 021/315] save comment when another attribe change --- app/coffee/modules/common.coffee | 8 ++++++++ e2e/helpers/detail-helper.js | 6 +++++- e2e/shared/detail.js | 16 ++++++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/app/coffee/modules/common.coffee b/app/coffee/modules/common.coffee index e6b47dbe..0113b34d 100644 --- a/app/coffee/modules/common.coffee +++ b/app/coffee/modules/common.coffee @@ -293,10 +293,18 @@ class QueueModelTransformation extends taiga.Service defered = @q.defer() @qqueue.add () => + obj = @.getObj() + comment = obj.comment + + obj.comment = '' + clone = @.clone() transformation(clone) + if comment.length + clone.comment = comment + success = () => @.scope[@.prop] = clone diff --git a/e2e/helpers/detail-helper.js b/e2e/helpers/detail-helper.js index 280a141d..bad59b39 100644 --- a/e2e/helpers/detail-helper.js +++ b/e2e/helpers/detail-helper.js @@ -158,11 +158,15 @@ helper.history = function() { }, addComment: async function(comment) { - el.$('textarea[tg-markitup]').sendKeys(comment); + obj.writeComment(comment); el.$('.save-comment').click(); await browser.waitForAngular(); }, + writeComment: function(comment) { + el.$('textarea[tg-markitup]').sendKeys(comment); + }, + countComments: async function() { let moreComments = el.$('.comments-list .show-more-comments'); let moreCommentsIsPresent = await moreComments.isPresent(); diff --git a/e2e/shared/detail.js b/e2e/shared/detail.js index fc59b856..ca61a479 100644 --- a/e2e/shared/detail.js +++ b/e2e/shared/detail.js @@ -209,14 +209,26 @@ shared.historyTesting = async function() { let newDeletedCommentsCounter = await historyHelper.countDeletedComments(); expect(newDeletedCommentsCounter).to.be.equal(deletedCommentsCounter+1); - //Restore last coment + //Restore last comment deletedCommentsCounter = await historyHelper.countDeletedComments(); await historyHelper.restoreLastComment(); newDeletedCommentsCounter = await historyHelper.countDeletedComments(); expect(newDeletedCommentsCounter).to.be.equal(deletedCommentsCounter-1); + //Store comment with a modification + commentsCounter = await historyHelper.countComments(); + + historyHelper.writeComment("New comment " + date); + let title = detailHelper.title(); + title.setTitle('changed'); + title.save(); + + newCommentsCounter = await historyHelper.countComments(); + + expect(newCommentsCounter).to.be.equal(commentsCounter+1); + //Check activity - historyHelper.selectActivityTab(); + await historyHelper.selectActivityTab(); let activitiesCounter = await historyHelper.countActivities(); From 148ca16184b4928fd8d9cf57258240bf8eee0336 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 17 May 2016 08:29:09 +0200 Subject: [PATCH 022/315] fix backlog points --- app/coffee/modules/backlog/main.coffee | 10 +++- app/coffee/modules/common/estimation.coffee | 63 +++++++++++++-------- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index 987cffd1..de1f7807 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -55,11 +55,12 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F "$tgAnalytics", "$translate", "$tgLoading", - "tgResources" + "tgResources", + "$tgQueueModelTransformation" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, - @location, @appMetaService, @navUrls, @events, @analytics, @translate, @loading, @rs2) -> + @location, @appMetaService, @navUrls, @events, @analytics, @translate, @loading, @rs2, @modelTransform) -> bindMethods(@) @.page = 1 @@ -958,7 +959,10 @@ UsPointsDirective = ($tgEstimationsService, $repo, $tgTemplate) -> if estimationProcess.isEditable bindClickElements() - estimationProcess.onSelectedPointForRole = (roleId, pointId) -> + estimationProcess.onSelectedPointForRole = (roleId, pointId, points) -> + us.points = points + estimationProcess.render() + @save(roleId, pointId).then -> $ctrl.loadProjectStats() diff --git a/app/coffee/modules/common/estimation.coffee b/app/coffee/modules/common/estimation.coffee index 0e274ca4..6cef0ef6 100644 --- a/app/coffee/modules/common/estimation.coffee +++ b/app/coffee/modules/common/estimation.coffee @@ -45,11 +45,13 @@ LbUsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $template, $scope.$watch $attrs.ngModel, (us) -> if us estimationProcess = $tgEstimationsService.create($el, us, $scope.project) - estimationProcess.onSelectedPointForRole = (roleId, pointId) -> + estimationProcess.onSelectedPointForRole = (roleId, pointId, points) -> + us.points = points + estimationProcess.render() + $scope.$apply -> $model.$setViewValue(us) - estimationProcess.render = () -> ctx = { totalPoints: @calculateTotalPoints() @@ -80,7 +82,7 @@ module.directive("tgLbUsEstimation", ["$tgEstimationsService", "$rootScope", "$t ## User story estimation directive ############################################################################# -UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $template, $compile) -> +UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $template, $compile, $modelTransform, $confirm) -> # Display the points of a US and you can edit it. # # Example: @@ -91,14 +93,25 @@ UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $template, $c # - scope.project object link = ($scope, $el, $attrs, $model) -> + save = (points) -> + transform = $modelTransform.save (us) => + us.points = points + + return us + + onError = => + $confirm.notify("error") + + return transform.then(null, onError) + $scope.$watchCollection () -> return $model.$modelValue && $model.$modelValue.points , () -> us = $model.$modelValue if us estimationProcess = $tgEstimationsService.create($el, us, $scope.project) - estimationProcess.onSelectedPointForRole = (roleId, pointId) -> - @save(roleId, pointId).then () -> + estimationProcess.onSelectedPointForRole = (roleId, pointId, points) -> + save(points).then () -> $rootScope.$broadcast("object:updated") estimationProcess.render = () -> @@ -125,14 +138,15 @@ UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $template, $c } module.directive("tgUsEstimation", ["$tgEstimationsService", "$rootScope", "$tgRepo", - "$tgTemplate", "$compile", UsEstimationDirective]) + "$tgTemplate", "$compile", "$tgQueueModelTransformation", + "$tgConfirm", UsEstimationDirective]) ############################################################################# ## Estimations service ############################################################################# -EstimationsService = ($template, $modelTransform, $repo, $confirm, $q) -> +EstimationsService = ($template, $repo, $confirm, $q, $qqueue) -> pointsTemplate = $template.get("common/estimation/us-estimation-points.html", true) class EstimationProcess @@ -146,23 +160,17 @@ EstimationsService = ($template, $modelTransform, $repo, $confirm, $q) -> save: (roleId, pointId) -> deferred = $q.defer() + $qqueue.add () => + onSuccess = => + deferred.resolve() - transform = $modelTransform.save (us) => - points = _.clone(@us.points, true) - points[roleId] = pointId + onError = => + $confirm.notify("error") + @us.revert() + @render() + deferred.reject() - us.points = points - - return us - - onSuccess = => - deferred.resolve() - - onError = => - $confirm.notify("error") - deferred.reject() - - transform.then(onSuccess, onError) + $repo.save(@us).then(onSuccess, onError) return deferred.promise @@ -206,7 +214,12 @@ EstimationsService = ($template, $modelTransform, $repo, $confirm, $q) -> roleId = target.data("role-id") pointId = target.data("point-id") @$el.find(".popover").popover().close() - @onSelectedPointForRole(roleId, pointId) + + + points = _.clone(@us.points, true) + points[roleId] = pointId + + @onSelectedPointForRole(roleId, pointId, points) renderPointsSelector: (roleId, target) -> points = _.map @points, (point) => @@ -252,5 +265,5 @@ EstimationsService = ($template, $modelTransform, $repo, $confirm, $q) -> create: create } -module.factory("$tgEstimationsService", ["$tgTemplate", "$tgQueueModelTransformation", "$tgRepo", "$tgConfirm", - "$q", EstimationsService]) +module.factory("$tgEstimationsService", ["$tgTemplate", "$tgRepo", "$tgConfirm", + "$q", "$tgQqueue", EstimationsService]) From 7031a80c888abd0a9c2f76af111325366fde29ab Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 17 May 2016 09:05:58 +0200 Subject: [PATCH 023/315] select browser in e2e --- conf.e2e.js | 13 +++++++++++++ run-e2e.js | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/conf.e2e.js b/conf.e2e.js index d61df045..b308fa06 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -158,4 +158,17 @@ if (argv.json) { config.mochaOpts.reporter = 'reporter-file'; } +if (argv.firefox) { + config.capabilities = { + browserName: 'firefox' + }; +} + +if (argv.ie) { + config.capabilities = { + browserName: 'internet explorer', + version: '11' + }; +} + exports.config = config; diff --git a/run-e2e.js b/run-e2e.js index f50e6a5f..298c7ae3 100644 --- a/run-e2e.js +++ b/run-e2e.js @@ -41,6 +41,14 @@ function launchProtractor(suit) { protractorParams.push('--json'); } + if (argv.ie) { + protractorParams.push('--ie'); + } + + if (argv.firefox) { + protractorParams.push('--firefox'); + } + child_process.spawnSync('protractor', protractorParams, {stdio: "inherit"}); } From 6a1a9e42ac6c1e439a699c62e88f7dcf7af4bdf0 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 17 May 2016 09:16:11 +0200 Subject: [PATCH 024/315] configurable seleniumAddress --- conf.e2e.js | 4 ++++ run-e2e.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/conf.e2e.js b/conf.e2e.js index b308fa06..eb0ad698 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -171,4 +171,8 @@ if (argv.ie) { }; } +if (argv.seleniumAddress) { + config.seleniumAddress = argv.seleniumAddress; +} + exports.config = config; diff --git a/run-e2e.js b/run-e2e.js index 298c7ae3..9b7bb856 100644 --- a/run-e2e.js +++ b/run-e2e.js @@ -49,6 +49,10 @@ function launchProtractor(suit) { protractorParams.push('--firefox'); } + if (argv.seleniumAddress) { + protractorParams.push('--seleniumAddress=' + seleniumAddress); + } + child_process.spawnSync('protractor', protractorParams, {stdio: "inherit"}); } From 48496ff82347f605480260d3a6d629adbe588d40 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 17 May 2016 14:33:43 +0200 Subject: [PATCH 025/315] fix seleniumAddress param --- run-e2e.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-e2e.js b/run-e2e.js index 9b7bb856..9a74844f 100644 --- a/run-e2e.js +++ b/run-e2e.js @@ -50,7 +50,7 @@ function launchProtractor(suit) { } if (argv.seleniumAddress) { - protractorParams.push('--seleniumAddress=' + seleniumAddress); + protractorParams.push('--seleniumAddress=' + argv.seleniumAddress); } child_process.spawnSync('protractor', protractorParams, {stdio: "inherit"}); From 9e0f411582bbde0ab43a19f9ccb6f3c9f9e2cad0 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 17 May 2016 15:14:02 +0200 Subject: [PATCH 026/315] report by browser --- conf.e2e.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/conf.e2e.js b/conf.e2e.js index eb0ad698..54d04d11 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -152,8 +152,16 @@ if (argv.json) { var suites = argv.suite.split(',').join('-'); + var reportFileName = 'report-' + suites + '-chrome.json'; + + if (argv.firefox) { + reportFileName = 'report-' + suites + '-firefox.json'; + } else if (argv.ie) { + reportFileName = 'report-' + suites + '-ie.json'; + } + process.env['MOCHA_REPORTER'] = 'JSON'; - process.env['MOCHA_REPORTER_FILE'] = 'e2e/reports/report-' + suites +'.json'; + process.env['MOCHA_REPORTER_FILE'] = 'e2e/reports/' + reportFileName; config.mochaOpts.reporter = 'reporter-file'; } From fece2e2737fc5b2b974e4d8aa790288dcd296474 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 18 May 2016 07:41:48 +0200 Subject: [PATCH 027/315] configurable e2e host --- conf.e2e.js | 5 +++++ run-e2e.js | 27 ++++++++++++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/conf.e2e.js b/conf.e2e.js index 54d04d11..a88f4a79 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -183,4 +183,9 @@ if (argv.seleniumAddress) { config.seleniumAddress = argv.seleniumAddress; } + +if (argv.host) { + config.params.glob.host = argv.host; +} + exports.config = config; diff --git a/run-e2e.js b/run-e2e.js index 9a74844f..77d8e48b 100644 --- a/run-e2e.js +++ b/run-e2e.js @@ -37,20 +37,21 @@ function backup() { function launchProtractor(suit) { let protractorParams = ['conf.e2e.js', '--suite=' + suit, '--back=' + taigaBackPath]; - if (argv.json) { - protractorParams.push('--json'); - } + var discard = [ + "_", + "s", + "a", + "b" + ]; - if (argv.ie) { - protractorParams.push('--ie'); - } - - if (argv.firefox) { - protractorParams.push('--firefox'); - } - - if (argv.seleniumAddress) { - protractorParams.push('--seleniumAddress=' + argv.seleniumAddress); + for(var arg in argv) { + if (discard.indexOf(arg) === -1) { + if(typeof argv[arg] === 'boolean') { + protractorParams.push('--' + arg); + } else { + protractorParams.push('--' + arg + "=" + argv[arg]); + } + } } child_process.spawnSync('protractor', protractorParams, {stdio: "inherit"}); From fd189552f8bc789fd30b4a734c0e47fc6198204d Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 19 May 2016 08:11:21 +0200 Subject: [PATCH 028/315] upgrade dependencies --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b904bb30..4b3e17c0 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "gulp-filter": "^4.0.0", "gulp-flatten": "0.2.0", "gulp-if": "^2.0.0", - "gulp-imagemin": "^2.2.1", + "gulp-imagemin": "^3.0.1", "gulp-insert": "^0.5.0", "gulp-jade": "^1.0.0", "gulp-jade-inheritance": "0.5.5", @@ -65,7 +65,7 @@ "gulp-rename": "^1.2.0", "gulp-replace": "^0.5.3", "gulp-sass": "^2.3.1", - "gulp-scss-lint": "0.3.9", + "gulp-scss-lint": "0.4.0", "gulp-size": "^2.0.0", "gulp-sourcemaps": "^2.0.0-alpha", "gulp-template": "^4.0.0", @@ -78,13 +78,13 @@ "karma": "^0.13.10", "karma-chai-plugins": "^0.7.0", "karma-chrome-launcher": "^1.0.1", - "karma-coffee-preprocessor": "^0.3.0", + "karma-coffee-preprocessor": "^1.0.0", "karma-mocha": "^1.0.1", "karma-sourcemap-loader": "^0.3.4", "merge-stream": "^1.0.0", "minimist": "^1.1.1", "mocha": "^2.2.4", - "node-sass": "3.6.0", + "node-sass": "3.7.0", "node-uuid": "^1.4.3", "photoswipe": "^4.1.0", "pre-commit": "^1.0.5", From e042449ca08fde63603021762f4edd5853b4076e Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 19 May 2016 08:11:36 +0200 Subject: [PATCH 029/315] ignore mixin files --- app/modules/components/attachments-simple/attachment-simple.scss | 0 gulpfile.js | 1 + 2 files changed, 1 insertion(+) delete mode 100644 app/modules/components/attachments-simple/attachment-simple.scss diff --git a/app/modules/components/attachments-simple/attachment-simple.scss b/app/modules/components/attachments-simple/attachment-simple.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/gulpfile.js b/gulpfile.js index 350a09a4..0f1fab49 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -78,6 +78,7 @@ paths.modulesLocales = paths.app + "modules/**/locales/*.json"; paths.sass = [ paths.app + "**/*.scss", + "!" + paths.app + "**/*.mixin.scss", "!" + paths.app + "styles/bourbon/**/*.scss", "!" + paths.app + "styles/dependencies/**/*.scss", "!" + paths.app + "styles/extras/**/*.scss", From 563d69f91f8a89fd75310255badde6342212cfc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 25 Nov 2015 16:31:45 +0100 Subject: [PATCH 030/315] Tribe integration in taiga --- CHANGELOG.md | 1 + app-loader/app-loader.coffee | 1 + app/coffee/modules/userstories/detail.coffee | 18 ++- app/images/monster-fight.png | Bin 0 -> 35983 bytes app/images/tribe-logo.png | Bin 0 -> 6212 bytes app/locales/taiga/locale-en.json | 11 ++ .../tribe-button.directive.coffee | 37 ++++++ .../components/tribe-button/tribe-button.jade | 10 ++ .../tribe-linked.directive.coffee | 48 +++++++ .../components/tribe-button/tribe-linked.jade | 33 +++++ .../components/tribe-button/tribe-linked.scss | 123 ++++++++++++++++++ app/modules/external-apps/external-app.scss | 7 +- app/partials/us/us-detail.jade | 16 +++ app/styles/components/buttons.scss | 25 ++++ app/themes/high-contrast/variables.scss | 4 +- app/themes/material-design/variables.scss | 4 +- app/themes/taiga/variables.scss | 4 +- conf/conf.example.json | 3 +- 18 files changed, 334 insertions(+), 11 deletions(-) create mode 100644 app/images/monster-fight.png create mode 100644 app/images/tribe-logo.png create mode 100644 app/modules/components/tribe-button/tribe-button.directive.coffee create mode 100644 app/modules/components/tribe-button/tribe-button.jade create mode 100644 app/modules/components/tribe-button/tribe-linked.directive.coffee create mode 100644 app/modules/components/tribe-button/tribe-linked.jade create mode 100644 app/modules/components/tribe-button/tribe-linked.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index ad2e8f5b..1f05dcd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - Show a confirmation notice when you exit edit mode by pressing ESC in the markdown inputs. +- Add the tribe button to link stories from tree.taiga.io with gigs in tribe.taiga.io. ### Misc - Lots of small and not so small bugfixes. diff --git a/app-loader/app-loader.coffee b/app-loader/app-loader.coffee index 6cbe2f03..cee8fe74 100644 --- a/app-loader/app-loader.coffee +++ b/app-loader/app-loader.coffee @@ -3,6 +3,7 @@ window._version = "___VERSION___" window.taigaConfig = { "api": "http://localhost:8000/api/v1/", "eventsUrl": null, + "tribeHost": null, "eventsMaxMissedHeartbeats": 5, "eventsHeartbeatIntervalTime": 60000, "debug": true, diff --git a/app/coffee/modules/userstories/detail.coffee b/app/coffee/modules/userstories/detail.coffee index 8836c8e7..db09821d 100644 --- a/app/coffee/modules/userstories/detail.coffee +++ b/app/coffee/modules/userstories/detail.coffee @@ -50,15 +50,18 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin) "$tgNavUrls", "$tgAnalytics", "$translate", + "$tgConfig", "$tgQueueModelTransformation" ] - constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, - @log, @appMetaService, @navUrls, @analytics, @translate, @modelTransform) -> + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @log, @appMetaService, + @navUrls, @analytics, @translate, @configService, @modelTransform) -> bindMethods(@) @scope.usRef = @params.usref @scope.sectionName = @translate.instant("US.SECTION_NAME") + @scope.tribeEnabled = @configService.config.tribeHost + @.initializeEventHandlers() promise = @.loadInitialData() @@ -239,6 +242,17 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin) return @rs.userstories.unwatch(@scope.usId).then(onSuccess, onError) + onTribeInfo: -> + publishTitle = @translate.instant("US.TRIBE.PUBLISH_MORE_INFO_TITLE") + image = $('') + .attr({ + 'src': "/#{window._version}/images/monster-fight.png", + 'alt': @translate.instant("US.TRIBE.PUBLISH_MORE_INFO_TITLE") + }) + text = @translate.instant("US.TRIBE.PUBLISH_MORE_INFO_TEXT") + publishDesc = $('
').append(image).append(text) + @confirm.success(publishTitle, publishDesc) + module.controller("UserStoryDetailController", UserStoryDetailController) diff --git a/app/images/monster-fight.png b/app/images/monster-fight.png new file mode 100644 index 0000000000000000000000000000000000000000..9dd83f364d48dcf5f145e53231229028bdef31d7 GIT binary patch literal 35983 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 literal 0 HcmV?d00001 diff --git a/app/images/tribe-logo.png b/app/images/tribe-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5b4643c5b4b07f98a5d650d4fa5a8839aa8b7baf GIT binary patch literal 6212 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 you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Team Requirement", "CLIENT_REQUIREMENT": "Client Requirement", diff --git a/app/modules/components/tribe-button/tribe-button.directive.coffee b/app/modules/components/tribe-button/tribe-button.directive.coffee new file mode 100644 index 00000000..4f961f06 --- /dev/null +++ b/app/modules/components/tribe-button/tribe-button.directive.coffee @@ -0,0 +1,37 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: tribe-button.directive.coffee +### + +TribeButtonDirective = (configService) -> + link = (scope, el, attrs) -> + + scope.vm = {} + scope.vm.tribeHost = configService.config.tribeHost + + return { + scope: {usId: "=", projectSlug: "="} + controllerAs: "vm", + templateUrl: "components/tribe-button/tribe-button.html", + link: link + } + +TribeButtonDirective.$inject = [ + "$tgConfig" +] + +angular.module("taigaComponents").directive("tgTribeButton", TribeButtonDirective) diff --git a/app/modules/components/tribe-button/tribe-button.jade b/app/modules/components/tribe-button/tribe-button.jade new file mode 100644 index 00000000..1d1cad90 --- /dev/null +++ b/app/modules/components/tribe-button/tribe-button.jade @@ -0,0 +1,10 @@ +a.button-tribe( + ng-href="{{::vm.tribeHost}}/gigs/import-from-taiga?project={{projectSlug}}&us={{usId}}", + title="{{ 'US.TRIBE.PUBLISH' | translate }}" + target="_blank" +) + img.tribe-logo( + src="/#{v}/images/tribe-logo.png" + alt="{{ 'US.TRIBE.PUBLISH' | translate }}" + ) + span {{ 'US.TRIBE.PUBLISH' | translate }} diff --git a/app/modules/components/tribe-button/tribe-linked.directive.coffee b/app/modules/components/tribe-button/tribe-linked.directive.coffee new file mode 100644 index 00000000..0c062aaf --- /dev/null +++ b/app/modules/components/tribe-button/tribe-linked.directive.coffee @@ -0,0 +1,48 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: tribe-linked.directive.coffee +### + +TribeLinkedDirective = (configService) -> + link = (scope, el, attrs) -> + + scope.vm = {} + + scope.vm.tribeHost = configService.config.tribeHost + + scope.vm.show = () -> + scope.vm.open = true + + scope.vm.hide = (event) -> + scope.vm.open = false + + directive = { + templateUrl: "components/tribe-button/tribe-linked.html", + scope: { + gigTitle: "=", + gigId: "=" + }, + link: link + } + + return directive + +TribeLinkedDirective.$inject = [ + "$tgConfig" +] + +angular.module("taigaComponents").directive("tgTribeLinked", TribeLinkedDirective) diff --git a/app/modules/components/tribe-button/tribe-linked.jade b/app/modules/components/tribe-button/tribe-linked.jade new file mode 100644 index 00000000..6be83206 --- /dev/null +++ b/app/modules/components/tribe-button/tribe-linked.jade @@ -0,0 +1,33 @@ +.tribe-linked.js-tribe-linked(ng-class="{'is-active': vm.open, 'is-inactive': vm.open == false}") + .tribe-linked-inner + .tribe-linked-header + img.tribe-logo( + ng-click="vm.show()" + alt="" + title="{{ 'US.TRIBE.PUBLISHED_AS_GIG' | translate }}" + src="/#{v}/images/tribe-logo.png" + ) + p.title {{ "US.TRIBE.PUBLISHED_AS_GIG" | translate }} + a.close( + ng-click="vm.hide()" + href="" + title="{{ 'US.TRIBE.CLOSE' | translate }}" + ) + svg.icon.icon-remove + use(xlink:href="#icon-remove") + + a.gig-title( + href="{{::vm.tribeHost}}/gigs/{{gigId}}" + title="gigTitle" + target="_blank" + ) {{gigTitle}} + + a.delete-link( + href="{{::vm.tribeHost}}/gigs/{{gigId}}/link-with-taiga?from=taiga" + title="{{ 'US.TRIBE.EDIT_LINK' | translate }}" + ) {{ 'US.TRIBE.EDIT_LINK' | translate }} + + a.synchronize-link.button-tribe( + ng-href="{{::vm.tribeHost}}/gigs/sync/{{gigId}}?from=taiga" + title="{{ 'US.TRIBE.SINCHRONIZE_LINK' }}" + ) {{ 'US.TRIBE.SYNCHRONIZE_LINK' | translate }} diff --git a/app/modules/components/tribe-button/tribe-linked.scss b/app/modules/components/tribe-button/tribe-linked.scss new file mode 100644 index 00000000..0da433ef --- /dev/null +++ b/app/modules/components/tribe-button/tribe-linked.scss @@ -0,0 +1,123 @@ +.tribe-linked { + margin-left: auto; + overflow: hidden; + position: absolute; + right: 0; + top: 0; + z-index: 99; + .tribe-linked-inner { + padding: .5rem; + transition: .2s; + &:hover { + background: $white; + cursor: pointer; + } + .title, + .gig-title, + .delete-link, + .synchronize-link, + .close { + display: none; + opacity: 0; + } + } + .tribe-logo { + height: 2rem; + width: 2rem; + } + &.is-active { + animation-duration: 1s; + animation-name: slideTribeInner; + background: $white; + box-shadow: 1px 1px 5px rgba($grayer, .2); + overflow: hidden; + .tribe-linked-inner { + height: 100%; + min-width: 300px; + .title, + .gig-title, + .delete-link, + .synchronize-link, + .close { + animation-duration: 1.25s; + animation-name: fadeInFromNone; + display: block; + opacity: 1; + } + } + .tribe-linked-header { + align-items: center; + display: flex; + margin-bottom: 1rem; + } + .tribe-logo { + margin-right: .5rem; + vertical-align: text-bottom; + } + svg { + fill: $red-light; + height: 1.5rem; + max-height: 1.5rem; + max-width: 1.5rem; + transition: all .2s; + width: 1.5rem; + &:hover { + fill: $red; + } + } + .title { + margin-bottom: 0; + } + .gig-title { + @include font-type(light); + color: $tribe-primary; + margin-bottom: .5rem; + } + .delete-link { + @include font-type(light); + @include font-size(small); + color: $primary; + display: block; + margin-bottom: 1rem; + } + .synchronize-link { + display: block; + padding: .5rem; + } + .close { + align-self: flex-start; + margin-left: 1rem; + } + } +} + +@keyframes slideTribeInner { + 0% { + max-height: 60px; + width: 100px; + } + 20% { + max-height: 60px; + width: 300px; + } + 100% { + max-height: 225px; + } +} + +@keyframes fadeInFromNone { + 0% { + display: none; + opacity: 0; + } + + 80% { + display: block; + opacity: 0; + } + + 100% { + display: block; + opacity: 1; + } +} diff --git a/app/modules/external-apps/external-app.scss b/app/modules/external-apps/external-app.scss index 17f5ff08..cc00b2b3 100644 --- a/app/modules/external-apps/external-app.scss +++ b/app/modules/external-apps/external-app.scss @@ -3,9 +3,12 @@ text-align: center; width: 480px; .logo { - height: 6rem; + height: 4rem; margin: 0 auto; - width: 6rem; + width: 4rem; + } + svg { + @include svg-size(4rem); } h1 { margin-bottom: 0; diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index 20a72a44..0896a232 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -107,6 +107,12 @@ div.wrapper( ng-model="us" ) + tg-tribe-linked( + ng-if="tribeEnabled && us.tribe_gig", + gig-title="us.tribe_gig.title" + gig-id="us.tribe_gig.id" + ) + tg-us-estimation.ticket-estimation(ng-model="us") section.ticket-assigned-to( @@ -129,6 +135,16 @@ div.wrapper( required-perm="modify_us" ) + section.ticket-to-tribe(ng-if="tribeEnabled && !us.tribe_gig") + tg-tribe-button( + us-id="us.id" + project-slug="project.slug" + ) + a.tribe-more-info( + href="" + title="{{'US.TRIBE.PUBLISH_TITLE' | translate}}" + ng-click="ctrl.onTribeInfo()" + ) {{'US.TRIBE.PUBLISH_INFO' | translate}} section.ticket-detail-settings tg-us-team-requirement-button(ng-model="us") diff --git a/app/styles/components/buttons.scss b/app/styles/components/buttons.scss index 99795199..f6f95f46 100755 --- a/app/styles/components/buttons.scss +++ b/app/styles/components/buttons.scss @@ -130,3 +130,28 @@ a.button-gray { background: $black; } } + +.button-tribe { + @extend %button; + align-items: center; + background: $tribe-primary; + display: flex; + padding: .4rem; + padding-left: 1.5rem; + &:hover, + &.active { + background: $tribe-secondary; + color: $white; + } + .tribe-logo { + margin-right: .5rem; + width: 1.5rem; + } +} + +.tribe-more-info { + @include font-size(small); + color: $primary; + display: inline-block; + margin-top: .5rem; +} diff --git a/app/themes/high-contrast/variables.scss b/app/themes/high-contrast/variables.scss index 861556da..cd5ef6e6 100755 --- a/app/themes/high-contrast/variables.scss +++ b/app/themes/high-contrast/variables.scss @@ -31,8 +31,8 @@ $red-amaranth: #e91e63; $purple-eggplant: #9c27b0; $yellow-pear: #ffc107; -$tribe-primary: #98e0eb; -$tribe-secondary: #107a8a; +$tribe-primary: #107a8a; +$tribe-secondary: darken($tribe-primary, 10%); $top-icon-color: $white; $dropdown-color: rgba(darken($primary-dark, 20%), 1); diff --git a/app/themes/material-design/variables.scss b/app/themes/material-design/variables.scss index cf662a5d..c76c2e0b 100755 --- a/app/themes/material-design/variables.scss +++ b/app/themes/material-design/variables.scss @@ -31,8 +31,8 @@ $red-amaranth: #e91e63; $purple-eggplant: #9c27b0; $yellow-pear: #ffc107; -$tribe-primary: #98e0eb; -$tribe-secondary: #107a8a; +$tribe-primary: #107a8a; +$tribe-secondary: darken($tribe-primary, 10%); $top-icon-color: $white; $dropdown-color: rgba(darken($primary-dark, 20%), 1); diff --git a/app/themes/taiga/variables.scss b/app/themes/taiga/variables.scss index 3eaa69df..e8d00c10 100755 --- a/app/themes/taiga/variables.scss +++ b/app/themes/taiga/variables.scss @@ -31,8 +31,8 @@ $red-amaranth: #e43050; $purple-eggplant: #810061; $yellow-pear: #bbe831; -$tribe-primary: #98e0eb; -$tribe-secondary: #107a8a; +$tribe-primary: #107a8a; +$tribe-secondary: darken($tribe-primary, 10%); $top-icon-color: #11241f; $dropdown-color: rgba(darken($grayer, 20%), 1); diff --git a/conf/conf.example.json b/conf/conf.example.json index 1779eec1..7868fbf1 100644 --- a/conf/conf.example.json +++ b/conf/conf.example.json @@ -13,5 +13,6 @@ "privacyPolicyUrl": null, "termsOfServiceUrl": null, "maxUploadFileSize": null, - "contribPlugins": [] + "contribPlugins": [], + "tribeHost": null } From f9490829af6a7b93197cef73eabdbcf3db3b36d0 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 23 May 2016 08:31:51 +0200 Subject: [PATCH 031/315] prevent drop in userstory in taskboard --- app/coffee/modules/taskboard/sortable.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/coffee/modules/taskboard/sortable.coffee b/app/coffee/modules/taskboard/sortable.coffee index c29875ef..8c5d7ee8 100644 --- a/app/coffee/modules/taskboard/sortable.coffee +++ b/app/coffee/modules/taskboard/sortable.coffee @@ -62,6 +62,7 @@ TaskboardSortableDirective = ($repo, $rs, $rootscope) -> copySortSource: false, copy: false, mirrorContainer: $el[0], + accepts: (el, target) -> return !$(target).hasClass('taskboard-userstory-box') moves: (item) -> return $(item).hasClass('taskboard-task') }) From 2c03ca70c2aa2671bf3e911254570916e0e8f6ed Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 23 May 2016 11:02:47 +0200 Subject: [PATCH 032/315] prevent template injection --- .../user-timeline-item-title.service.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee index 406b57eb..b19769ed 100644 --- a/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee @@ -130,10 +130,14 @@ class UserTimelineItemTitle _getLink: (url, text, title) -> title = title || text + span = $('') + .attr('ng-non-bindable', true) + .text(text) + return $('') .attr('tg-nav', url) - .text(text) .attr('title', title) + .append(span) .prop('outerHTML') _getUsernameSpan: (text) -> From 28827419d576fe0065e90088d2ad5305ad59d455 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 23 May 2016 12:54:28 +0200 Subject: [PATCH 033/315] fix wiki nav permissions --- 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 09b4e16d..40c69277 100644 --- a/app/coffee/modules/wiki/main.coffee +++ b/app/coffee/modules/wiki/main.coffee @@ -134,7 +134,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin) @q.all([@.loadWikiLinks(), @.loadWiki()]).then @.checkLinksPerms.bind(this) checkLinksPerms: -> - if @scope.project.my_permissions.indexOf("modify_wiki_link") != -1 || + if @scope.project.my_permissions.indexOf("add_wiki_link") != -1 || (@scope.project.my_permissions.indexOf("view_wiki_links") != -1 && @scope.wikiLinks.length) @scope.linksVisible = true From 8457f943235fce78a37f712e81f7c568465c5ccb Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 24 May 2016 09:22:06 +0200 Subject: [PATCH 034/315] fix unit tests warning --- .../attachments-full.controller.spec.coffee | 2 +- .../attachments-full/attachments-full.service.coffee | 2 +- .../external-apps/external-app.controller.spec.coffee | 8 +++----- app/modules/profile/profile.controller.spec.coffee | 8 +++----- .../like-project-button.controller.spec.coffee | 4 ++-- .../watch-project-button.controller.spec.coffee | 4 ++-- .../transfer/transfer-project.controller.spec.coffee | 8 ++++---- app/modules/services/current-user.service.spec.coffee | 2 +- 8 files changed, 17 insertions(+), 21 deletions(-) diff --git a/app/modules/components/attachments-full/attachments-full.controller.spec.coffee b/app/modules/components/attachments-full/attachments-full.controller.spec.coffee index 6c623174..2c94948d 100644 --- a/app/modules/components/attachments-full/attachments-full.controller.spec.coffee +++ b/app/modules/components/attachments-full/attachments-full.controller.spec.coffee @@ -158,7 +158,7 @@ describe "AttachmentsController", -> deleteFile = Immutable.Map() mocks.attachmentsFullService.deleteAttachment = sinon.stub() - mocks.attachmentsFullService.deleteAttachment.withArgs(deleteFile, 'us').promise().reject() + mocks.attachmentsFullService.deleteAttachment.withArgs(deleteFile, 'us').promise().reject(new Error('error')) askResponse = { finish: sinon.spy() diff --git a/app/modules/components/attachments-full/attachments-full.service.coffee b/app/modules/components/attachments-full/attachments-full.service.coffee index 8f9a83a3..9e1fd874 100644 --- a/app/modules/components/attachments-full/attachments-full.service.coffee +++ b/app/modules/components/attachments-full/attachments-full.service.coffee @@ -73,7 +73,7 @@ class AttachmentsFullService extends taiga.Service resolve(attachment) else - reject(file) + reject(new Error(file)) loadAttachments: (type, objId, projectId)-> @attachmentsService.list(type, objId, projectId).then (files) => diff --git a/app/modules/external-apps/external-app.controller.spec.coffee b/app/modules/external-apps/external-app.controller.spec.coffee index f6db0c86..e19c5b89 100644 --- a/app/modules/external-apps/external-app.controller.spec.coffee +++ b/app/modules/external-apps/external-app.controller.spec.coffee @@ -104,17 +104,15 @@ describe "ExternalAppController", -> mocks.routeParams.application = 6 mocks.routeParams.state = "testing-state" - xhr = { - status: 404 - } + error = new Error('404') - mocks.tgExternalAppsService.getApplicationToken.withArgs(mocks.routeParams.application, mocks.routeParams.state).promise().reject(xhr) + mocks.tgExternalAppsService.getApplicationToken.withArgs(mocks.routeParams.application, mocks.routeParams.state).promise().reject(error) ctrl = $controller("ExternalApp") setTimeout ( -> expect(mocks.tgLoader.start.withArgs(false)).to.be.calledOnce - expect(mocks.tgXhrErrorService.response.withArgs(xhr)).to.be.calledOnce + expect(mocks.tgXhrErrorService.response.withArgs(error)).to.be.calledOnce done() ) diff --git a/app/modules/profile/profile.controller.spec.coffee b/app/modules/profile/profile.controller.spec.coffee index df37298b..c306682a 100644 --- a/app/modules/profile/profile.controller.spec.coffee +++ b/app/modules/profile/profile.controller.spec.coffee @@ -126,16 +126,14 @@ describe "ProfileController", -> mocks.routeParams.slug = "user-slug" - xhr = { - status: 404 - } + error = new Error('404') - mocks.userService.getUserByUserName.withArgs(mocks.routeParams.slug).promise().reject(xhr) + mocks.userService.getUserByUserName.withArgs(mocks.routeParams.slug).promise().reject(error) ctrl = $controller("Profile") setTimeout ( -> - expect(mocks.xhrErrorService.response.withArgs(xhr)).to.be.calledOnce + expect(mocks.xhrErrorService.response.withArgs(error)).to.be.calledOnce done() ) diff --git a/app/modules/projects/components/like-project-button/like-project-button.controller.spec.coffee b/app/modules/projects/components/like-project-button/like-project-button.controller.spec.coffee index 4669fd9b..8a020485 100644 --- a/app/modules/projects/components/like-project-button/like-project-button.controller.spec.coffee +++ b/app/modules/projects/components/like-project-button/like-project-button.controller.spec.coffee @@ -91,7 +91,7 @@ describe "LikeProjectButton", -> ctrl = $controller("LikeProjectButton") ctrl.project = project - mocks.tgLikeProjectButton.like.withArgs(project.get('id')).promise().reject() + mocks.tgLikeProjectButton.like.withArgs(project.get('id')).promise().reject(new Error('error')) ctrl.toggleLike().finally () -> expect(mocks.tgConfirm.notify.withArgs("error")).to.be.calledOnce @@ -127,7 +127,7 @@ describe "LikeProjectButton", -> ctrl = $controller("LikeProjectButton") ctrl.project = project - mocks.tgLikeProjectButton.unlike.withArgs(project.get('id')).promise().reject() + mocks.tgLikeProjectButton.unlike.withArgs(project.get('id')).promise().reject(new Error('error')) ctrl.toggleLike().finally () -> expect(mocks.tgConfirm.notify.withArgs("error")).to.be.calledOnce diff --git a/app/modules/projects/components/watch-project-button/watch-project-button.controller.spec.coffee b/app/modules/projects/components/watch-project-button/watch-project-button.controller.spec.coffee index c58e4d0e..1d6ce2fe 100644 --- a/app/modules/projects/components/watch-project-button/watch-project-button.controller.spec.coffee +++ b/app/modules/projects/components/watch-project-button/watch-project-button.controller.spec.coffee @@ -118,7 +118,7 @@ describe "WatchProjectButton", -> ctrl.project = project ctrl.showWatchOptions = true - mocks.tgWatchProjectButton.watch.withArgs(project.get('id'), notifyLevel).promise().reject() + mocks.tgWatchProjectButton.watch.withArgs(project.get('id'), notifyLevel).promise().reject(new Error('error')) ctrl.watch(notifyLevel).finally () -> expect(mocks.tgConfirm.notify.withArgs("error")).to.be.calledOnce @@ -159,7 +159,7 @@ describe "WatchProjectButton", -> ctrl.project = project ctrl.showWatchOptions = true - mocks.tgWatchProjectButton.unwatch.withArgs(project.get('id')).promise().reject() + mocks.tgWatchProjectButton.unwatch.withArgs(project.get('id')).promise().reject(new Error('error')) ctrl.unwatch().finally () -> expect(mocks.tgConfirm.notify.withArgs("error")).to.be.calledOnce diff --git a/app/modules/projects/transfer/transfer-project.controller.spec.coffee b/app/modules/projects/transfer/transfer-project.controller.spec.coffee index 6d6af88c..812b67b8 100644 --- a/app/modules/projects/transfer/transfer-project.controller.spec.coffee +++ b/app/modules/projects/transfer/transfer-project.controller.spec.coffee @@ -113,7 +113,7 @@ describe "TransferProject", -> mocks.auth.refresh.promise().resolve() mocks.routeParams.token = "BAD_TOKEN" mocks.currentUserService.getUser.returns(user) - mocks.projectsService.transferValidateToken.withArgs(1, "BAD_TOKEN").promise().reject() + mocks.projectsService.transferValidateToken.withArgs(1, "BAD_TOKEN").promise().reject(new Error('error')) mocks.tgNavUrls.resolve.withArgs("not-found").returns("/not-found") ctrl = $controller("TransferProjectController") @@ -247,7 +247,7 @@ describe "TransferProject", -> expect(mocks.location.path).to.be.calledWith("/project/slug/") expect(mocks.tgConfirm.notify).to.be.calledWith("success", "ACCEPTED_PROJECT_OWNERNSHIP", '', 5000) - done() + done() it "transfer reject", (done) -> project = Immutable.fromJS({ @@ -262,7 +262,7 @@ describe "TransferProject", -> mocks.currentUserService.getUser.returns(user) mocks.projectsService.transferValidateToken.withArgs(1, "TOKEN").promise().resolve() mocks.projectsService.transferReject.withArgs(1, "TOKEN", "this is my reason").promise().resolve() - mocks.tgNavUrls.resolve.withArgs("project-admin-project-profile-details", {project: "slug"}).returns("/project/slug/") + mocks.tgNavUrls.resolve.withArgs("home", {project: "slug"}).returns("/project/slug/") mocks.translate.instant.withArgs("ADMIN.PROJECT_TRANSFER.REJECTED_PROJECT_OWNERNSHIP").returns("REJECTED_PROJECT_OWNERNSHIP") ctrl = $controller("TransferProjectController") @@ -272,4 +272,4 @@ describe "TransferProject", -> expect(mocks.location.path).to.be.calledWith("/project/slug/") expect(mocks.tgConfirm.notify).to.be.calledWith("success", "REJECTED_PROJECT_OWNERNSHIP", '', 5000) - done() + done() diff --git a/app/modules/services/current-user.service.spec.coffee b/app/modules/services/current-user.service.spec.coffee index fba99a7f..90c08398 100644 --- a/app/modules/services/current-user.service.spec.coffee +++ b/app/modules/services/current-user.service.spec.coffee @@ -190,7 +190,7 @@ describe "tgCurrentUserService", -> done() it "create default joyride config", (done) -> - mocks.resources.user.getUserStorage.withArgs('joyride').promise().reject() + mocks.resources.user.getUserStorage.withArgs('joyride').promise().reject(new Error('error')) currentUserService.loadJoyRideConfig().then (config) -> joyride = { From 6687e2e3117a7dcaea6cb898123314b5660aa304 Mon Sep 17 00:00:00 2001 From: Iago Last Date: Fri, 20 May 2016 09:06:02 +0200 Subject: [PATCH 035/315] Add word-break rule in .kanban-task This closes #1014 --- app/styles/components/kanban-task.scss | 1 + app/styles/components/taskboard-task.scss | 1 + 2 files changed, 2 insertions(+) diff --git a/app/styles/components/kanban-task.scss b/app/styles/components/kanban-task.scss index 121bcfb2..0b938f02 100644 --- a/app/styles/components/kanban-task.scss +++ b/app/styles/components/kanban-task.scss @@ -88,6 +88,7 @@ } .task-name { @include font-type(bold); + word-break: break-word; } .loading { bottom: .5rem; diff --git a/app/styles/components/taskboard-task.scss b/app/styles/components/taskboard-task.scss index 7a9e6516..3b60133e 100644 --- a/app/styles/components/taskboard-task.scss +++ b/app/styles/components/taskboard-task.scss @@ -100,6 +100,7 @@ } .task-name { @include font-type(bold); + word-break: break-word; } .taskboard-text { @include font-size(small); From d45ba022688fe457df6d0078dd06c836c7d5dc0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 23 May 2016 09:29:35 +0200 Subject: [PATCH 036/315] Add Iago Last to AUTHORS --- AUTHORS.rst | 1 + app/styles/components/kanban-task.scss | 1 - app/styles/components/taskboard-task.scss | 1 - app/styles/shame/shame.scss | 13 +++++++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 6b5963bf..120d1e5b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -31,3 +31,4 @@ answer newbie questions, and generally made Taiga that much better: - Ryan Swanstrom - Vlad Topala - Wil Wade +- Iago Last diff --git a/app/styles/components/kanban-task.scss b/app/styles/components/kanban-task.scss index 0b938f02..121bcfb2 100644 --- a/app/styles/components/kanban-task.scss +++ b/app/styles/components/kanban-task.scss @@ -88,7 +88,6 @@ } .task-name { @include font-type(bold); - word-break: break-word; } .loading { bottom: .5rem; diff --git a/app/styles/components/taskboard-task.scss b/app/styles/components/taskboard-task.scss index 3b60133e..7a9e6516 100644 --- a/app/styles/components/taskboard-task.scss +++ b/app/styles/components/taskboard-task.scss @@ -100,7 +100,6 @@ } .task-name { @include font-type(bold); - word-break: break-word; } .taskboard-text { @include font-size(small); diff --git a/app/styles/shame/shame.scss b/app/styles/shame/shame.scss index 35b86fc0..bdd3b817 100644 --- a/app/styles/shame/shame.scss +++ b/app/styles/shame/shame.scss @@ -26,3 +26,16 @@ svg { a[ng-click] svg { pointer-events: auto; } + +// chrome url break +.kanban-task { + .task-name { + word-break: break-word; + } +} + +.taskboard-task { + .task-name { + word-break: break-word; + } +} \ No newline at end of file From 1d0f7c7ba787373819c9b45f0a91f773e146a798 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 25 May 2016 08:36:23 +0200 Subject: [PATCH 037/315] Issue 4239: Deprecated attachments are not checked as deprecated --- app/modules/components/attachment/attachment.jade | 1 + 1 file changed, 1 insertion(+) diff --git a/app/modules/components/attachment/attachment.jade b/app/modules/components/attachment/attachment.jade index 8f55672d..ec3b885b 100644 --- a/app/modules/components/attachment/attachment.jade +++ b/app/modules/components/attachment/attachment.jade @@ -37,6 +37,7 @@ form.single-attachment( input( type="checkbox" ng-model="vm.form.is_deprecated" + ng-checked="vm.attachment.getIn(['file', 'is_deprecated'])" name="is-deprecated" id="attach-{{::vm.attachment.getIn(['file', 'id'])}}-is-deprecated" ) From 623129dedc8fe202a16db61759ef49b6908c0a6b Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 25 May 2016 15:08:20 +0200 Subject: [PATCH 038/315] autofocus directive --- app/coffee/modules/common.coffee | 9 +++++++++ app/partials/task/related-task-create-form.jade | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/common.coffee b/app/coffee/modules/common.coffee index 0113b34d..d7c660cf 100644 --- a/app/coffee/modules/common.coffee +++ b/app/coffee/modules/common.coffee @@ -386,3 +386,12 @@ Svg = () -> } module.directive("tgSvg", [Svg]) + +Autofocus = ($timeout) -> + return { + restrict: 'A', + link : ($scope, $element) -> + $timeout -> $element[0].focus() + } + +module.directive('tgAutofocus', ['$timeout', Autofocus]) diff --git a/app/partials/task/related-task-create-form.jade b/app/partials/task/related-task-create-form.jade index 3110a132..69cce1d0 100644 --- a/app/partials/task/related-task-create-form.jade +++ b/app/partials/task/related-task-create-form.jade @@ -1,6 +1,6 @@ .row.single-related-task.related-task-create-form.active(ng-if="openNewRelatedTask") .task-name - input(type='text', autofocus, placeholder="{{'TASK.PLACEHOLDER_SUBJECT' | translate}}") + input(type='text', tg-autofocus, placeholder="{{'TASK.PLACEHOLDER_SUBJECT' | translate}}") .task-settings a.save-task(ng-click="save()" title="{{'COMMON.SAVE' | translate}}") tg-svg(svg-icon="icon-save") From b69622cd5b34f779e34ba31e68dde024553ce672 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 13 May 2016 11:51:52 +0200 Subject: [PATCH 039/315] Don't redirect on errors - not-found - error - permissions - blocked --- CHANGELOG.md | 1 + app/coffee/app.coffee | 25 +++++----- app/coffee/classes.coffee | 6 +-- app/coffee/modules/admin/memberships.coffee | 9 ++-- .../modules/admin/project-profile.coffee | 7 +-- .../modules/admin/project-values.coffee | 7 +-- app/coffee/modules/admin/roles.coffee | 7 +-- app/coffee/modules/admin/third-parties.coffee | 7 +-- app/coffee/modules/backlog/main.coffee | 9 ++-- app/coffee/modules/issues/detail.coffee | 5 +- app/coffee/modules/issues/list.coffee | 7 +-- app/coffee/modules/kanban/main.coffee | 7 +-- app/coffee/modules/search.coffee | 5 +- app/coffee/modules/taskboard/main.coffee | 7 +-- app/coffee/modules/tasks/detail.coffee | 5 +- app/coffee/modules/team/main.coffee | 5 +- app/coffee/modules/user-settings/main.coffee | 8 ++-- .../user-settings/notifications.coffee | 5 +- app/coffee/modules/userstories/detail.coffee | 9 ++-- app/coffee/modules/wiki/main.coffee | 7 +-- app/index.jade | 8 +++- .../transfer-project.controller.coffee | 7 +-- .../transfer-project.controller.spec.coffee | 10 +++- .../services/error-handling.service.coffee | 48 +++++++++++++++++++ app/modules/services/xhrError.service.coffee | 11 ++--- .../services/xhrError.service.spec.coffee | 31 ++++-------- 26 files changed, 161 insertions(+), 102 deletions(-) create mode 100644 app/modules/services/error-handling.service.coffee diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f05dcd6..1da0e824 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Features - Show a confirmation notice when you exit edit mode by pressing ESC in the markdown inputs. - Add the tribe button to link stories from tree.taiga.io with gigs in tribe.taiga.io. +- Errors (not found, server error, permissions and blocked project) don't change the current url. ### Misc - Lots of small and not so small bugfixes. diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index ac0d17ed..5a0b7370 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -443,7 +443,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $routeProvider.when("/permission-denied", {templateUrl: "error/permission-denied.html"}) - $routeProvider.otherwise({redirectTo: "/not-found"}) + $routeProvider.otherwise({templateUrl: "error/not-found.html"}) $locationProvider.html5Mode({enabled: true, requireBase: false}) defaultHeaders = { @@ -465,12 +465,12 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $tgEventsProvider.setSessionId(taiga.sessionId) # Add next param when user try to access to a secction need auth permissions. - authHttpIntercept = ($q, $location, $navUrls, $lightboxService) -> + authHttpIntercept = ($q, $location, $navUrls, $lightboxService, errorHandlingService) -> httpResponseError = (response) -> if response.status == 0 || (response.status == -1 && !response.config.cancelable) $lightboxService.closeAll() - $location.path($navUrls.resolve("error")) - $location.replace() + + errorHandlingService.error() else if response.status == 401 and $location.url().indexOf('/login') == -1 nextUrl = encodeURIComponent($location.url()) $location.url($navUrls.resolve("login")).search("next=#{nextUrl}") @@ -481,7 +481,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven responseError: httpResponseError } - $provide.factory("authHttpIntercept", ["$q", "$location", "$tgNavUrls", "lightboxService", + $provide.factory("authHttpIntercept", ["$q", "$location", "$tgNavUrls", "lightboxService", "tgErrorHandlingService", authHttpIntercept]) $httpProvider.interceptors.push("authHttpIntercept") @@ -536,7 +536,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $httpProvider.interceptors.push("versionCheckHttpIntercept") - blockingIntercept = ($q, $routeParams, $location, $navUrls) -> + blockingIntercept = ($q, $routeParams, $location, $navUrls, errorHandlingService) -> # API calls can return blocked elements and in that situation the user will be redirected # to the blocked project page # This can happens in two scenarios @@ -544,10 +544,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven # - An error reponse when updating/creating/deleting including a 451 error code redirectToBlockedPage = -> pslug = $routeParams.pslug - blockedUrl = $navUrls.resolve("blocked-project", {project: pslug}) - currentUrl = $location.url() - if currentUrl.indexOf(blockedUrl) == -1 - $location.replace().path(blockedUrl) + errorHandlingService.block() responseOk = (response) -> if response.data.blocked_code @@ -566,7 +563,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven responseError: responseError } - $provide.factory("blockingIntercept", ["$q", "$routeParams", "$location", "$tgNavUrls", blockingIntercept]) + $provide.factory("blockingIntercept", ["$q", "$routeParams", "$location", "$tgNavUrls", "tgErrorHandlingService", blockingIntercept]) $httpProvider.interceptors.push("blockingIntercept") @@ -637,7 +634,7 @@ i18nInit = (lang, $translate) -> checksley.updateMessages('default', messages) -init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, projectService, loaderService, navigationBarService) -> +init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, projectService, loaderService, navigationBarService, errorHandlingService) -> $log.debug("Initialize application") $rootscope.$on '$translatePartialLoaderStructureChanged', () -> @@ -691,6 +688,8 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na un() $rootscope.$on '$routeChangeSuccess', (event, next) -> + errorHandlingService.init() + if next.loader loaderService.start(true) @@ -803,6 +802,6 @@ module.run([ "tgProjectService", "tgLoader", "tgNavigationBarService", - "$route", + "tgErrorHandlingService", init ]) diff --git a/app/coffee/classes.coffee b/app/coffee/classes.coffee index a3a74a31..89a12998 100644 --- a/app/coffee/classes.coffee +++ b/app/coffee/classes.coffee @@ -28,11 +28,9 @@ class TaigaController extends TaigaBase onInitialDataError: (xhr) => if xhr if xhr.status == 404 - @location.path(@navUrls.resolve("not-found")) - @location.replace() + @errorHandlingService.notfound() else if xhr.status == 403 - @location.path(@navUrls.resolve("permission-denied")) - @location.replace() + @errorHandlingService.permissionDenied() return @q.reject(xhr) diff --git a/app/coffee/modules/admin/memberships.coffee b/app/coffee/modules/admin/memberships.coffee index e0404170..261d2f3f 100644 --- a/app/coffee/modules/admin/memberships.coffee +++ b/app/coffee/modules/admin/memberships.coffee @@ -48,12 +48,13 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai "$tgAnalytics", "tgAppMetaService", "$translate", - "$tgAuth" - "tgLightboxFactory" + "$tgAuth", + "tgLightboxFactory", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @analytics, - @appMetaService, @translate, @auth, @lightboxFactory) -> + @appMetaService, @translate, @auth, @lightboxFactory, @errorHandlingService) -> bindMethods(@) @scope.project = {} @@ -75,7 +76,7 @@ class MembershipsController extends mixOf(taiga.Controller, taiga.PageMixin, tai loadProject: -> return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.i_am_admin - @location.path(@navUrls.resolve("permission-denied")) + @errorHandlingService.permissionDenied() @scope.projectId = project.id @scope.project = project diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 7bc7efbf..4be5738b 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -53,11 +53,12 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin) "tgAppMetaService", "$translate", "$tgAuth", - "tgCurrentUserService" + "tgCurrentUserService", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, - @appMetaService, @translate, @tgAuth, @currentUserService) -> + @appMetaService, @translate, @tgAuth, @currentUserService, @errorHandlingService) -> @scope.project = {} promise = @.loadInitialData() @@ -83,7 +84,7 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin) loadProject: -> return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.i_am_admin - @location.path(@navUrls.resolve("permission-denied")) + @errorHandlingService.permissionDenied() @scope.projectId = project.id @scope.project = project diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index 1d9229ee..d76149d0 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -50,11 +50,12 @@ class ProjectValuesSectionController extends mixOf(taiga.Controller, taiga.PageM "$tgLocation", "$tgNavUrls", "tgAppMetaService", - "$translate" + "$translate", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, - @appMetaService, @translate) -> + @appMetaService, @translate, @errorHandlingService) -> @scope.project = {} promise = @.loadInitialData() @@ -74,7 +75,7 @@ class ProjectValuesSectionController extends mixOf(taiga.Controller, taiga.PageM loadProject: -> return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.i_am_admin - @location.path(@navUrls.resolve("permission-denied")) + @errorHandlingService.permissionDenied() @scope.projectId = project.id @scope.project = project diff --git a/app/coffee/modules/admin/roles.coffee b/app/coffee/modules/admin/roles.coffee index 9d4173a2..a8142907 100644 --- a/app/coffee/modules/admin/roles.coffee +++ b/app/coffee/modules/admin/roles.coffee @@ -48,11 +48,12 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil "$tgLocation", "$tgNavUrls", "tgAppMetaService", - "$translate" + "$translate", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, - @appMetaService, @translate) -> + @appMetaService, @translate, @errorHandlingService) -> bindMethods(@) @scope.sectionName = "ADMIN.MENU.PERMISSIONS" @@ -71,7 +72,7 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil loadProject: -> return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.i_am_admin - @location.path(@navUrls.resolve("permission-denied")) + @errorHandlingService.permissionDenied() @scope.projectId = project.id @scope.project = project diff --git a/app/coffee/modules/admin/third-parties.coffee b/app/coffee/modules/admin/third-parties.coffee index 9cc4eaf6..2d4ea15c 100644 --- a/app/coffee/modules/admin/third-parties.coffee +++ b/app/coffee/modules/admin/third-parties.coffee @@ -45,10 +45,11 @@ class WebhooksController extends mixOf(taiga.Controller, taiga.PageMixin, taiga. "$tgLocation", "$tgNavUrls", "tgAppMetaService", - "$translate" + "$translate", + "tgErrorHandlingService" ] - constructor: (@scope, @repo, @rs, @params, @location, @navUrls, @appMetaService, @translate) -> + constructor: (@scope, @repo, @rs, @params, @location, @navUrls, @appMetaService, @translate, @errorHandlingService) -> bindMethods(@) @scope.sectionName = "ADMIN.WEBHOOKS.SECTION_NAME" @@ -72,7 +73,7 @@ class WebhooksController extends mixOf(taiga.Controller, taiga.PageMixin, taiga. loadProject: -> return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.i_am_admin - @location.path(@navUrls.resolve("permission-denied")) + @errorHandlingService.permissionDenied() @scope.projectId = project.id @scope.project = project diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index de1f7807..ea9a256a 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -56,11 +56,12 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F "$translate", "$tgLoading", "tgResources", - "$tgQueueModelTransformation" + "$tgQueueModelTransformation", + "tgErrorHandlingService" ] - constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, - @location, @appMetaService, @navUrls, @events, @analytics, @translate, @loading, @rs2, @modelTransform) -> + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @appMetaService, @navUrls, + @events, @analytics, @translate, @loading, @rs2, @modelTransform, @errorHandlingService) -> bindMethods(@) @.page = 1 @@ -317,7 +318,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F loadProject: -> return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.is_backlog_activated - @location.path(@navUrls.resolve("permission-denied")) + @errorHandlingService.permissionDenied() @scope.projectId = project.id @scope.project = project diff --git a/app/coffee/modules/issues/detail.coffee b/app/coffee/modules/issues/detail.coffee index 1448c28a..bb65f413 100644 --- a/app/coffee/modules/issues/detail.coffee +++ b/app/coffee/modules/issues/detail.coffee @@ -52,11 +52,12 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin) "$tgAnalytics", "$tgNavUrls", "$translate", - "$tgQueueModelTransformation" + "$tgQueueModelTransformation", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, - @log, @appMetaService, @analytics, @navUrls, @translate, @modelTransform) -> + @log, @appMetaService, @analytics, @navUrls, @translate, @modelTransform, @errorHandlingService) -> bindMethods(@) @scope.issueRef = @params.issueref diff --git a/app/coffee/modules/issues/list.coffee b/app/coffee/modules/issues/list.coffee index 21ab8c3a..b555393a 100644 --- a/app/coffee/modules/issues/list.coffee +++ b/app/coffee/modules/issues/list.coffee @@ -54,11 +54,12 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi "$tgNavUrls", "$tgEvents", "$tgAnalytics", - "$translate" + "$translate", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @urls, @params, @q, @location, @appMetaService, - @navUrls, @events, @analytics, @translate) -> + @navUrls, @events, @analytics, @translate, @errorHandlingService) -> @scope.sectionName = "Issues" @scope.filters = {} @@ -98,7 +99,7 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi loadProject: -> return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.is_issues_activated - @location.path(@navUrls.resolve("permission-denied")) + @errorHandlingService.permissionDenied() @scope.projectId = project.id @scope.project = project diff --git a/app/coffee/modules/kanban/main.coffee b/app/coffee/modules/kanban/main.coffee index c23f4f28..8b99feba 100644 --- a/app/coffee/modules/kanban/main.coffee +++ b/app/coffee/modules/kanban/main.coffee @@ -61,11 +61,12 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi "$tgNavUrls", "$tgEvents", "$tgAnalytics", - "$translate" + "$translate", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, - @appMetaService, @navUrls, @events, @analytics, @translate) -> + @appMetaService, @navUrls, @events, @analytics, @translate, @errorHandlingService) -> bindMethods(@) @@ -193,7 +194,7 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi loadProject: -> return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.is_kanban_activated - @location.path(@navUrls.resolve("permission-denied")) + @errorHandlingService.permissionDenied() @scope.projectId = project.id @scope.project = project diff --git a/app/coffee/modules/search.coffee b/app/coffee/modules/search.coffee index 1c154887..895a7d55 100644 --- a/app/coffee/modules/search.coffee +++ b/app/coffee/modules/search.coffee @@ -48,10 +48,11 @@ class SearchController extends mixOf(taiga.Controller, taiga.PageMixin) "$tgLocation", "tgAppMetaService", "$tgNavUrls", - "$translate" + "$translate", + "tgErrorHandlingService" ] - constructor: (@scope, @repo, @rs, @params, @q, @location, @appMetaService, @navUrls, @translate) -> + constructor: (@scope, @repo, @rs, @params, @q, @location, @appMetaService, @navUrls, @translate, @errorHandlingService) -> @scope.sectionName = "Search" promise = @.loadInitialData() diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee index d32b159f..68620f62 100644 --- a/app/coffee/modules/taskboard/main.coffee +++ b/app/coffee/modules/taskboard/main.coffee @@ -52,11 +52,12 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) "$tgNavUrls" "$tgEvents" "$tgAnalytics", - "$translate" + "$translate", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appMetaService, @location, @navUrls, - @events, @analytics, @translate) -> + @events, @analytics, @translate, @errorHandlingService) -> bindMethods(@) @scope.sectionName = @translate.instant("TASKBOARD.SECTION_NAME") @@ -124,7 +125,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) loadProject: -> return @rs.projects.get(@scope.projectId).then (project) => if not project.is_backlog_activated - @location.path(@navUrls.resolve("permission-denied")) + @errorHandlingService.permissionDenied() @scope.project = project # Not used at this momment diff --git a/app/coffee/modules/tasks/detail.coffee b/app/coffee/modules/tasks/detail.coffee index 05e6aff4..2e9ae523 100644 --- a/app/coffee/modules/tasks/detail.coffee +++ b/app/coffee/modules/tasks/detail.coffee @@ -50,11 +50,12 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin) "$tgNavUrls", "$tgAnalytics", "$translate", - "$tgQueueModelTransformation" + "$tgQueueModelTransformation", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, - @log, @appMetaService, @navUrls, @analytics, @translate, @modelTransform) -> + @log, @appMetaService, @navUrls, @analytics, @translate, @modelTransform, @errorHandlingService) -> bindMethods(@) @scope.taskRef = @params.taskref diff --git a/app/coffee/modules/team/main.coffee b/app/coffee/modules/team/main.coffee index a90b6181..e11a4769 100644 --- a/app/coffee/modules/team/main.coffee +++ b/app/coffee/modules/team/main.coffee @@ -45,11 +45,12 @@ class TeamController extends mixOf(taiga.Controller, taiga.PageMixin) "tgAppMetaService", "$tgAuth", "$translate", - "tgProjectService" + "tgProjectService", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appMetaService, @auth, - @translate, @projectService) -> + @translate, @projectService, @errorHandlingService) -> @scope.sectionName = "TEAM.SECTION_NAME" promise = @.loadInitialData() diff --git a/app/coffee/modules/user-settings/main.coffee b/app/coffee/modules/user-settings/main.coffee index 98348150..e1ac9139 100644 --- a/app/coffee/modules/user-settings/main.coffee +++ b/app/coffee/modules/user-settings/main.coffee @@ -45,19 +45,19 @@ class UserSettingsController extends mixOf(taiga.Controller, taiga.PageMixin) "$tgLocation", "$tgNavUrls", "$tgAuth", - "$translate" + "$translate", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @config, @repo, @confirm, @rs, @params, @q, @location, @navUrls, - @auth, @translate) -> + @auth, @translate, @errorHandlingService) -> @scope.sectionName = "USER_SETTINGS.MENU.SECTION_TITLE" @scope.project = {} @scope.user = @auth.getUser() if !@scope.user - @location.path(@navUrls.resolve("permission-denied")) - @location.replace() + @errorHandlingService.permissionDenied() @scope.lang = @getLan() @scope.theme = @getTheme() diff --git a/app/coffee/modules/user-settings/notifications.coffee b/app/coffee/modules/user-settings/notifications.coffee index c089c542..0cb25f4c 100644 --- a/app/coffee/modules/user-settings/notifications.coffee +++ b/app/coffee/modules/user-settings/notifications.coffee @@ -44,10 +44,11 @@ class UserNotificationsController extends mixOf(taiga.Controller, taiga.PageMixi "$q", "$tgLocation", "$tgNavUrls", - "$tgAuth" + "$tgAuth", + "tgErrorHandlingService" ] - constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth) -> + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @navUrls, @auth, @errorHandlingService) -> @scope.sectionName = "USER_SETTINGS.NOTIFICATIONS.SECTION_NAME" @scope.user = @auth.getUser() promise = @.loadInitialData() diff --git a/app/coffee/modules/userstories/detail.coffee b/app/coffee/modules/userstories/detail.coffee index db09821d..5f80cdaf 100644 --- a/app/coffee/modules/userstories/detail.coffee +++ b/app/coffee/modules/userstories/detail.coffee @@ -50,12 +50,13 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin) "$tgNavUrls", "$tgAnalytics", "$translate", - "$tgConfig", - "$tgQueueModelTransformation" + "$tgQueueModelTransformation", + "tgErrorHandlingService", + "$tgConfig" ] - constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @log, @appMetaService, - @navUrls, @analytics, @translate, @configService, @modelTransform) -> + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, + @log, @appMetaService, @navUrls, @analytics, @translate, @modelTransform, @errorHandlingService, @configService) -> bindMethods(@) @scope.usRef = @params.usref diff --git a/app/coffee/modules/wiki/main.coffee b/app/coffee/modules/wiki/main.coffee index 40c69277..7d237ea2 100644 --- a/app/coffee/modules/wiki/main.coffee +++ b/app/coffee/modules/wiki/main.coffee @@ -51,11 +51,12 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin) "tgAppMetaService", "$tgNavUrls", "$tgAnalytics", - "$translate" + "$translate", + "tgErrorHandlingService" ] constructor: (@scope, @rootscope, @repo, @model, @confirm, @rs, @params, @q, @location, - @filter, @log, @appMetaService, @navUrls, @analytics, @translate) -> + @filter, @log, @appMetaService, @navUrls, @analytics, @translate, @errorHandlingService) -> @scope.projectSlug = @params.pslug @scope.wikiSlug = @params.slug @scope.wikiTitle = @scope.wikiSlug @@ -86,7 +87,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin) loadProject: -> return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.is_wiki_activated - @location.path(@navUrls.resolve("permission-denied")) + @errorHandlingService.permissionDenied() @scope.projectId = project.id @scope.project = project diff --git a/app/index.jade b/app/index.jade index 66d7d234..6f5cd237 100644 --- a/app/index.jade +++ b/app/index.jade @@ -20,9 +20,13 @@ html(lang="en") window.prerenderReady = false; body(tg-main) - div(tg-navigation-bar) + div(tg-navigation-bar, ng-if="!errorHandling.showingError") + div.master(ng-view, ng-if="!errorHandling.showingError") - div.master(ng-view) + div(ng-if="errorHandling.notfound", ng-include="'error/not-found.html'") + div(ng-if="errorHandling.error", ng-include="'error/error.html'") + div(ng-if="errorHandling.permissionDenied", ng-include="'error/permission-denied.html'") + div(ng-if="errorHandling.blocked", ng-include="'projects/project/blocked-project.html'") div.lightbox.lightbox-generic-ask include partials/includes/modules/lightbox-generic-ask diff --git a/app/modules/projects/transfer/transfer-project.controller.coffee b/app/modules/projects/transfer/transfer-project.controller.coffee index 715d45b3..b7bed191 100644 --- a/app/modules/projects/transfer/transfer-project.controller.coffee +++ b/app/modules/projects/transfer/transfer-project.controller.coffee @@ -28,10 +28,11 @@ class TransferProject "tgCurrentUserService", "$tgNavUrls", "$translate", - "$tgConfirm" + "$tgConfirm", + "tgErrorHandlingService" ] - constructor: (@routeParams, @projectService, @location, @authService, @currentUserService, @navUrls, @translate, @confirmService) -> + constructor: (@routeParams, @projectService, @location, @authService, @currentUserService, @navUrls, @translate, @confirmService, @errorHandlingService) -> initialize: () -> @.projectId = @.project.get("id") @@ -41,7 +42,7 @@ class TransferProject _validateToken: () -> return @projectService.transferValidateToken(@.projectId, @.token).then null, (data, status) => - @location.path(@navUrls.resolve("not-found")) + @errorHandlingService.notfound() _refreshUserData: () -> return @authService.refresh().then () => diff --git a/app/modules/projects/transfer/transfer-project.controller.spec.coffee b/app/modules/projects/transfer/transfer-project.controller.spec.coffee index 812b67b8..c87baecb 100644 --- a/app/modules/projects/transfer/transfer-project.controller.spec.coffee +++ b/app/modules/projects/transfer/transfer-project.controller.spec.coffee @@ -28,6 +28,13 @@ describe "TransferProject", -> mocks.routeParams = {} provide.value "$routeParams", mocks.routeParams + _mockErrorHandlingService = () -> + mocks.errorHandlingService = { + notfound: sinon.stub() + } + + provide.value "tgErrorHandlingService", mocks.errorHandlingService + _mockProjectsService = () -> mocks.projectsService = { transferValidateToken: sinon.stub() @@ -90,6 +97,7 @@ describe "TransferProject", -> _mockTgNavUrls() _mockTranslate() _mockTgConfirm() + _mockErrorHandlingService() return null _inject = (callback) -> @@ -119,7 +127,7 @@ describe "TransferProject", -> ctrl = $controller("TransferProjectController") ctrl.project = project ctrl.initialize().then () -> - expect(mocks.location.path).to.be.calledWith("/not-found") + expect(mocks.errorHandlingService.notfound).have.been.called; done() it "valid token private project with max projects for user", (done) -> diff --git a/app/modules/services/error-handling.service.coffee b/app/modules/services/error-handling.service.coffee new file mode 100644 index 00000000..a3ceef53 --- /dev/null +++ b/app/modules/services/error-handling.service.coffee @@ -0,0 +1,48 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: error-handling.service.coffee +### + +taiga = @.taiga + +class ErrorHandlingService + @.$inject = [ + "$rootScope" + ] + + constructor: (@rootScope) -> + + init: () -> + @rootScope.errorHandling = {}; + + notfound: -> + @rootScope.errorHandling.showingError = true + @rootScope.errorHandling.notfound = true + + error: -> + @rootScope.errorHandling.showingError = true + @rootScope.errorHandling.error = true + + permissionDenied: -> + @rootScope.errorHandling.showingError = true + @rootScope.errorHandling.permissionDenied = true + + block: -> + @rootScope.errorHandling.showingError = true + @rootScope.errorHandling.blocked = true + +angular.module("taigaCommon").service("tgErrorHandlingService", ErrorHandlingService) diff --git a/app/modules/services/xhrError.service.coffee b/app/modules/services/xhrError.service.coffee index 9015e858..42de3515 100644 --- a/app/modules/services/xhrError.service.coffee +++ b/app/modules/services/xhrError.service.coffee @@ -20,19 +20,16 @@ class xhrError extends taiga.Service @.$inject = [ "$q", - "$location", - "$tgNavUrls" + "tgErrorHandlingService" ] - constructor: (@q, @location, @navUrls) -> + constructor: (@q, @errorHandlingService) -> notFound: () -> - @location.path(@navUrls.resolve("not-found")) - @location.replace() + @errorHandlingService.notfound() permissionDenied: () -> - @location.path(@navUrls.resolve("permission-denied")) - @location.replace() + @errorHandlingService.permissionDenied() response: (xhr) -> if xhr diff --git a/app/modules/services/xhrError.service.spec.coffee b/app/modules/services/xhrError.service.spec.coffee index f69ef84a..2efc12ef 100644 --- a/app/modules/services/xhrError.service.spec.coffee +++ b/app/modules/services/xhrError.service.spec.coffee @@ -28,20 +28,14 @@ describe "tgXhrErrorService", -> provide.value "$q", mocks.q - _mockLocation = () -> - mocks.location = { - path: sinon.spy(), - replace: sinon.spy() + + _mockErrorHandling = () -> + mocks.errorHandling = { + notfound: sinon.stub(), + permissionDenied: sinon.stub() } - provide.value "$location", mocks.location - - _mockNavUrls = () -> - mocks.navUrls = { - resolve: sinon.stub() - } - - provide.value "$tgNavUrls", mocks.navUrls + provide.value "tgErrorHandlingService", mocks.errorHandling _inject = (callback) -> inject (_tgXhrErrorService_) -> @@ -52,8 +46,7 @@ describe "tgXhrErrorService", -> module ($provide) -> provide = $provide _mockQ() - _mockLocation() - _mockNavUrls() + _mockErrorHandling() return null @@ -70,23 +63,17 @@ describe "tgXhrErrorService", -> status: 404 } - mocks.navUrls.resolve.withArgs("not-found").returns("not-found") - xhrErrorService.response(xhr) expect(mocks.q.reject.withArgs(xhr)).to.be.calledOnce - expect(mocks.location.path.withArgs("not-found")).to.be.calledOnce - expect(mocks.location.replace).to.be.calledOnce + expect(mocks.errorHandling.notfound).to.be.calledOnce it "403 status redirect to permission-denied page", () -> xhr = { status: 403 } - mocks.navUrls.resolve.withArgs("permission-denied").returns("permission-denied") - xhrErrorService.response(xhr) expect(mocks.q.reject.withArgs(xhr)).to.be.calledOnce - expect(mocks.location.path.withArgs("permission-denied")).to.be.calledOnce - expect(mocks.location.replace).to.be.calledOnce + expect(mocks.errorHandling.permissionDenied).to.be.calledOnce From 389582bd1f23bf3ed9589a0499363ff6716814cd Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 20 May 2016 15:01:03 +0200 Subject: [PATCH 040/315] attachments image slider --- CHANGELOG.md | 1 + app/coffee/modules/common.coffee | 47 +++ app/coffee/modules/common/lightboxes.coffee | 16 - app/modules/attachments/attachments.scss | 20 + .../attachment-link.directive.coffee | 12 +- .../attachment/attachment-gallery.jade | 2 +- .../components/attachment/attachment.jade | 2 +- .../attachments-full.controller.coffee | 7 +- .../attachments-full/attachments-full.jade | 5 + .../attachments-preview.controller.coffee | 75 ++++ ...attachments-preview.controller.spec.coffee | 346 ++++++++++++++++++ .../attachments-preview.directive.coffee | 48 +++ .../attachments-preview.jade | 21 ++ .../attachments-preview.service.coffee | 25 ++ .../lightbox/lightbox-attachment-preview.jade | 5 - e2e/helpers/detail-helper.js | 12 + e2e/shared/detail.js | 22 ++ e2e/utils/lightbox.js | 4 + 18 files changed, 638 insertions(+), 32 deletions(-) create mode 100644 app/modules/components/attachments-preview/attachments-preview.controller.coffee create mode 100644 app/modules/components/attachments-preview/attachments-preview.controller.spec.coffee create mode 100644 app/modules/components/attachments-preview/attachments-preview.directive.coffee create mode 100644 app/modules/components/attachments-preview/attachments-preview.jade create mode 100644 app/modules/components/attachments-preview/attachments-preview.service.coffee delete mode 100644 app/partials/common/lightbox/lightbox-attachment-preview.jade diff --git a/CHANGELOG.md b/CHANGELOG.md index 1da0e824..c7263396 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Show a confirmation notice when you exit edit mode by pressing ESC in the markdown inputs. - Add the tribe button to link stories from tree.taiga.io with gigs in tribe.taiga.io. - Errors (not found, server error, permissions and blocked project) don't change the current url. +- Attachments image slider ### Misc - Lots of small and not so small bugfixes. diff --git a/app/coffee/modules/common.coffee b/app/coffee/modules/common.coffee index d7c660cf..3f36dd4d 100644 --- a/app/coffee/modules/common.coffee +++ b/app/coffee/modules/common.coffee @@ -395,3 +395,50 @@ Autofocus = ($timeout) -> } module.directive('tgAutofocus', ['$timeout', Autofocus]) + +module.directive 'tgPreloadImage', () -> + spinner = "loading..." + + template = """ +
+ +
+ """ + + preload = (src, onLoad) -> + image = new Image() + image.onload = onLoad + image.src = src + + return image + + return { + template: template, + transclude: true, + replace: true, + link: (scope, el, attrs) -> + image = el.find('img:last') + timeout = null + + onLoad = () -> + el.find('.loading-spinner').remove() + image.show() + + if timeout + clearTimeout(timeout) + timeout = null + + attrs.$observe 'preloadSrc', (src) -> + if timeout + clearTimeout(timeout) + + el.find('.loading-spinner').remove() + + timeout = setTimeout () -> + el.prepend(spinner) + , 200 + + image.hide() + + preload(src, onLoad) + } diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index c658a710..205b51bb 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -686,22 +686,6 @@ WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationS module.directive("tgLbWatchers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", WatchersLightboxDirective]) -############################################################################# -## Attachment Preview Lighbox -############################################################################# - -AttachmentPreviewLightboxDirective = (lightboxService, $template, $compile) -> - link = ($scope, $el, attrs) -> - lightboxService.open($el) - - return { - templateUrl: 'common/lightbox/lightbox-attachment-preview.html', - link: link, - scope: true - } - -module.directive("tgLbAttachmentPreview", ["lightboxService", "$tgTemplate", "$compile", AttachmentPreviewLightboxDirective]) - LightboxLeaveProjectWarningDirective = (lightboxService, $template, $compile) -> link = ($scope, $el, attrs) -> lightboxService.open($el) diff --git a/app/modules/attachments/attachments.scss b/app/modules/attachments/attachments.scss index 16e465cf..f715d93e 100644 --- a/app/modules/attachments/attachments.scss +++ b/app/modules/attachments/attachments.scss @@ -127,6 +127,26 @@ } .attachment-preview { + .attachment-preview-container { + svg { + @include svg-size(3rem); + fill: $gray-light; + &:hover { + fill: $primary-light; + transition: fill .3s linear; + } + } + } + .previous { + left: 3rem; + position: absolute; + top: calc(50% - 3rem); + } + .next { + position: absolute; + right: 3rem; + top: calc(50% - 3rem); + } img { max-height: 80vh; max-width: 80vw; diff --git a/app/modules/components/attachment-link/attachment-link.directive.coffee b/app/modules/components/attachment-link/attachment-link.directive.coffee index 50cd109e..2e357f15 100644 --- a/app/modules/components/attachment-link/attachment-link.directive.coffee +++ b/app/modules/components/attachment-link/attachment-link.directive.coffee @@ -17,7 +17,7 @@ # File: attachment-link.directive.coffee ### -AttachmentLinkDirective = ($parse, lightboxFactory) -> +AttachmentLinkDirective = ($parse, attachmentsPreviewService, lightboxService) -> link = (scope, el, attrs) -> attachment = $parse(attrs.tgAttachmentLink)(scope) @@ -26,11 +26,8 @@ AttachmentLinkDirective = ($parse, lightboxFactory) -> event.preventDefault() scope.$apply -> - lightboxFactory.create('tg-lb-attachment-preview', { - class: 'lightbox lightbox-block' - }, { - file: attachment.get('file') - }) + lightboxService.open($('tg-attachments-preview')) + attachmentsPreviewService.fileId = attachment.getIn(['file', 'id']) scope.$on "$destroy", -> el.off() return { @@ -39,7 +36,8 @@ AttachmentLinkDirective = ($parse, lightboxFactory) -> AttachmentLinkDirective.$inject = [ "$parse", - "tgLightboxFactory" + "tgAttachmentsPreviewService", + "lightboxService" ] angular.module("taigaComponents").directive("tgAttachmentLink", AttachmentLinkDirective) diff --git a/app/modules/components/attachment/attachment-gallery.jade b/app/modules/components/attachment/attachment-gallery.jade index eda74610..4a131ef2 100644 --- a/app/modules/components/attachment/attachment-gallery.jade +++ b/app/modules/components/attachment/attachment-gallery.jade @@ -2,7 +2,7 @@ ng-class="{deprecated: vm.attachment.getIn(['file', 'is_deprecated'])}", ng-if="vm.attachment.getIn(['file', 'id'])", ) - a.attachment-image( + a.attachment-image.e2e-attachment-link( tg-attachment-link="vm.attachment" href="{{::vm.attachment.getIn(['file', 'url'])}}" title="{{::vm.attachment.getIn(['file', 'name'])}}" diff --git a/app/modules/components/attachment/attachment.jade b/app/modules/components/attachment/attachment.jade index ec3b885b..842a9a7f 100644 --- a/app/modules/components/attachment/attachment.jade +++ b/app/modules/components/attachment/attachment.jade @@ -5,7 +5,7 @@ form.single-attachment( ) .attachment-name - a( + a.e2e-attachment-link( tg-attachment-link="vm.attachment" href="{{::vm.attachment.getIn(['file', 'url'])}}" title="{{::vm.attachment.get(['file', 'name'])}}" diff --git a/app/modules/components/attachments-full/attachments-full.controller.coffee b/app/modules/components/attachments-full/attachments-full.controller.coffee index 400ee65f..32a10cb6 100644 --- a/app/modules/components/attachments-full/attachments-full.controller.coffee +++ b/app/modules/components/attachments-full/attachments-full.controller.coffee @@ -26,10 +26,11 @@ class AttachmentsFullController "$tgConfig", "$tgStorage", "tgAttachmentsFullService", - "tgProjectService" + "tgProjectService", + "tgAttachmentsPreviewService" ] - constructor: (@translate, @confirm, @config, @storage, @attachmentsFullService, @projectService) -> + constructor: (@translate, @confirm, @config, @storage, @attachmentsFullService, @projectService, @attachmentsPreviewService) -> @.mode = @storage.get('attachment-mode', 'list') @.maxFileSize = @config.get("maxUploadFileSize", null) @@ -64,6 +65,8 @@ class AttachmentsFullController @attachmentsFullService.loadAttachments(@.type, @.objId, @.projectId) deleteAttachment: (toDeleteAttachment) -> + @attachmentsPreviewService.fileId = null + title = @translate.instant("ATTACHMENT.TITLE_LIGHTBOX_DELETE_ATTACHMENT") message = @translate.instant("ATTACHMENT.MSG_LIGHTBOX_DELETE_ATTACHMENT", { fileName: toDeleteAttachment.getIn(['file', 'name']) diff --git a/app/modules/components/attachments-full/attachments-full.jade b/app/modules/components/attachments-full/attachments-full.jade index d441def1..ce78603f 100644 --- a/app/modules/components/attachments-full/attachments-full.jade +++ b/app/modules/components/attachments-full/attachments-full.jade @@ -94,3 +94,8 @@ section.attachments( alt="{{'COMMON.LOADING' | translate}}" ) .attachment-data {{file.progressMessage}} + +tg-attachments-preview.lightbox.lightbox-block( + ng-show="vm.showAttachments()", + attachments="vm.attachments" +) diff --git a/app/modules/components/attachments-preview/attachments-preview.controller.coffee b/app/modules/components/attachments-preview/attachments-preview.controller.coffee new file mode 100644 index 00000000..b1876b5d --- /dev/null +++ b/app/modules/components/attachments-preview/attachments-preview.controller.coffee @@ -0,0 +1,75 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: attchments-preview.controller.coffee +### + +class AttachmentsPreviewController + @.$inject = [ + "tgAttachmentsPreviewService" + ] + + constructor: (@attachmentsPreviewService) -> + taiga.defineImmutableProperty @, "current", () => + if !@attachmentsPreviewService.fileId + return null + + return @.getCurrent() + + hasPagination: () -> + images = @.attachments.filter (attachment) => + return taiga.isImage(attachment.getIn(['file', 'name'])) + + return images.size > 1 + + getCurrent: () -> + attachment = @.attachments.find (attachment) => + @attachmentsPreviewService.fileId == attachment.getIn(['file', 'id']) + + file = attachment.get('file') + + return file + + getIndex: () -> + return @.attachments.findIndex (attachment) => + @attachmentsPreviewService.fileId == attachment.getIn(['file', 'id']) + + next: () -> + attachmentIndex = @.getIndex() + + image = @.attachments.slice(attachmentIndex + 1).find (attachment) -> + return taiga.isImage(attachment.getIn(['file', 'name'])) + + if !image + image = @.attachments.find (attachment) -> + return taiga.isImage(attachment.getIn(['file', 'name'])) + + + @attachmentsPreviewService.fileId = image.getIn(['file', 'id']) + + previous: () -> + attachmentIndex = @.getIndex() + + image = @.attachments.slice(0, attachmentIndex).findLast (attachment) -> + return taiga.isImage(attachment.getIn(['file', 'name'])) + + if !image + image = @.attachments.findLast (attachment) -> + return taiga.isImage(attachment.getIn(['file', 'name'])) + + @attachmentsPreviewService.fileId = image.getIn(['file', 'id']) + +angular.module('taigaComponents').controller('AttachmentsPreview', AttachmentsPreviewController) diff --git a/app/modules/components/attachments-preview/attachments-preview.controller.spec.coffee b/app/modules/components/attachments-preview/attachments-preview.controller.spec.coffee new file mode 100644 index 00000000..a33464fe --- /dev/null +++ b/app/modules/components/attachments-preview/attachments-preview.controller.spec.coffee @@ -0,0 +1,346 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: attachments-preview.controller.spec.coffee +### + +describe "AttachmentsPreviewController", -> + $provide = null + $controller = null + scope = null + mocks = {} + + _mockAttachmentsPreviewService = -> + mocks.attachmentsPreviewService = {} + + $provide.value("tgAttachmentsPreviewService", mocks.attachmentsPreviewService) + + _mocks = -> + module (_$provide_) -> + $provide = _$provide_ + + _mockAttachmentsPreviewService() + + return null + + _inject = -> + inject (_$controller_, $rootScope) -> + $controller = _$controller_ + scope = $rootScope.$new() + + _setup = -> + _mocks() + _inject() + + beforeEach -> + module "taigaComponents" + + _setup() + + it "get current file", () -> + attachment = Immutable.fromJS({ + file: { + description: 'desc', + is_deprecated: false + } + }) + + ctrl = $controller("AttachmentsPreview", { + $scope: scope + }) + + + ctrl.attachments = Immutable.fromJS([ + { + file: { + id: 1 + } + }, + { + file: { + id: 2 + } + }, + { + file: { + id: 3 + } + } + ]) + + mocks.attachmentsPreviewService.fileId = 2; + + current = ctrl.getCurrent() + + expect(current.get('id')).to.be.equal(2) + expect(ctrl.current.get('id')).to.be.equal(2) + + + it "has pagination", () -> + attachment = Immutable.fromJS({ + file: { + description: 'desc', + is_deprecated: false + } + }) + + ctrl = $controller("AttachmentsPreview", { + $scope: scope + }) + + ctrl.getIndex = sinon.stub().returns(0) + + + ctrl.attachments = Immutable.fromJS([ + { + file: { + id: 1, + name: "xx" + } + }, + { + file: { + id: 2, + name: "xx" + } + }, + { + file: { + id: 3, + name: "xx.jpg" + } + } + ]) + + mocks.attachmentsPreviewService.fileId = 1; + + pagination = ctrl.hasPagination() + + expect(pagination).to.be.false + + ctrl.attachments = ctrl.attachments.push(Immutable.fromJS({ + file: { + id: 4, + name: "xx.jpg" + } + })) + + pagination = ctrl.hasPagination() + + expect(pagination).to.be.true + + it "get index", () -> + attachment = Immutable.fromJS({ + file: { + description: 'desc', + is_deprecated: false + } + }) + + ctrl = $controller("AttachmentsPreview", { + $scope: scope + }) + + + ctrl.attachments = Immutable.fromJS([ + { + file: { + id: 1 + } + }, + { + file: { + id: 2 + } + }, + { + file: { + id: 3 + } + } + ]) + + mocks.attachmentsPreviewService.fileId = 2; + + currentIndex = ctrl.getIndex() + + expect(currentIndex).to.be.equal(1) + + it "next", () -> + attachment = Immutable.fromJS({ + file: { + description: 'desc', + is_deprecated: false + } + }) + + ctrl = $controller("AttachmentsPreview", { + $scope: scope + }) + + ctrl.getIndex = sinon.stub().returns(0) + + + ctrl.attachments = Immutable.fromJS([ + { + file: { + id: 1, + name: "xx" + } + }, + { + file: { + id: 2, + name: "xx" + } + }, + { + file: { + id: 3, + name: "xx.jpg" + } + } + ]) + + mocks.attachmentsPreviewService.fileId = 1; + + currentIndex = ctrl.next() + + expect(mocks.attachmentsPreviewService.fileId).to.be.equal(3) + + it "next infinite", () -> + attachment = Immutable.fromJS({ + file: { + description: 'desc', + is_deprecated: false + } + }) + + ctrl = $controller("AttachmentsPreview", { + $scope: scope + }) + + ctrl.getIndex = sinon.stub().returns(2) + + ctrl.attachments = Immutable.fromJS([ + { + file: { + id: 1, + name: "xx.jpg" + } + }, + { + file: { + id: 2, + name: "xx" + } + }, + { + file: { + id: 3, + name: "xx.jpg" + } + } + ]) + + mocks.attachmentsPreviewService.fileId = 3; + + currentIndex = ctrl.next() + + expect(mocks.attachmentsPreviewService.fileId).to.be.equal(1) + + it "previous", () -> + attachment = Immutable.fromJS({ + file: { + description: 'desc', + is_deprecated: false + } + }) + + ctrl = $controller("AttachmentsPreview", { + $scope: scope + }) + + ctrl.getIndex = sinon.stub().returns(2) + + + ctrl.attachments = Immutable.fromJS([ + { + file: { + id: 1, + name: "xx.jpg" + } + }, + { + file: { + id: 2, + name: "xx" + } + }, + { + file: { + id: 3, + name: "xx.jpg" + } + } + ]) + + mocks.attachmentsPreviewService.fileId = 3; + + currentIndex = ctrl.previous() + + expect(mocks.attachmentsPreviewService.fileId).to.be.equal(1) + + it "previous infinite", () -> + attachment = Immutable.fromJS({ + file: { + description: 'desc', + is_deprecated: false + } + }) + + ctrl = $controller("AttachmentsPreview", { + $scope: scope + }) + + ctrl.getIndex = sinon.stub().returns(0) + + ctrl.attachments = Immutable.fromJS([ + { + file: { + id: 1, + name: "xx.jpg" + } + }, + { + file: { + id: 2, + name: "xx" + } + }, + { + file: { + id: 3, + name: "xx.jpg" + } + } + ]) + + mocks.attachmentsPreviewService.fileId = 1; + + currentIndex = ctrl.previous() + + expect(mocks.attachmentsPreviewService.fileId).to.be.equal(3) diff --git a/app/modules/components/attachments-preview/attachments-preview.directive.coffee b/app/modules/components/attachments-preview/attachments-preview.directive.coffee new file mode 100644 index 00000000..4e6b48cf --- /dev/null +++ b/app/modules/components/attachments-preview/attachments-preview.directive.coffee @@ -0,0 +1,48 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: attachments-preview.directive.coffee +### + +AttachmentPreviewLightboxDirective = (lightboxService, attachmentsPreviewService) -> + link = ($scope, el, attrs, ctrl) -> + $(document.body).on "keydown.image-preview", (e) -> + if attachmentsPreviewService.fileId + if e.keyCode == 39 + ctrl.next() + else if e.keyCode == 37 + ctrl.previous() + + $scope.$digest() + + $scope.$on '$destroy', () -> + $(document.body).off('.image-preview') + + return { + scope: {}, + controller: 'AttachmentsPreview', + templateUrl: 'components/attachments-preview/attachments-preview.html', + link: link, + controllerAs: "vm", + bindToController: { + attachments: "=" + } + } + +angular.module('taigaComponents').directive("tgAttachmentsPreview", [ + "lightboxService", + "tgAttachmentsPreviewService", + AttachmentPreviewLightboxDirective]) diff --git a/app/modules/components/attachments-preview/attachments-preview.jade b/app/modules/components/attachments-preview/attachments-preview.jade new file mode 100644 index 00000000..0be41189 --- /dev/null +++ b/app/modules/components/attachments-preview/attachments-preview.jade @@ -0,0 +1,21 @@ +.attachment-preview(ng-if="vm.attachments.size && vm.current") + tg-lightbox-close + + .attachment-preview-container + a.previous( + href="#", + ng-click="vm.previous()", + ng-if="vm.hasPagination()" + ) + tg-svg(svg-icon="icon-arrow-left") + + a(href="{{vm.current.get('url')}}", title="{{vm.current.get('description')}}", target="_blank", download="{{vm.current.get('name')}}") + tg-preload-image(preload-src="{{vm.getCurrent().get('url')}}") + img(ng-src="{{vm.getCurrent().get('url')}}") + + a.next( + href="#", + ng-click="vm.next()", + ng-if="vm.hasPagination()" + ) + tg-svg(svg-icon="icon-arrow-right") diff --git a/app/modules/components/attachments-preview/attachments-preview.service.coffee b/app/modules/components/attachments-preview/attachments-preview.service.coffee new file mode 100644 index 00000000..5739e8a0 --- /dev/null +++ b/app/modules/components/attachments-preview/attachments-preview.service.coffee @@ -0,0 +1,25 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: attachments-preview.service.coffee +### + +class AttachmentsPreviewService extends taiga.Service + @.$inject = [] + + constructor: () -> + +angular.module("taigaComponents").service("tgAttachmentsPreviewService", AttachmentsPreviewService) diff --git a/app/partials/common/lightbox/lightbox-attachment-preview.jade b/app/partials/common/lightbox/lightbox-attachment-preview.jade deleted file mode 100644 index 84223a72..00000000 --- a/app/partials/common/lightbox/lightbox-attachment-preview.jade +++ /dev/null @@ -1,5 +0,0 @@ -.attachment-preview - tg-lightbox-close - - a(href="{{::file.get('url')}}", title="{{::file.get('description')}}", target="_blank", download="{{::file.get('name')}}") - img(src="{{::file.get('url')}}") diff --git a/e2e/helpers/detail-helper.js b/e2e/helpers/detail-helper.js index bad59b39..eb6c77f9 100644 --- a/e2e/helpers/detail-helper.js +++ b/e2e/helpers/detail-helper.js @@ -417,6 +417,18 @@ helper.attachment = function() { list: function() { $('.view-list').click(); }, + previewLightbox: function() { + return utils.lightbox.open($('tg-attachments-preview')); + }, + getPreviewSrc: function() { + return $('tg-attachments-preview img').getAttribute('src'); + }, + nextPreview: function() { + return $('tg-attachments-preview .next').click(); + }, + attachmentLinks: function() { + return $$('.e2e-attachment-link'); + } }; return obj; diff --git a/e2e/shared/detail.js b/e2e/shared/detail.js index ca61a479..43f2525f 100644 --- a/e2e/shared/detail.js +++ b/e2e/shared/detail.js @@ -315,6 +315,28 @@ shared.attachmentTesting = async function() { attachmentHelper.list(); + // Gallery images + var fileToUploadImage = commonUtil.uploadImagePath(); + + await attachmentHelper.upload(fileToUploadImage, 'testing image ' + date); + + await attachmentHelper.upload(fileToUpload, 'testing image ' + date); + + await attachmentHelper.upload(fileToUploadImage, 'testing image ' + date); + await browser.sleep(5000); + + attachmentHelper.attachmentLinks().last().click(); + + await attachmentHelper.previewLightbox(); + let previewSrc = await attachmentHelper.getPreviewSrc(); + + await attachmentHelper.nextPreview(); + + let previewSrc2 = await attachmentHelper.getPreviewSrc(); + + expect(previewSrc).not.to.be.equal(previewSrc2); + await lightbox.exit(); + // Deleting attachmentsLength = await attachmentHelper.countAttachments(); await attachmentHelper.deleteLastAttachment(); diff --git a/e2e/utils/lightbox.js b/e2e/utils/lightbox.js index 9b8fe6d8..2070f026 100644 --- a/e2e/utils/lightbox.js +++ b/e2e/utils/lightbox.js @@ -4,6 +4,10 @@ var lightbox = module.exports; var transition = 300; lightbox.exit = function(el) { + if (!el) { + el = $('.lightbox.open'); + } + if (typeof el === 'string' || el instanceof String) { el = $(el); } From 8ae0b4d105b25939f95920d62be0f8d8af19ea13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 25 May 2016 12:56:55 +0200 Subject: [PATCH 041/315] Add feedback in points and tags --- app/coffee/modules/common/estimation.coffee | 8 ++++++- app/coffee/modules/common/tags.coffee | 17 +++++++++++++- .../common/components/editable-subject.jade | 11 +++++++--- .../us-estimation-points-per-role.jade | 9 +++++--- app/partials/common/tag/tag-line.jade | 12 +++++++--- app/partials/common/tag/tags-line-tags.jade | 12 ++++++++-- app/styles/components/tag.scss | 22 ++++++++++++++++++- 7 files changed, 77 insertions(+), 14 deletions(-) diff --git a/app/coffee/modules/common/estimation.coffee b/app/coffee/modules/common/estimation.coffee index 6cef0ef6..175e546d 100644 --- a/app/coffee/modules/common/estimation.coffee +++ b/app/coffee/modules/common/estimation.coffee @@ -111,14 +111,19 @@ UsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $template, $c if us estimationProcess = $tgEstimationsService.create($el, us, $scope.project) estimationProcess.onSelectedPointForRole = (roleId, pointId, points) -> + estimationProcess.loading = roleId + estimationProcess.render() save(points).then () -> + estimationProcess.loading = false $rootScope.$broadcast("object:updated") + estimationProcess.render() estimationProcess.render = () -> ctx = { totalPoints: @calculateTotalPoints() roles: @calculateRoles() editable: @isEditable + loading: estimationProcess.loading } mainTemplate = "common/estimation/us-estimation-points-per-role.html" template = $template.get(mainTemplate, true) @@ -154,6 +159,7 @@ EstimationsService = ($template, $repo, $confirm, $q, $qqueue) -> @isEditable = @project.my_permissions.indexOf("modify_us") != -1 @roles = @project.roles @points = @project.points + @loading = false @pointsById = groupBy(@points, (x) -> x.id) @onSelectedPointForRole = (roleId, pointId) -> @render = () -> @@ -163,6 +169,7 @@ EstimationsService = ($template, $repo, $confirm, $q, $qqueue) -> $qqueue.add () => onSuccess = => deferred.resolve() + @render() onError = => $confirm.notify("error") @@ -215,7 +222,6 @@ EstimationsService = ($template, $repo, $confirm, $q, $qqueue) -> pointId = target.data("point-id") @$el.find(".popover").popover().close() - points = _.clone(@us.points, true) points[roleId] = pointId diff --git a/app/coffee/modules/common/tags.coffee b/app/coffee/modules/common/tags.coffee index 0bd42dfa..22e7dc30 100644 --- a/app/coffee/modules/common/tags.coffee +++ b/app/coffee/modules/common/tags.coffee @@ -112,7 +112,7 @@ LbTagLineDirective = ($rs, $template, $compile) -> link = ($scope, $el, $attrs, $model) -> ## Render - renderTags = (tags, tagsColors) -> + renderTags = (tags, tagsColors = []) -> ctx = { tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]}) } @@ -231,6 +231,8 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $modelTransform, $template link = ($scope, $el, $attrs, $model) -> autocomplete = null + loading = false + deleteTagLoading = null isEditable = -> if $attrs.requiredPerm? @@ -243,7 +245,10 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $modelTransform, $template ctx = { tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]}) isEditable: isEditable() + loading: loading + deleteTagLoading: deleteTagLoading } + html = $compile(templateTags(ctx))($scope) $el.find("div.tags-container").html(html) @@ -270,8 +275,10 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $modelTransform, $template ## Aux methods addValue = (value) -> + loading = true value = trim(value.toLowerCase()) return if value.length == 0 + renderTags($model.$modelValue.tags, $scope.project?.tags_colors) transform = $modelTransform.save (item) -> if not item.tags @@ -287,6 +294,8 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $modelTransform, $template onSuccess = -> $rootScope.$broadcast("object:updated") + loading = false + renderTags($model.$modelValue.tags, $scope.project?.tags_colors) onError = -> $confirm.notify("error") @@ -298,6 +307,8 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $modelTransform, $template deleteValue = (value) -> value = trim(value.toLowerCase()) return if value.length == 0 + deleteTagLoading = value + renderTags($model.$modelValue.tags, $scope.project?.tags_colors) transform = $modelTransform.save (item) -> tags = _.clone(item.tags, false) @@ -307,9 +318,12 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $modelTransform, $template onSuccess = -> $rootScope.$broadcast("object:updated") + renderTags($model.$modelValue.tags, $scope.project?.tags_colors) + deleteTagLoading = null onError = -> $confirm.notify("error") + deleteTagLoading = null return transform.then(onSuccess, onError) @@ -357,6 +371,7 @@ TagLineDirective = ($rootScope, $repo, $rs, $confirm, $modelTransform, $template value = target.siblings(".tag-name").text() deleteValue(value) + $scope.$digest() bindOnce $scope, "project.tags_colors", (tags_colors) -> if not isEditable() diff --git a/app/partials/common/components/editable-subject.jade b/app/partials/common/components/editable-subject.jade index 0a294cba..bc1738c5 100644 --- a/app/partials/common/components/editable-subject.jade +++ b/app/partials/common/components/editable-subject.jade @@ -1,11 +1,16 @@ -.view-subject - | {{ item.subject }} +.view-subject {{ item.subject }} tg-svg.edit( svg-icon="icon-edit", title="{{'COMMON.EDIT' | translate}}" ) .edit-subject - input(type="text", ng-model="item.subject", data-required="true", data-maxlength="500", ng-model-options="{ debounce: 200 }") + input( + type="text" + ng-model="item.subject" + data-required="true" + data-maxlength="500" + ng-model-options="{ debounce: 200 }" + ) span.save-container a.save(href="") tg-svg( diff --git a/app/partials/common/estimation/us-estimation-points-per-role.jade b/app/partials/common/estimation/us-estimation-points-per-role.jade index e7a2fbb0..94544c0f 100644 --- a/app/partials/common/estimation/us-estimation-points-per-role.jade +++ b/app/partials/common/estimation/us-estimation-points-per-role.jade @@ -1,11 +1,14 @@ ul.points-per-role <% _.each(roles, function(role) { %> - li.ticket-role-points.total(class!="<% if(editable){ %>clickable<% } %>", data-role-id!="<%- role.id %>", title!="<%- role.name %>") + li.ticket-role-points.total( + class!="<% if(editable){ %>clickable<% } %>" + data-role-id!="<%- role.id %>" + title!="<%- role.name %>" + ) span.points <%- role.points %> tg-svg(svg-icon="icon-arrow-down") - span.role - <%- role.name %> + span.role(tg-loading!="<%- loading == role.id %>") <%- role.name %> <% }); %> li.ticket-role-points.total span.points <%- totalPoints %> diff --git a/app/partials/common/tag/tag-line.jade b/app/partials/common/tag/tag-line.jade index 336b3731..7457060a 100644 --- a/app/partials/common/tag/tag-line.jade +++ b/app/partials/common/tag/tag-line.jade @@ -1,9 +1,15 @@ .tags-container -a(href="#", class="add-tag hidden", title="{{'COMMON.TAGS.ADD' | translate}}") +a.add-tag.hidden( + href="#" + title="{{'COMMON.TAGS.ADD' | translate}}" +) tg-svg(svg-icon="icon-add") span.add-tag-text(translate="COMMON.TAGS.ADD") span.add-tag-input - input(type="text", placeholder="{{'COMMON.TAGS.PLACEHOLDER' | translate}}", class="tag-input hidden") + input.tag-input.hidden( + type="text" + placeholder="{{'COMMON.TAGS.PLACEHOLDER' | translate}}" + ) span.save.hidden(title="{{'COMMON.SAVE' | translate}}") - tg-svg(svg-icon="icon-save") \ No newline at end of file + tg-svg(svg-icon="icon-save") diff --git a/app/partials/common/tag/tags-line-tags.jade b/app/partials/common/tag/tags-line-tags.jade index 90b934e4..53ee66ac 100644 --- a/app/partials/common/tag/tags-line-tags.jade +++ b/app/partials/common/tag/tags-line-tags.jade @@ -1,8 +1,16 @@ <% _.each(tags, function(tag) { %> -span(class="tag", style!="border-left: 5px solid <%- tag.color %>;") +<% if (tag.name == deleteTagLoading) { %> +div(tg-loading="true") +<% } else { %> +span.tag(style!="border-left: 5px solid <%- tag.color %>;") span.tag-name <%- tag.name %> <% if (isEditable) { %> - a.remove-tag(href="", title="{{'COMMON.TAGS.DELETE' | translate}}") + a.remove-tag( + href="" + title="{{'COMMON.TAGS.DELETE' | translate}}" + ) tg-svg(svg-icon="icon-close") <% } %> +<% } %> <% }); %> +div(tg-loading!="<%- loading %>") diff --git a/app/styles/components/tag.scss b/app/styles/components/tag.scss index ff2ed64c..3148af14 100644 --- a/app/styles/components/tag.scss +++ b/app/styles/components/tag.scss @@ -34,8 +34,23 @@ } .tags-block { + align-content: center; + display: flex; + flex-wrap: wrap; .tags-container { - display: inline-block; + align-items: center; + display: flex; + flex-wrap: wrap; + } + .add-tag-input { + align-items: flex-start; + display: flex; + flex-grow: 0; + flex-shrink: 0; + width: 250px; + .icon-save { + margin-top: .5rem; + } } input { margin-right: .25rem; @@ -55,6 +70,9 @@ transition: .2s linear; } } + .loading-spinner { + margin-right: .5rem; + } .tag { @include font-size(small); margin: 0 .5rem .5rem 0; @@ -78,6 +96,8 @@ } .icon-add { @include svg-size(.9rem); + margin-right: .25rem; + margin-top: .5rem; } .add-tag-text { @include font-size(small); From 6386dd57b235076224117d85de7345b9f2823ac3 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 10 May 2016 13:17:10 +0200 Subject: [PATCH 042/315] Admin area for tags --- CHANGELOG.md | 1 + app/coffee/app.coffee | 7 +- .../modules/admin/project-values.coffee | 81 ++++++++++++++++--- app/coffee/modules/base.coffee | 1 + app/locales/taiga/locale-en.json | 9 ++- .../admin/admin-project-values-tags.jade | 67 +++++++++++++++ .../modules/admin-submenu-project-values.jade | 4 + app/styles/layout/admin-project-tags.scss | 20 +++++ app/styles/layout/admin-project-values.scss | 9 +++ app/styles/modules/common/colors-table.scss | 76 ++++++++++------- e2e/helpers/admin-attributes-helper.js | 24 ++++++ e2e/suites/admin/attributes/tags.e2e.js | 56 +++++++++++++ 12 files changed, 316 insertions(+), 39 deletions(-) create mode 100644 app/partials/admin/admin-project-values-tags.jade create mode 100644 app/styles/layout/admin-project-tags.scss create mode 100644 e2e/suites/admin/attributes/tags.e2e.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c7263396..c8b29a01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Add the tribe button to link stories from tree.taiga.io with gigs in tribe.taiga.io. - Errors (not found, server error, permissions and blocked project) don't change the current url. - Attachments image slider +- New admin area to edit the tag colors used in your project ### Misc - Lots of small and not so small bugfixes. diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 5a0b7370..10413ddc 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -289,7 +289,12 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven section: "admin" } ) - + $routeProvider.when("/project/:pslug/admin/project-values/tags", + { + templateUrl: "admin/admin-project-values-tags.html", + section: "admin" + } + ) $routeProvider.when("/project/:pslug/admin/memberships", { templateUrl: "admin/admin-memberships.html", diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index d76149d0..ba9533d1 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -266,14 +266,6 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, $tra editionRow.removeClass('hidden') editionRow.find('input:visible').first().focus().select() - $el.on "keyup", ".edition input", (event) -> - if event.keyCode == 13 - target = angular.element(event.currentTarget) - saveValue(target) - else if event.keyCode == 27 - target = angular.element(event.currentTarget) - cancel(target) - $el.on "keyup", ".new-value input", (event) -> if event.keyCode == 13 target = $el.find(".new-value") @@ -349,7 +341,7 @@ ColorSelectionDirective = () -> event.preventDefault() event.stopPropagation() target = angular.element(event.currentTarget) - $el.find(".select-color").hide() + $(".select-color").hide() target.siblings(".select-color").show() # Hide when click outside body = angular.element("body") @@ -372,6 +364,15 @@ ColorSelectionDirective = () -> $model.$modelValue.color = $scope.color $el.find(".select-color").hide() + $el.on "keyup", "input", (event) -> + if event.keyCode == 13 + $scope.$apply -> + $model.$modelValue.color = $scope.color + $el.find(".select-color").hide() + + else if event.keyCode == 27 + $el.find(".select-color").hide() + $scope.$on "$destroy", -> $el.off() @@ -689,3 +690,65 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame, $translate) module.directive("tgProjectCustomAttributes", ["$log", "$tgConfirm", "animationFrame", "$translate", ProjectCustomAttributesDirective]) + + +############################################################################# +## Tags Controller +############################################################################# + +class ProjectTagsController extends mixOf(taiga.Controller, taiga.PageMixin) + @.$inject = [ + "$scope", + "$rootScope", + "$tgRepo", + "tgAppMetaService", + "$translate" + ] + + constructor: (@scope, @rootscope, @repo, @appMetaService, @translate) -> + @.loading = true + @rootscope.$on "project:loaded", => + sectionName = @translate.instant(@scope.sectionName) + title = @translate.instant("ADMIN.CUSTOM_ATTRIBUTES.PAGE_TITLE", { + "sectionName": sectionName, + "projectName": @scope.project.name + }) + description = @scope.project.description + @appMetaService.setAll(title, description) + + @.loading = false + @.tagNames = Object.keys(@scope.project.tags_colors).sort() + @scope.projectTags = _.map(@.tagNames, (tagName) => {name: tagName, color: @scope.project.tags_colors[tagName]}) + + updateTag: (tag) -> + tags_colors = angular.copy(@scope.project.tags_colors) + tags_colors[tag.name] = tag.color + @scope.project.tags_colors = tags_colors + return @repo.save(@scope.project) + +module.controller("ProjectTagsController", ProjectTagsController) + + +############################################################################# +## Tags Directive +############################################################################# + +ProjectTagDirective = () -> + link = ($scope, $el, $attrs) -> + $el.color = $scope.tag.color + $ctrl = $el.controller() + + $scope.$on "$destroy", -> + $el.off() + + $scope.$watch "tag.color", (newColor) => + if $el.color != newColor + promise = $ctrl.updateTag($scope.tag) + promise.then null, (data) -> + form.setErrors(data) + + $el.color = newColor + + return {link: link} + +module.directive("tgProjectTag", [ProjectTagDirective]) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 2806d182..e593f3c6 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -99,6 +99,7 @@ urls = { "project-admin-project-values-severities": "/project/:project/admin/project-values/severities" "project-admin-project-values-types": "/project/:project/admin/project-values/types" "project-admin-project-values-custom-fields": "/project/:project/admin/project-values/custom-fields" + "project-admin-project-values-tags": "/project/:project/admin/project-values/tags" "project-admin-memberships": "/project/:project/admin/memberships" "project-admin-roles": "/project/:project/admin/roles" diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index bf80f4f2..8904a654 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -557,6 +557,12 @@ "ISSUE_TITLE": "Issues types", "ACTION_ADD": "Add new {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Roles - {{projectName}}", "WARNING_NO_ROLE": "Be careful, no role in your project will be able to estimate the point value for user stories", @@ -682,7 +688,8 @@ "PRIORITIES": "Priorities", "SEVERITIES": "Severities", "TYPES": "Types", - "CUSTOM_FIELDS": "Custom fields" + "CUSTOM_FIELDS": "Custom fields", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Project Profile" diff --git a/app/partials/admin/admin-project-values-tags.jade b/app/partials/admin/admin-project-values-tags.jade new file mode 100644 index 00000000..fc52290b --- /dev/null +++ b/app/partials/admin/admin-project-values-tags.jade @@ -0,0 +1,67 @@ +doctype html + +div.wrapper( + ng-controller="ProjectValuesSectionController", + ng-init="sectionName='ADMIN.PROJECT_VALUES_TAGS.TITLE'" +) + + tg-project-menu + + sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="project-values") + include ../includes/modules/admin-menu + + sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-tags") + include ../includes/modules/admin-submenu-project-values + + section.main.admin-common.admin-attributes.colors-table + include ../includes/components/mainTitle + p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_TAGS.SUBTITLE") + + .admin-attributes-section( + ng-controller="ProjectTagsController as ctrl" + ) + .admin-attributes-section-wrapper-empty( + ng-if="!projectTags.length" + tg-loading="ctrl.loading" + ) + p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY") + .admin-attributes-section-wrapper( + ng-if="projectTags.length" + ) + .table-header.table-tags-editor + .row + .color-column(translate="COMMON.FIELDS.COLOR") + .color-name(translate="COMMON.FIELDS.NAME") + .color-filter + input.e2e-tags-filter( + type="text" + name="name" + ng-model="tagsFilter.name" + ng-model-options="{debounce: 200}" + ) + tg-svg( + svg-icon="icon-search" + ) + + p.admin-attributes-section-wrapper-empty( + tg-loading="ctrl.loading" + translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY_SEARCH" + ng-if="!(projectTags | filter:tagsFilter).length" + ) + .table-main( + ng-repeat="tag in projectTags | filter:tagsFilter" + tg-bind-scope + ) + form( + tg-project-tag + ng-model="tag" + ) + .row.edition.no-draggable + .color-column( + tg-color-selection + ng-model="tag" + ) + .current-color(ng-style="{background: tag.color}") + include ../includes/components/select-color + + .color-name {{ tag.name }} diff --git a/app/partials/includes/modules/admin-submenu-project-values.jade b/app/partials/includes/modules/admin-submenu-project-values.jade index 00533831..bd0c71c3 100644 --- a/app/partials/includes/modules/admin-submenu-project-values.jade +++ b/app/partials/includes/modules/admin-submenu-project-values.jade @@ -24,3 +24,7 @@ section.admin-submenu 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") + + li#adminmenu-values-tags + a(href="", tg-nav="project-admin-project-values-tags:project=project.slug") + span.title(translate="ADMIN.SUBMENU_PROJECT_VALUES.TAGS") diff --git a/app/styles/layout/admin-project-tags.scss b/app/styles/layout/admin-project-tags.scss new file mode 100644 index 00000000..f341214f --- /dev/null +++ b/app/styles/layout/admin-project-tags.scss @@ -0,0 +1,20 @@ +.table-tags-editor { + input[type="text"] { + background-color: transparent; + border: 0; + border-bottom: 1px solid transparent; + box-shadow: none; + transition: border-bottom .2s linear; + &:focus { + border-bottom: 1px solid $gray; + outline: none; + } + } + .color-filter { + align-items: center; + display: flex; + flex-grow: 1; + padding: 0 10px; + position: relative; + } +} diff --git a/app/styles/layout/admin-project-values.scss b/app/styles/layout/admin-project-values.scss index 7474d016..c0863739 100644 --- a/app/styles/layout/admin-project-values.scss +++ b/app/styles/layout/admin-project-values.scss @@ -11,6 +11,15 @@ width: 100%; } } + .admin-attributes-section-wrapper-empty { + color: $gray-light; + padding: 10vh 0 0; + text-align: center; + } + .loading-spinner { + max-height: 3rem; + max-width: 3rem; + } } } diff --git a/app/styles/modules/common/colors-table.scss b/app/styles/modules/common/colors-table.scss index 872bbbeb..bc5d0d36 100644 --- a/app/styles/modules/common/colors-table.scss +++ b/app/styles/modules/common/colors-table.scss @@ -8,32 +8,8 @@ } .row { padding-left: 50px; - } - } - .table-main { - .row:hover { - background: lighten($primary, 60%); - cursor: move; - transition: background .2s ease-in; - .icon { - opacity: 1; - transition: opacity .2s ease-in; - } - .options-column { - opacity: 1; - transition: opacity .3s linear; - } - } - .options-column { - a { - display: inline-block; - } - } - } - form { - &:last-child { - .row { - border: 0; + &:hover { + background: transparent; } } } @@ -58,6 +34,25 @@ &.hidden { display: none; } + &:hover { + background: lighten($primary, 60%); + cursor: move; + transition: background .2s ease-in; + .icon { + opacity: 1; + transition: opacity .2s ease-in; + } + .options-column { + opacity: 1; + transition: opacity .3s linear; + } + } + &.no-draggable { + padding-left: 50px; + &:hover { + cursor: auto; + } + } .color-column { flex-basis: 60px; flex-grow: 1; @@ -70,10 +65,13 @@ .status-wip-limit { flex-basis: 100px; flex-grow: 1; + flex-shrink: 0; } - .status-name { + .status-name, + .color-name { flex-basis: 150px; - flex-grow: 6; + flex-grow: 1; + flex-shrink: 0; padding: 0 10px; position: relative; span { @@ -81,6 +79,9 @@ display: block; } } + .color-name { + flex-basis: 100px; + } .status-slug { flex-basis: 150px; flex-grow: 6; @@ -105,7 +106,21 @@ padding: 0 0 0 10px; text-align: center; } + } + .options-column { + a { + display: inline-block; + } + } + form { + &:last-child { + .row { + border: 0; + } + } + } + .row-edit { .options-column { opacity: 1; @@ -132,6 +147,11 @@ fill: $primary; opacity: 1; } + &.icon-search { + cursor: none; + fill: $primary; + opacity: 1; + } &.icon-drag { cursor: move; } diff --git a/e2e/helpers/admin-attributes-helper.js b/e2e/helpers/admin-attributes-helper.js index dd742b5f..7590053f 100644 --- a/e2e/helpers/admin-attributes-helper.js +++ b/e2e/helpers/admin-attributes-helper.js @@ -34,6 +34,22 @@ helper.getSection = function(item) { }; }; +helper.getTagsSection = function(item) { + let section = $$('.admin-attributes-section').get(item); + + return { + el: section, + rows: function() { + return section.$$('.table-main > div'); + } + }; +}; + + +helper.getTagsFilter = function() { + return $('.table-header .e2e-tags-filter'); +} + helper.getStatusNames = function(section) { return section.$$('.status-name span').getText(); }; @@ -94,6 +110,14 @@ helper.getGenericForm = function(form) { return form.$('.status-name input'); }; + obj.colorBox = function() { + return form.$('.edition .current-color'); + } + + obj.colorText = function() { + return form.$('.select-color input'); + } + return obj; }; diff --git a/e2e/suites/admin/attributes/tags.e2e.js b/e2e/suites/admin/attributes/tags.e2e.js new file mode 100644 index 00000000..8f3641c3 --- /dev/null +++ b/e2e/suites/admin/attributes/tags.e2e.js @@ -0,0 +1,56 @@ +var utils = require('../../../utils'); + +var adminAttributesHelper = require('../../../helpers').adminAttributes; + +var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); +var expect = chai.expect; + +describe.only('attributes - tags', function() { + before(async function(){ + browser.get(browser.params.glob.host + 'project/project-0/admin/project-values/tags'); + + await adminAttributesHelper.waitLoad(); + + utils.common.takeScreenshot('attributes', 'tags'); + }); + + it('edit', async function() { + let section = adminAttributesHelper.getTagsSection(0); + let rows = section.rows(); + let row = rows.get(0); + + let form = adminAttributesHelper.getGenericForm(row.$('form')); + + var colorBox = form.colorBox(); + await colorBox.click(); + await form.colorText().clear(); + await form.colorText().sendKeys('#000000'); + await browser.actions().sendKeys(protractor.Key.ENTER).perform(); + + await browser.waitForAngular(); + + section = adminAttributesHelper.getTagsSection(0); + rows = section.rows(); + row = rows.get(0); + let backgroundColor = await row.$$('.edition .current-color').get(0).getCssValue('background-color'); + expect(backgroundColor).to.be.equal('rgba(0, 0, 0, 1)'); + utils.common.takeScreenshot('attributes', 'tag edited is black'); + }); + + it('filter', async function() { + let tagsFilter = adminAttributesHelper.getTagsFilter(); + await tagsFilter.clear(); + await tagsFilter.sendKeys('ad'); + await browser.waitForAngular(); + + let section = adminAttributesHelper.getTagsSection(0); + let rows = section.rows(); + let count = await rows.count(); + expect(count).to.be.equal(2); + }); + + +}); From 053c91ebb9cab1e60a3f1436c127501642acba00 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 26 May 2016 15:27:35 +0200 Subject: [PATCH 043/315] fix issue 4251 - autoscroller in taskboard --- app/coffee/modules/taskboard/sortable.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/coffee/modules/taskboard/sortable.coffee b/app/coffee/modules/taskboard/sortable.coffee index 8c5d7ee8..941f4dca 100644 --- a/app/coffee/modules/taskboard/sortable.coffee +++ b/app/coffee/modules/taskboard/sortable.coffee @@ -87,7 +87,7 @@ TaskboardSortableDirective = ($repo, $rs, $rootscope) -> $scope.$apply -> $rootscope.$broadcast("taskboard:task:move", itemTask, newUsId, newStatusId, itemIndex) - scroll = autoScroll(containers, { + scroll = autoScroll([$('.taskboard-table-body')[0]], { margin: 20, pixels: 30, scrollWhenOutside: true, From 69a3b3de16d97bce888bf794e03e1b0da976e923 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 27 May 2016 08:01:09 +0200 Subject: [PATCH 044/315] prevent interpolation in admin members section --- app/partials/admin/memberships-row-avatar.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/partials/admin/memberships-row-avatar.jade b/app/partials/admin/memberships-row-avatar.jade index dd81bfd4..a15f3b45 100644 --- a/app/partials/admin/memberships-row-avatar.jade +++ b/app/partials/admin/memberships-row-avatar.jade @@ -1,7 +1,7 @@ figure.avatar img(src!="<%- imgurl %>", alt!="<%- full_name %>") figcaption - span.name <%- full_name %> + span.name(ng-non-bindable) <%- full_name %> <% if (isOwner) { %> tg-svg( svg-icon="icon-badge", From fefb6aca1005b67a7ed02da0e7d777f8284fc9ce Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 27 May 2016 09:00:42 +0200 Subject: [PATCH 045/315] enhance kanban & taskboard scrolling --- app/coffee/modules/kanban/sortable.coffee | 2 +- app/coffee/modules/taskboard/sortable.coffee | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/coffee/modules/kanban/sortable.coffee b/app/coffee/modules/kanban/sortable.coffee index 41cbb23d..bcdd55d6 100644 --- a/app/coffee/modules/kanban/sortable.coffee +++ b/app/coffee/modules/kanban/sortable.coffee @@ -86,7 +86,7 @@ KanbanSortableDirective = ($repo, $rs, $rootscope) -> $rootscope.$broadcast("kanban:us:move", itemUs, itemUs.status, newStatusId, itemIndex) scroll = autoScroll(containers, { - margin: 20, + margin: 100, pixels: 30, scrollWhenOutside: true, autoScroll: () -> diff --git a/app/coffee/modules/taskboard/sortable.coffee b/app/coffee/modules/taskboard/sortable.coffee index 941f4dca..a5698cc8 100644 --- a/app/coffee/modules/taskboard/sortable.coffee +++ b/app/coffee/modules/taskboard/sortable.coffee @@ -87,8 +87,9 @@ TaskboardSortableDirective = ($repo, $rs, $rootscope) -> $scope.$apply -> $rootscope.$broadcast("taskboard:task:move", itemTask, newUsId, newStatusId, itemIndex) + scroll = autoScroll([$('.taskboard-table-body')[0]], { - margin: 20, + margin: 100, pixels: 30, scrollWhenOutside: true, autoScroll: () -> From 389f27880c0c99b74884dbcb02b2d9242bce443b Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 27 May 2016 09:26:16 +0200 Subject: [PATCH 046/315] open pdf attachments in a new tab --- app/coffee/utils.coffee | 4 ++++ .../attachment-link/attachment-link.directive.coffee | 3 +++ 2 files changed, 7 insertions(+) diff --git a/app/coffee/utils.coffee b/app/coffee/utils.coffee index a8ec6979..fc6d1757 100644 --- a/app/coffee/utils.coffee +++ b/app/coffee/utils.coffee @@ -215,6 +215,9 @@ _.mixin isImage = (name) -> return name.match(/\.(jpe?g|png|gif|gifv|webm)/i) != null +isPdf = (name) -> + return name.match(/\.(pdf)/i) != null + patch = (oldImmutable, newImmutable) -> pathObj = {} @@ -252,4 +255,5 @@ taiga.stripTags = stripTags taiga.replaceTags = replaceTags taiga.defineImmutableProperty = defineImmutableProperty taiga.isImage = isImage +taiga.isPdf = isPdf taiga.patch = patch diff --git a/app/modules/components/attachment-link/attachment-link.directive.coffee b/app/modules/components/attachment-link/attachment-link.directive.coffee index 2e357f15..d1a5d299 100644 --- a/app/modules/components/attachment-link/attachment-link.directive.coffee +++ b/app/modules/components/attachment-link/attachment-link.directive.coffee @@ -28,6 +28,9 @@ AttachmentLinkDirective = ($parse, attachmentsPreviewService, lightboxService) - scope.$apply -> lightboxService.open($('tg-attachments-preview')) attachmentsPreviewService.fileId = attachment.getIn(['file', 'id']) + else if taiga.isPdf(attachment.getIn(['file', 'name'])) + event.preventDefault() + window.open(attachment.getIn(['file', 'url'])) scope.$on "$destroy", -> el.off() return { From a09e1a7649b7995402e3bf03d3c85c26418510a1 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 27 May 2016 10:15:09 +0200 Subject: [PATCH 047/315] force next url register form --- app/coffee/modules/auth.coffee | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/auth.coffee b/app/coffee/modules/auth.coffee index a2e66632..73976d66 100644 --- a/app/coffee/modules/auth.coffee +++ b/app/coffee/modules/auth.coffee @@ -315,7 +315,7 @@ module.directive("tgLogin", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgConfig" ## Register Directive ############################################################################# -RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $routeParams, $analytics, $translate) -> +RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $routeParams, $analytics, $translate, $window) -> link = ($scope, $el, $attrs) -> if not $config.get("publicRegisterEnabled") $location.path($navUrls.resolve("not-found")) @@ -324,12 +324,20 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $routeParams $scope.data = {} form = $el.find("form").checksley({onlyOneErrorElement: true}) - $scope.nextUrl = $navUrls.resolve("home") + console.log $routeParams['forceNext'] + + if $routeParams['forceNext'] and $routeParams['forceNext'] != $navUrls.resolve("register") + $scope.nextUrl = decodeURIComponent($routeParams['forceNext']) + else + $scope.nextUrl = $navUrls.resolve("home") onSuccessSubmit = (response) -> $analytics.trackEvent("auth", "register", "user registration", 1) - $location.url($scope.nextUrl) + if $scope.nextUrl.indexOf('http') == 0 + $window.location.href = $scope.nextUrl + else + $location.url($scope.nextUrl) onErrorSubmit = (response) -> if response.data._error_message @@ -357,7 +365,7 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $routeParams return {link:link} module.directive("tgRegister", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgNavUrls", "$tgConfig", - "$routeParams", "$tgAnalytics", "$translate", RegisterDirective]) + "$routeParams", "$tgAnalytics", "$translate", "$window", RegisterDirective]) ############################################################################# From 0292588b5649b5b82a20d9ae6033635da8cf3abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 27 May 2016 13:02:17 +0200 Subject: [PATCH 048/315] Fix close button in tribe link popover --- app/modules/components/tribe-button/tribe-linked.jade | 10 ++++------ app/modules/components/tribe-button/tribe-linked.scss | 5 +---- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/modules/components/tribe-button/tribe-linked.jade b/app/modules/components/tribe-button/tribe-linked.jade index 6be83206..852c98b2 100644 --- a/app/modules/components/tribe-button/tribe-linked.jade +++ b/app/modules/components/tribe-button/tribe-linked.jade @@ -12,21 +12,19 @@ ng-click="vm.hide()" href="" title="{{ 'US.TRIBE.CLOSE' | translate }}" - ) - svg.icon.icon-remove - use(xlink:href="#icon-remove") - + ) tg-svg.icon-remove(svg-icon="icon-close") + a.gig-title( href="{{::vm.tribeHost}}/gigs/{{gigId}}" title="gigTitle" target="_blank" ) {{gigTitle}} - + a.delete-link( href="{{::vm.tribeHost}}/gigs/{{gigId}}/link-with-taiga?from=taiga" title="{{ 'US.TRIBE.EDIT_LINK' | translate }}" ) {{ 'US.TRIBE.EDIT_LINK' | translate }} - + a.synchronize-link.button-tribe( ng-href="{{::vm.tribeHost}}/gigs/sync/{{gigId}}?from=taiga" title="{{ 'US.TRIBE.SINCHRONIZE_LINK' }}" diff --git a/app/modules/components/tribe-button/tribe-linked.scss b/app/modules/components/tribe-button/tribe-linked.scss index 0da433ef..a94dd879 100644 --- a/app/modules/components/tribe-button/tribe-linked.scss +++ b/app/modules/components/tribe-button/tribe-linked.scss @@ -55,12 +55,9 @@ vertical-align: text-bottom; } svg { + @include svg-size(); fill: $red-light; - height: 1.5rem; - max-height: 1.5rem; - max-width: 1.5rem; transition: all .2s; - width: 1.5rem; &:hover { fill: $red; } From 9c7f0044617a4e1c2c3f5650afef784444a911c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 27 May 2016 13:14:34 +0200 Subject: [PATCH 049/315] Fix close button in tribe link popover (now is when :smile_cat:) --- app/modules/components/tribe-button/tribe-linked.jade | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/modules/components/tribe-button/tribe-linked.jade b/app/modules/components/tribe-button/tribe-linked.jade index 852c98b2..965e8997 100644 --- a/app/modules/components/tribe-button/tribe-linked.jade +++ b/app/modules/components/tribe-button/tribe-linked.jade @@ -12,7 +12,8 @@ ng-click="vm.hide()" href="" title="{{ 'US.TRIBE.CLOSE' | translate }}" - ) tg-svg.icon-remove(svg-icon="icon-close") + ) + tg-svg.icon-remove(svg-icon="icon-close") a.gig-title( href="{{::vm.tribeHost}}/gigs/{{gigId}}" From 91662a8ad7229c53f359d0f003154a466a0c8696 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 30 May 2016 08:32:36 +0200 Subject: [PATCH 050/315] remove bind name in assigned to and watchers --- app/partials/common/components/assigned-to.jade | 4 ++-- app/partials/common/components/watchers.jade | 2 +- app/partials/common/lightbox/lightbox-assigned-to-users.jade | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/partials/common/components/assigned-to.jade b/app/partials/common/components/assigned-to.jade index 9157fc4d..e5f68800 100644 --- a/app/partials/common/components/assigned-to.jade +++ b/app/partials/common/components/assigned-to.jade @@ -14,7 +14,7 @@ .assigned-to-options <% if (!isEditable && fullNameVisible) { %> - span.assigned-name + span.assigned-name(ng-non-bindable) <%- fullName %> <% }; %> @@ -24,7 +24,7 @@ title="{{ 'COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT'|translate }}" class!="user-assigned <% if (isEditable) { %>editable<% }; %>" ) - span.assigned-name + span.assigned-name(ng-non-bindable) <% if (fullNameVisible) { %> <%- fullName %> <% }; %> diff --git a/app/partials/common/components/watchers.jade b/app/partials/common/components/watchers.jade index 7959982f..fa5da31e 100644 --- a/app/partials/common/components/watchers.jade +++ b/app/partials/common/components/watchers.jade @@ -7,7 +7,7 @@ alt!="<%- watcher.full_name_display %>" ) .user-list-name - span <%- watcher.full_name_display %> + span(ng-non-bindable) <%- watcher.full_name_display %> <% if(isEditable){ %> tg-svg.js-delete-watcher.delete-watcher( diff --git a/app/partials/common/lightbox/lightbox-assigned-to-users.jade b/app/partials/common/lightbox/lightbox-assigned-to-users.jade index d25096e0..39065790 100644 --- a/app/partials/common/lightbox/lightbox-assigned-to-users.jade +++ b/app/partials/common/lightbox/lightbox-assigned-to-users.jade @@ -9,6 +9,7 @@ a.user-list-name( href="" title!="<%- selected.full_name_display %>" + ng-non-bindable ) | <%-selected.full_name_display %> tg-svg.remove-assigned-to( @@ -28,6 +29,7 @@ a.user-list-name( href="" title!="<%- user.full_name_display %>" + ng-non-bindable ) | <%- user.full_name_display %> <% }) %> From 211d8972a06bbbf6a9eca83a7295403bc4010911 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 31 May 2016 12:24:09 +0200 Subject: [PATCH 051/315] fix drag&drop tests --- app/js/dragula-drag-multiple.js | 7 +- .../includes/modules/kanban-table.jade | 2 +- e2e/helpers/backlog-helper.js | 22 ++++++ .../admin/attributes/custom-fields.e2e.js | 6 +- e2e/suites/admin/attributes/points.e2e.js | 2 +- e2e/suites/admin/attributes/priorities.e2e.js | 2 +- e2e/suites/admin/attributes/severities.e2e.js | 2 +- e2e/suites/admin/attributes/status.e2e.js | 2 +- e2e/suites/admin/attributes/tags.e2e.js | 2 +- e2e/suites/admin/attributes/types.e2e.js | 2 +- e2e/suites/backlog.e2e.js | 58 ++++++++++------ e2e/suites/home.e2e.js | 4 +- e2e/suites/kanban.e2e.js | 8 +-- e2e/suites/tasks/taskboard.e2e.js | 5 +- e2e/utils/common.js | 68 ++++++++++++------- 15 files changed, 129 insertions(+), 63 deletions(-) diff --git a/app/js/dragula-drag-multiple.js b/app/js/dragula-drag-multiple.js index 1f7c521a..dba5e387 100644 --- a/app/js/dragula-drag-multiple.js +++ b/app/js/dragula-drag-multiple.js @@ -2,6 +2,7 @@ var multipleSortableClass = 'ui-multisortable-multiple'; var mainClass = 'main-drag-item'; var inProgress = false; + var removeEventFn = null; var reset = function(elm) { $(elm) @@ -59,7 +60,7 @@ var current = dragMultiple.items.elm; var container = dragMultiple.items.container; - $(window).off('mousemove.dragmultiple'); + document.documentElement.removeEventListener('mousemove', removeEventFn); // reset dragMultiple.items = {}; @@ -199,12 +200,14 @@ dragMultiple.start = function(item, container) { if (isMultiple(item, container)) { - $(window).on('mousemove.dragmultiple', function() { + document.documentElement.addEventListener('mousemove', function() { if (!inProgress) { dragMultiple.prepare(item, container); } drag(); + + removeEventFn = arguments.callee; }); } }; diff --git a/app/partials/includes/modules/kanban-table.jade b/app/partials/includes/modules/kanban-table.jade index 328e52d8..c904c56d 100644 --- a/app/partials/includes/modules/kanban-table.jade +++ b/app/partials/includes/modules/kanban-table.jade @@ -54,7 +54,7 @@ div.kanban-table(tg-kanban-squish-column, tg-kanban-sortable) ) tg-svg.bulk-action(svg-icon="icon-bulk") - a.option( + a.option.e2e-archived( href="" ng-attr-title="{{title}}" ng-class="class" diff --git a/e2e/helpers/backlog-helper.js b/e2e/helpers/backlog-helper.js index aec19df6..954ec830 100644 --- a/e2e/helpers/backlog-helper.js +++ b/e2e/helpers/backlog-helper.js @@ -130,10 +130,20 @@ helper.openNewMilestone = function(item) { $('.add-sprint').click(); }; +helper.getClosedSprintTable = function() { + return $$('.sprint-empty').last(); +}; + helper.toggleClosedSprints = function() { $('.filter-closed-sprints').click(); }; +helper.toggleSprint = async function(el) { + el.$('.compact-sprint').click(); + + await utils.common.waitTransitionTime(el.$('.sprint-table')); +}; + helper.closedSprints = function() { return $$('.sprint-closed'); }; @@ -164,6 +174,18 @@ helper.getUsRef = function(elm) { return elm.$('span[tg-bo-ref]').getText(); }; +helper.loadFullBacklog = async function() { + do { + var uss = helper.userStories(); + var count = await uss.count(); + var last = uss.last(); + + await browser.executeScript("arguments[0].scrollIntoView();", last.getWebElement()); + + var newcount = await uss.count(); + } while(count < newcount); +}; + // get ref with the larger length helper.getTestingFilterRef = async function() { let userstories = helper.userStories(); diff --git a/e2e/suites/admin/attributes/custom-fields.e2e.js b/e2e/suites/admin/attributes/custom-fields.e2e.js index fd435b8a..a4ee80d3 100644 --- a/e2e/suites/admin/attributes/custom-fields.e2e.js +++ b/e2e/suites/admin/attributes/custom-fields.e2e.js @@ -47,7 +47,7 @@ describe('custom-fields', function() { expect(notification).to.be.true; }); - it.skip('drag', async function() { + it('drag', async function() { let nameOld = await customFieldsHelper.getName(typeIndex, 0); await customFieldsHelper.drag(typeIndex, 0, 1); @@ -99,7 +99,7 @@ describe('custom-fields', function() { expect(utils.notifications.success.open()).to.be.eventually.true; }); - it.skip('drag', async function() { + it('drag', async function() { let nameOld = await customFieldsHelper.getName(typeIndex, 0); await customFieldsHelper.drag(typeIndex, 0, 1); @@ -151,7 +151,7 @@ describe('custom-fields', function() { expect(utils.notifications.success.open()).to.be.eventually.true; }); - it.skip('drag', async function() { + it('drag', async function() { let nameOld = await customFieldsHelper.getName(typeIndex, 0); await customFieldsHelper.drag(typeIndex, 0, 1); diff --git a/e2e/suites/admin/attributes/points.e2e.js b/e2e/suites/admin/attributes/points.e2e.js index 1c9a5148..afe390bc 100644 --- a/e2e/suites/admin/attributes/points.e2e.js +++ b/e2e/suites/admin/attributes/points.e2e.js @@ -86,7 +86,7 @@ describe('attributes - points', function() { expect(newStatuses.indexOf(newStatusName)).to.be.not.equal(-1); }); - it.skip('drag', async function() { + it('drag', async function() { let section = adminAttributesHelper.getSection(0); let rows = section.rows(); let points = await adminAttributesHelper.getPointsNames(section.el); diff --git a/e2e/suites/admin/attributes/priorities.e2e.js b/e2e/suites/admin/attributes/priorities.e2e.js index fcbe4129..677ab263 100644 --- a/e2e/suites/admin/attributes/priorities.e2e.js +++ b/e2e/suites/admin/attributes/priorities.e2e.js @@ -84,7 +84,7 @@ describe('attributes - priorities', function() { expect(newPriorities.indexOf(newPriorityName)).to.be.not.equal(-1); }); - it.skip('drag', async function() { + it('drag', async function() { let section = adminAttributesHelper.getSection(0); let rows = section.rows(); let priorities = await adminAttributesHelper.getGenericNames(section.el); diff --git a/e2e/suites/admin/attributes/severities.e2e.js b/e2e/suites/admin/attributes/severities.e2e.js index 2078db4a..a50bfb24 100644 --- a/e2e/suites/admin/attributes/severities.e2e.js +++ b/e2e/suites/admin/attributes/severities.e2e.js @@ -84,7 +84,7 @@ describe('attributes - severities', function() { expect(newObjs.indexOf(newName)).to.be.not.equal(-1); }); - it.skip('drag', async function() { + it('drag', async function() { let section = adminAttributesHelper.getSection(0); let rows = section.rows(); let objs = await adminAttributesHelper.getGenericNames(section.el); diff --git a/e2e/suites/admin/attributes/status.e2e.js b/e2e/suites/admin/attributes/status.e2e.js index bb37dd23..d212fc9d 100644 --- a/e2e/suites/admin/attributes/status.e2e.js +++ b/e2e/suites/admin/attributes/status.e2e.js @@ -110,7 +110,7 @@ describe('attributes - status', function() { expect(newStatuses.indexOf(newStatusName)).to.be.not.equal(-1); }); - it.skip('drag', async function() { + it('drag', async function() { let section = adminAttributesHelper.getSection(0); let rows = section.rows(); let statuses = await adminAttributesHelper.getStatusNames(section.el); diff --git a/e2e/suites/admin/attributes/tags.e2e.js b/e2e/suites/admin/attributes/tags.e2e.js index 8f3641c3..9963997f 100644 --- a/e2e/suites/admin/attributes/tags.e2e.js +++ b/e2e/suites/admin/attributes/tags.e2e.js @@ -8,7 +8,7 @@ var chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); var expect = chai.expect; -describe.only('attributes - tags', function() { +describe('attributes - tags', function() { before(async function(){ browser.get(browser.params.glob.host + 'project/project-0/admin/project-values/tags'); diff --git a/e2e/suites/admin/attributes/types.e2e.js b/e2e/suites/admin/attributes/types.e2e.js index f16c763d..d646b6c5 100644 --- a/e2e/suites/admin/attributes/types.e2e.js +++ b/e2e/suites/admin/attributes/types.e2e.js @@ -84,7 +84,7 @@ describe('attributes - types', function() { expect(newObjs.indexOf(newName)).to.be.not.equal(-1); }); - it.skip('drag', async function() { + it('drag', async function() { let section = adminAttributesHelper.getSection(0); let rows = section.rows(); let objs = await adminAttributesHelper.getGenericNames(section.el); diff --git a/e2e/suites/backlog.e2e.js b/e2e/suites/backlog.e2e.js index 23ba0a3c..b88b5db9 100644 --- a/e2e/suites/backlog.e2e.js +++ b/e2e/suites/backlog.e2e.js @@ -199,13 +199,14 @@ describe('backlog', function() { expect(newUsCount).to.be.equal(usCount - 1); }); - it.skip('drag backlog us', async function() { + it('drag backlog us', async function() { let dragableElements = backlogHelper.userStories(); - let dragElement = dragableElements.get(1); + let dragElement = dragableElements.get(4); let dragElementHandler = dragElement.$('.icon-drag'); let draggedElementRef = await backlogHelper.getUsRef(dragElement); + await utils.common.drag(dragElementHandler, dragableElements.get(0)); await browser.waitForAngular(); @@ -214,7 +215,7 @@ describe('backlog', function() { expect(firstElementTextRef).to.be.equal(draggedElementRef); }); - it.skip('reorder multiple us', async function() { + it('reorder multiple us', async function() { let dragableElements = backlogHelper.userStories(); let count = await dragableElements.count(); @@ -233,8 +234,7 @@ describe('backlog', function() { let ref2 = await backlogHelper.getUsRef(dragElement); draggedRefs.push(await backlogHelper.getUsRef(dragElement)); - await utils.common.drag(dragElement, dragableElements.get(0)); - await browser.sleep(200); + await utils.common.drag(dragElement.$('.icon-drag'), dragableElements.get(0)); let elementRef1 = await backlogHelper.getUsRef(dragableElements.get(0)); let elementRef2 = await backlogHelper.getUsRef(dragableElements.get(1)); @@ -243,7 +243,7 @@ describe('backlog', function() { expect(elementRef1).to.be.equal(draggedRefs[1]); }); - it.skip('drag multiple us to milestone', async function() { + it.only('drag multiple us to milestone', async function() { let sprint = backlogHelper.sprints().get(0); let initUssSprintCount = await backlogHelper.getSprintUsertories(sprint).count(); @@ -256,7 +256,7 @@ describe('backlog', function() { let dragElement = dragableElements.get(0); let dragElementHandler = dragElement.$('.icon-drag'); - await utils.common.drag(dragElementHandler, sprint); + await utils.common.drag(dragElementHandler, sprint.$('.sprint-table')); await browser.waitForAngular(); let ussSprintCount = await backlogHelper.getSprintUsertories(sprint).count(); @@ -264,8 +264,8 @@ describe('backlog', function() { expect(ussSprintCount).to.be.equal(initUssSprintCount + 2); }); - it.skip('drag us to milestone', async function() { - let sprint = backlogHelper.sprints().get(0); + it('drag us to milestone', async function() { + let sprint = backlogHelper.sprints().get(0).$('.sprint-table'); let dragableElements = backlogHelper.userStories(); let dragElement = dragableElements.get(0); @@ -303,7 +303,7 @@ describe('backlog', function() { expect(sprintRefs.indexOf(draggedRef)).to.be.not.equal(-1); }); - it.skip('reorder milestone us', async function() { + it('reorder milestone us', async function() { let sprint = backlogHelper.sprints().get(0); let dragableElements = backlogHelper.getSprintUsertories(sprint); @@ -318,7 +318,7 @@ describe('backlog', function() { expect(firstElementRef).to.be.equal(firstElementRef); }); - it.skip('drag us from milestone to milestone', async function() { + it('drag us from milestone to milestone', async function() { let sprint1 = backlogHelper.sprints().get(0); let sprint2 = backlogHelper.sprints().get(1); @@ -326,7 +326,7 @@ describe('backlog', function() { let dragElement = backlogHelper.getSprintUsertories(sprint1).get(0); - await utils.common.drag(dragElement, sprint2); + await utils.common.drag(dragElement, sprint2.$('.sprint-table')); await browser.waitForAngular(); let firstElement = backlogHelper.getSprintUsertories(sprint2).get(0); @@ -606,12 +606,29 @@ describe('backlog', function() { } async function dragClosedUsToMilestone() { - await backlogHelper.setUsStatus(2, 5); + //create us + backlogHelper.openNewUs(); - let dragElement = backlogHelper.userStories().get(2); + let createUSLightbox = backlogHelper.getCreateEditUsLightbox(); + + await createUSLightbox.waitOpen(); + + createUSLightbox.subject().sendKeys('subject'); + + //closed status + createUSLightbox.status(5).click(); + + createUSLightbox.submit(); + + await utils.lightbox.close(createUSLightbox.el); + + await backlogHelper.loadFullBacklog(); + + // drag us to milestone + let dragElement = backlogHelper.userStories().last(); let dragElementHandler = dragElement.$('.icon-drag'); - let sprint = backlogHelper.sprints().last(); + let sprint = backlogHelper.getClosedSprintTable(); await utils.common.drag(dragElementHandler, sprint); return browser.waitForAngular(); @@ -638,16 +655,19 @@ describe('backlog', function() { expect(closedSprints).to.be.equal(0); }); - it.skip('open sprint by drag open US to closed sprint', async function() { + it('open sprint by drag open US to closed sprint', async function() { backlogHelper.toggleClosedSprints(); - await backlogHelper.setUsStatus(1, 0); + await backlogHelper.setUsStatus(1, 1); - let dragElement = backlogHelper.userStories().get(0); + let dragElement = backlogHelper.userStories().get(1); let dragElementHandler = dragElement.$('.icon-drag'); let sprint = backlogHelper.sprints().last(); - await utils.common.drag(dragElementHandler, sprint); + + await backlogHelper.toggleSprint(sprint); + + await utils.common.drag(dragElementHandler, sprint.$('.sprint-table')); await browser.waitForAngular(); let closedSprints = await backlogHelper.closedSprints().count(); diff --git a/e2e/suites/home.e2e.js b/e2e/suites/home.e2e.js index 43b4d352..2f7a19ea 100644 --- a/e2e/suites/home.e2e.js +++ b/e2e/suites/home.e2e.js @@ -47,7 +47,7 @@ describe('home', function() { }); }); - describe.skip("project drag and drop", function() { + describe("project drag and drop", function() { var draggedElementText; before(async function() { @@ -72,7 +72,7 @@ describe('home', function() { }); it('projects menu has the new order', async function() { - var firstElementText = await $$('div[tg-dropdown-project-list] ul a').first().getInnerHtml(); + var firstElementText = await $$('div[tg-dropdown-project-list] ul a span').first().getInnerHtml(); expect(firstElementText).to.be.equal(draggedElementText); }); diff --git a/e2e/suites/kanban.e2e.js b/e2e/suites/kanban.e2e.js index 2b2a477a..3f4fd80d 100644 --- a/e2e/suites/kanban.e2e.js +++ b/e2e/suites/kanban.e2e.js @@ -225,7 +225,7 @@ describe('kanban', function() { }); }); - it.skip('move us between columns', async function() { + it('move us between columns', async function() { let initOriginUsCount = await kanbanHelper.getBoxUss(0).count(); let initDestinationUsCount = await kanbanHelper.getBoxUss(1).count(); @@ -243,7 +243,7 @@ describe('kanban', function() { expect(destinationUsCount).to.be.equal(initDestinationUsCount + 1); }); - describe.skip('archive', function() { + describe('archive', function() { it('move to archive', async function() { let initOriginUsCount = await kanbanHelper.getBoxUss(3).count(); @@ -264,7 +264,7 @@ describe('kanban', function() { }); it('show archive', async function() { - $('.icon-open-eye').click(); + $('.e2e-archived').click(); await kanbanHelper.scrollRight(); @@ -276,7 +276,7 @@ describe('kanban', function() { }); it('close archive', async function() { - $('.icon-closed-eye').click(); + $('.e2e-archived').click(); let usCount = await kanbanHelper.getBoxUss(5).count(); diff --git a/e2e/suites/tasks/taskboard.e2e.js b/e2e/suites/tasks/taskboard.e2e.js index de04df4d..3113b4a0 100644 --- a/e2e/suites/tasks/taskboard.e2e.js +++ b/e2e/suites/tasks/taskboard.e2e.js @@ -213,7 +213,7 @@ describe('taskboard', function() { }); }); - describe.skip('move tasks', function() { + describe('move tasks', function() { it('move task between statuses', async function() { let initOriginTaskCount = await taskboardHelper.getBoxTasks(0, 0).count(); let initDestinationTaskCount = await taskboardHelper.getBoxTasks(0, 1).count(); @@ -232,8 +232,7 @@ describe('taskboard', function() { expect(destinationTaskCount).to.be.equal(initDestinationTaskCount + 1); }); - // jquery ui drag bug - it.skip('move task between US\s', async function() { + it('move task between US\s', async function() { let initOriginTaskCount = await taskboardHelper.getBoxTasks(0, 0).count(); let initDestinationTaskCount = await taskboardHelper.getBoxTasks(1, 1).count(); diff --git a/e2e/utils/common.js b/e2e/utils/common.js index 39a6bdbb..8ea6cda5 100644 --- a/e2e/utils/common.js +++ b/e2e/utils/common.js @@ -176,41 +176,63 @@ common.prepare = function() { common.dragEnd = function(elm) { return browser.wait(async function() { - let count = await $$('.ui-sortable-helper').count(); + let count = await $$('.gu-mirror').count(); return count === 0; }, 1000); }; -common.drag = async function(elm, elm2, offset) { - // this code doesn't have sense (jquery ui + scroll drag + selenium = :( ) - await browser.actions() - .mouseMove(elm) - .mouseDown() - .perform(); +common.drag = async function(elm, elm2) { + var drag = ` + var drag = arguments[0].origin; + var dest = arguments[0].dest; - await browser.actions() - .mouseMove(elm2, offset) - .perform(); + function triggerMouseEvent (node, eventType, opts) { + var event = new CustomEvent(eventType); + event.initEvent (eventType, true, true); - await browser.sleep(60); + if(opts && opts.cords) { + event.pageX = opts.cords.x; + event.clientX = opts.cords.x; + event.pageY = opts.cords.y; + event.clientY = opts.cords.y - window.pageYOffset; - await browser.actions() - .mouseMove({x: 10, y: -10}) // fire jqueryui mousemove event always - .perform(); + dest.scrollIntoView(); + } - await browser.sleep(60); + event.which = 1; + node.dispatchEvent(event); + } - await browser.actions() - .mouseMove({x: -10, y: 10}) - .perform(); + triggerMouseEvent(drag, "mousedown"); - await browser.sleep(60); + triggerMouseEvent(document.documentElement, "mousemove", { + cords: { + x: $(dest).offset().left, + y: $(dest).offset().top + } + }); - return browser.actions() - .mouseUp() - .perform() - .then(common.dragEnd); + triggerMouseEvent(document.documentElement, "mousemove", { + cords: { + x: $(dest).offset().left, + y: $(dest).offset().top + } + }); + + triggerMouseEvent(document.documentElement, "mouseup", { + cords: { + x: $(dest).offset().left, + y: $(dest).offset().top + } + }); + `; + + // return browser.executeScript(drag, elm, elm2); + return browser.executeScript(drag, { + origin: elm.getWebElement(), + dest: elm2.getWebElement() + }).then(common.dragEnd); }; common.transitionend = function(selector, property) { From 05c828339b63ac513590e486113b1fba5b5646a2 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 31 May 2016 13:54:28 +0200 Subject: [PATCH 052/315] fix exception on open create us lightbox --- app/coffee/modules/common/estimation.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/app/coffee/modules/common/estimation.coffee b/app/coffee/modules/common/estimation.coffee index 175e546d..ae108527 100644 --- a/app/coffee/modules/common/estimation.coffee +++ b/app/coffee/modules/common/estimation.coffee @@ -57,6 +57,7 @@ LbUsEstimationDirective = ($tgEstimationsService, $rootScope, $repo, $template, totalPoints: @calculateTotalPoints() roles: @calculateRoles() editable: @isEditable + loading: false } mainTemplate = "common/estimation/us-estimation-points-per-role.html" template = $template.get(mainTemplate, true) From 35b7894346055f4cd4340506ddca7173996cf504 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 31 May 2016 14:59:30 +0200 Subject: [PATCH 053/315] custom next url workflow in the register form --- app/coffee/app.coffee | 9 ++++++++- app/coffee/modules/auth.coffee | 24 +++++++++++++++--------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 10413ddc..9db8b891 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -478,7 +478,14 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven errorHandlingService.error() else if response.status == 401 and $location.url().indexOf('/login') == -1 nextUrl = encodeURIComponent($location.url()) - $location.url($navUrls.resolve("login")).search("next=#{nextUrl}") + search = $location.search() + + if search.force_next + $location.url($navUrls.resolve("login")) + .search("force_next", search.force_next) + else + $location.url($navUrls.resolve("login")) + .search("next", nextUrl) return $q.reject(response) diff --git a/app/coffee/modules/auth.coffee b/app/coffee/modules/auth.coffee index 73976d66..b2c6a4f5 100644 --- a/app/coffee/modules/auth.coffee +++ b/app/coffee/modules/auth.coffee @@ -243,8 +243,9 @@ PublicRegisterMessageDirective = ($config, $navUrls, $routeParams, templates) -> return "" url = $navUrls.resolve("register") - if $routeParams['next'] and $routeParams['next'] != $navUrls.resolve("register") - nextUrl = encodeURIComponent($routeParams['next']) + + if $routeParams['force_next'] + nextUrl = encodeURIComponent($routeParams['force_next']) url += "?next=#{nextUrl}" return template({url:url}) @@ -259,7 +260,7 @@ module.directive("tgPublicRegisterMessage", ["$tgConfig", "$tgNavUrls", "$routeP "$tgTemplate", PublicRegisterMessageDirective]) -LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $events, $translate) -> +LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $events, $translate, $window) -> link = ($scope, $el, $attrs) -> form = new checksley.Form($el.find("form.login-form")) @@ -268,9 +269,16 @@ LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $ else $scope.nextUrl = $navUrls.resolve("home") + if $routeParams['force_next'] + $scope.nextUrl = decodeURIComponent($routeParams['force_next']) + onSuccess = (response) -> $events.setupConnection() - $location.url($scope.nextUrl) + + if $scope.nextUrl.indexOf('http') == 0 + $window.location.href = $scope.nextUrl + else + $location.url($scope.nextUrl) onError = (response) -> $confirm.notify("light-error", $translate.instant("LOGIN_FORM.ERROR_AUTH_INCORRECT")) @@ -308,7 +316,7 @@ LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $ return {link:link} module.directive("tgLogin", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgConfig", "$routeParams", - "$tgNavUrls", "$tgEvents", "$translate", LoginDirective]) + "$tgNavUrls", "$tgEvents", "$translate", "$window", LoginDirective]) ############################################################################# @@ -324,10 +332,8 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $routeParams $scope.data = {} form = $el.find("form").checksley({onlyOneErrorElement: true}) - console.log $routeParams['forceNext'] - - if $routeParams['forceNext'] and $routeParams['forceNext'] != $navUrls.resolve("register") - $scope.nextUrl = decodeURIComponent($routeParams['forceNext']) + if $routeParams['next'] and $routeParams['next'] != $navUrls.resolve("login") + $scope.nextUrl = decodeURIComponent($routeParams['next']) else $scope.nextUrl = $navUrls.resolve("home") From ce2fa41d7d706ddbe1df31cd3d8addb8400200e2 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 1 Jun 2016 09:12:18 +0200 Subject: [PATCH 054/315] replace css slidedown by jquery --- app/coffee/modules/admin/third-parties.coffee | 11 +++++++++-- app/partials/admin/admin-third-parties-webhooks.jade | 2 +- .../modules/admin/admin-third-parties-webhooks.scss | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/admin/third-parties.coffee b/app/coffee/modules/admin/third-parties.coffee index 2d4ea15c..076bf552 100644 --- a/app/coffee/modules/admin/third-parties.coffee +++ b/app/coffee/modules/admin/third-parties.coffee @@ -194,15 +194,22 @@ WebhookDirective = ($rs, $repo, $confirm, $loading, $translate) -> $el.on "click", ".toggle-history", (event) -> target = angular.element(event.currentTarget) + if not webhook.logs? or webhook.logs.length == 0 updateLogs().then -> #Waiting for ng-repeat to finish timeout 0, -> - $el.find(".webhooks-history").toggleClass("open") + $el.find(".webhooks-history") + .toggleClass("open") + .slideToggle() + updateShowHideHistoryText() else - $el.find(".webhooks-history").toggleClass("open") + $el.find(".webhooks-history") + .toggleClass("open") + .slideToggle() + $scope.$apply () -> updateShowHideHistoryText() diff --git a/app/partials/admin/admin-third-parties-webhooks.jade b/app/partials/admin/admin-third-parties-webhooks.jade index 0246727f..98d68e50 100644 --- a/app/partials/admin/admin-third-parties-webhooks.jade +++ b/app/partials/admin/admin-third-parties-webhooks.jade @@ -91,7 +91,7 @@ div.wrapper.roles(ng-controller="WebhooksController as ctrl", a.delete-webhook(href="", title="{{'ADMIN.WEBHOOKS.DELETE' | translate}}") tg-svg(svg-icon="icon-trash") - div.webhooks-history(ng-show="webhook.logs") + div.webhooks-history div.history-single-wrapper(ng-repeat="log in webhook.logs") div.history-single div diff --git a/app/styles/modules/admin/admin-third-parties-webhooks.scss b/app/styles/modules/admin/admin-third-parties-webhooks.scss index 1cca310e..8ddaf331 100644 --- a/app/styles/modules/admin/admin-third-parties-webhooks.scss +++ b/app/styles/modules/admin/admin-third-parties-webhooks.scss @@ -82,7 +82,7 @@ } .webhooks-history { - @include slide(1000px, hidden, $min: 0); + display: none; } .history-single-wrapper { From 459a42e387dddcdb9e408ee7ea0d84b10ed58d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 19 Apr 2016 13:19:47 +0200 Subject: [PATCH 055/315] Change background color mass to a new color --- app/modules/attachments/attachments.scss | 2 +- .../discover-search-list-header.scss | 6 +++--- app/modules/home/home.scss | 2 +- app/modules/home/working-on/empty.scss | 2 +- .../profile-contacts/profile-contacts.jade | 6 ------ .../profile/styles/profile-contacts.scss | 17 ----------------- .../profile/styles/profile-sidebar.scss | 5 +---- .../projects/listing/styles/project-list.scss | 2 +- app/partials/contrib/main.jade | 5 ++++- app/partials/issue/issues-filters.jade | 3 +-- app/partials/issue/issues.jade | 6 +++++- app/styles/components/buttons.scss | 4 ++-- app/styles/components/editor-help.scss | 2 +- app/styles/components/markitup.scss | 4 ++-- app/styles/components/tag.scss | 2 +- app/styles/components/track-btn.scss | 8 ++++---- app/styles/layout/admin-project-values.scss | 2 +- app/styles/layout/backlog.scss | 10 +++++----- app/styles/layout/ticket-detail.scss | 6 +++--- app/styles/layout/wiki.scss | 4 ++-- .../modules/admin/admin-project-export.scss | 2 +- .../modules/admin/admin-project-profile.scss | 6 +++--- app/styles/modules/backlog/backlog-table.scss | 3 +++ .../modules/backlog/taskboard-table.scss | 2 +- app/styles/modules/common/custom-fields.scss | 2 +- app/styles/modules/common/history.scss | 2 +- app/styles/modules/common/lightbox.scss | 2 +- app/styles/modules/common/related-tasks.scss | 2 +- app/styles/modules/help/joyride.scss | 2 +- app/styles/modules/home-project.scss | 2 +- app/styles/modules/kanban/kanban-table.scss | 4 ++-- app/themes/high-contrast/variables.scss | 3 +++ app/themes/material-design/custom.scss | 6 +++--- app/themes/material-design/variables.scss | 3 +++ app/themes/taiga/custom.scss | 18 +++++++++--------- app/themes/taiga/variables.scss | 3 +++ 36 files changed, 76 insertions(+), 84 deletions(-) diff --git a/app/modules/attachments/attachments.scss b/app/modules/attachments/attachments.scss index f715d93e..fc1bfac8 100644 --- a/app/modules/attachments/attachments.scss +++ b/app/modules/attachments/attachments.scss @@ -20,7 +20,7 @@ .attachments-header { align-content: center; align-items: center; - background: $whitish; + background: $mass-white; display: flex; justify-content: space-between; min-height: 36px; diff --git a/app/modules/discover/components/discover-search-list-header/discover-search-list-header.scss b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.scss index 2633faed..3a88d3d2 100644 --- a/app/modules/discover/components/discover-search-list-header/discover-search-list-header.scss +++ b/app/modules/discover/components/discover-search-list-header/discover-search-list-header.scss @@ -35,9 +35,9 @@ } .discover-search-subfilter { - @include arrow('bottom', $whitish, $whitish, 1, 8); + @include arrow('bottom', $mass-white, $mass-white, 1, 8); align-items: center; - background: $whitish; + background: $mass-white; display: flex; justify-content: space-between; position: relative; @@ -80,7 +80,7 @@ } &.active { background: $primary-light; - color: $whitish; + color: $white; } } } diff --git a/app/modules/home/home.scss b/app/modules/home/home.scss index c1fd038a..5d1d28f4 100644 --- a/app/modules/home/home.scss +++ b/app/modules/home/home.scss @@ -14,7 +14,7 @@ @include font-type(light); @include font-size(larger); align-content: center; - background: $whitish; + background: $mass-white; display: flex; margin: 0 0 .5rem; padding: .9rem 1rem; diff --git a/app/modules/home/working-on/empty.scss b/app/modules/home/working-on/empty.scss index aa10d7a3..848ffebd 100644 --- a/app/modules/home/working-on/empty.scss +++ b/app/modules/home/working-on/empty.scss @@ -31,7 +31,7 @@ } .line { - background: $whitish; + background: $mass-white; height: 1rem; margin-bottom: 1rem; width: 40vw; diff --git a/app/modules/profile/profile-contacts/profile-contacts.jade b/app/modules/profile/profile-contacts/profile-contacts.jade index 30306343..c4dc8b71 100644 --- a/app/modules/profile/profile-contacts/profile-contacts.jade +++ b/app/modules/profile/profile-contacts/profile-contacts.jade @@ -11,12 +11,6 @@ section.profile-contacts div(ng-if="vm.isCurrentUser") p(translate="USER.PROFILE.CURRENT_USER_CONTACTS_EMPTY") p(translate="USER.PROFILE.CURRENT_USER_CONTACTS_EMPTY_EXPLAIN") - //- - 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.list-itemtype-user(tg-repeat="contact in ::vm.contacts") a.list-itemtype-avatar(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name')}}") diff --git a/app/modules/profile/styles/profile-contacts.scss b/app/modules/profile/styles/profile-contacts.scss index 4479f9f7..a50fd678 100644 --- a/app/modules/profile/styles/profile-contacts.scss +++ b/app/modules/profile/styles/profile-contacts.scss @@ -3,20 +3,3 @@ display: flex; flex-direction: column; } - -.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: $primary; - } - } -} diff --git a/app/modules/profile/styles/profile-sidebar.scss b/app/modules/profile/styles/profile-sidebar.scss index edfba05a..6ff361db 100644 --- a/app/modules/profile/styles/profile-sidebar.scss +++ b/app/modules/profile/styles/profile-sidebar.scss @@ -1,7 +1,7 @@ .profile-sidebar { h4 { @include font-type(bold); - background: $whitish; + background: $mass-white; color: $gray; margin-bottom: .5rem; padding: .5rem; @@ -19,7 +19,4 @@ a { color: $primary; } - .trans-button { - margin-bottom: 1rem; - } } diff --git a/app/modules/projects/listing/styles/project-list.scss b/app/modules/projects/listing/styles/project-list.scss index 4fa8d390..2bbf058f 100644 --- a/app/modules/projects/listing/styles/project-list.scss +++ b/app/modules/projects/listing/styles/project-list.scss @@ -2,7 +2,7 @@ position: relative; .project-list-title { align-items: center; - background: $whitish; + background: $mass-white; display: flex; justify-content: space-between; margin: 2rem 0 1rem; diff --git a/app/partials/contrib/main.jade b/app/partials/contrib/main.jade index 5ed1fd83..b1ffdf7b 100644 --- a/app/partials/contrib/main.jade +++ b/app/partials/contrib/main.jade @@ -1,6 +1,9 @@ doctype html -div.wrapper.roles(ng-init="section='admin'", ng-controller="ContribController as ctrl") +div.wrapper.roles( + ng-init="section='admin'" + ng-controller="ContribController as ctrl" +) tg-project-menu sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="contrib") diff --git a/app/partials/issue/issues-filters.jade b/app/partials/issue/issues-filters.jade index 95763e72..5c26bdd5 100644 --- a/app/partials/issue/issues-filters.jade +++ b/app/partials/issue/issues-filters.jade @@ -15,8 +15,7 @@ <% } %> <% }) %> span(class="new") -input( - class="hidden my-filter-name" +input.hidden.my-filter-name( type="text" placeholder="{{'ISSUES.PLACEHOLDER_FILTER_NAME' | translate}}" ) diff --git a/app/partials/issue/issues.jade b/app/partials/issue/issues.jade index 70f2f701..2e6adb2b 100644 --- a/app/partials/issue/issues.jade +++ b/app/partials/issue/issues.jade @@ -1,6 +1,10 @@ doctype html -div.wrapper.issues.lightbox-generic-form(tg-issues, ng-controller="IssuesController as ctrl", ng-init="section='issues'") +div.wrapper.issues.lightbox-generic-form( + tg-issues + ng-controller="IssuesController as ctrl" + ng-init="section='issues'" +) tg-project-menu sidebar.menu-secondary.extrabar.filters-bar(tg-issues-filters) include ../includes/modules/issues-filters diff --git a/app/styles/components/buttons.scss b/app/styles/components/buttons.scss index f6f95f46..b1f34cfd 100755 --- a/app/styles/components/buttons.scss +++ b/app/styles/components/buttons.scss @@ -33,13 +33,13 @@ } &.disabled, &[disabled] { - background: $whitish; + background: $mass-white; box-shadow: none; color: $gray-light; cursor: not-allowed; opacity: .65; &:hover { - background: $whitish; + background: $mass-white; color: $gray-light; } } diff --git a/app/styles/components/editor-help.scss b/app/styles/components/editor-help.scss index 5336a078..6f770967 100644 --- a/app/styles/components/editor-help.scss +++ b/app/styles/components/editor-help.scss @@ -1,5 +1,5 @@ .wysiwyg-help { - background: $whitish; + background: $mass-white; display: flex; justify-content: space-between; margin-top: -.5rem; diff --git a/app/styles/components/markitup.scss b/app/styles/components/markitup.scss index 93beffec..59dc99a2 100644 --- a/app/styles/components/markitup.scss +++ b/app/styles/components/markitup.scss @@ -1,6 +1,6 @@ .markItUpHeader { ul { - background: $whitish; + background: $mass-white; padding: .3rem; li { display: inline-block; @@ -30,7 +30,7 @@ .preview { .actions { - background: $whitish; + background: $mass-white; margin-top: .5rem; min-height: 2rem; padding: .3rem; diff --git a/app/styles/components/tag.scss b/app/styles/components/tag.scss index 3148af14..810eb23d 100644 --- a/app/styles/components/tag.scss +++ b/app/styles/components/tag.scss @@ -1,7 +1,7 @@ .tag { @include font-type(light); @include font-size(small); - background: $whitish; // Fallback + background: $mass-white; border-radius: 0 5px 5px 0; color: $grayer; display: inline-block; diff --git a/app/styles/components/track-btn.scss b/app/styles/components/track-btn.scss index 3ed50c71..221d893a 100644 --- a/app/styles/components/track-btn.scss +++ b/app/styles/components/track-btn.scss @@ -13,7 +13,7 @@ position: relative; .track-inner { align-items: center; - background: $whitish; + background: $mass-white; border-radius: 4px 0 0 4px; display: flex; flex: 1; @@ -22,7 +22,7 @@ margin-right: .1rem; min-width: 140px; &:hover { - background: darken($whitish, 5%); + background: darken($mass-white, 5%); transition: background .3s; } } @@ -131,7 +131,7 @@ justify-content: center; margin-right: .3rem; .vote-inner { - background: $whitish; + background: $mass-white; color: $gray-light; display: block; padding: 1rem; @@ -139,7 +139,7 @@ } a { &:hover { - background: darken($whitish, 5%); + background: darken($mass-white, 5%); color: $primary-dark; transition: background .3s; path { diff --git a/app/styles/layout/admin-project-values.scss b/app/styles/layout/admin-project-values.scss index c0863739..cae0e99f 100644 --- a/app/styles/layout/admin-project-values.scss +++ b/app/styles/layout/admin-project-values.scss @@ -40,7 +40,7 @@ .project-values-title { align-content: center; align-items: center; - background: $whitish; + background: $mass-white; display: flex; justify-content: space-between; padding: .8em 1rem; diff --git a/app/styles/layout/backlog.scss b/app/styles/layout/backlog.scss index 340aa1af..ec8c3afb 100644 --- a/app/styles/layout/backlog.scss +++ b/app/styles/layout/backlog.scss @@ -1,5 +1,5 @@ .backlog-menu { - background: $whitish; + background: $mass-white; color: $blackish; display: flex; justify-content: space-between; @@ -7,15 +7,15 @@ .trans-button { color: $blackish; display: inline-block; - padding: .4rem 1.5rem; + padding: .3rem 1.5rem; &.active, &:hover { - background: darken($whitish, 10%); - color: $grayer; + background: $whitish; + color: $gray; } &.active { &:hover { - background: lighten($gray, 30%); + background: darken($whitish, 10%); } } &.move-to-sprint { diff --git a/app/styles/layout/ticket-detail.scss b/app/styles/layout/ticket-detail.scss index 93017208..7021eca6 100644 --- a/app/styles/layout/ticket-detail.scss +++ b/app/styles/layout/ticket-detail.scss @@ -11,7 +11,7 @@ @include font-size(large); @include font-type(text); align-items: flex-start; - background: $whitish; + background: $mass-white; display: flex; flex: 1; flex-direction: column; @@ -217,7 +217,7 @@ transition: all .2s linear; } .editable { - background: $whitish; + background: $mass-white; cursor: pointer; } .no-description { @@ -255,7 +255,7 @@ } .view-description { .edit { - background: $whitish; + background: $mass-white; height: 2rem; left: 0; opacity: 0; diff --git a/app/styles/layout/wiki.scss b/app/styles/layout/wiki.scss index 696267da..6c661125 100644 --- a/app/styles/layout/wiki.scss +++ b/app/styles/layout/wiki.scss @@ -28,7 +28,7 @@ &.editable { &:hover { .wysiwyg { - background: $whitish; + background: $mass-white; cursor: pointer; } } @@ -44,7 +44,7 @@ } .edit { @include svg-size(2rem); - background: $whitish; + background: $mass-white; left: 0; opacity: 0; padding: .2rem .5rem; diff --git a/app/styles/modules/admin/admin-project-export.scss b/app/styles/modules/admin/admin-project-export.scss index 1a02135c..3508321e 100644 --- a/app/styles/modules/admin/admin-project-export.scss +++ b/app/styles/modules/admin/admin-project-export.scss @@ -15,7 +15,7 @@ h3 { @include font-type(bold); @include font-size(large); - background: $whitish; + background: $mass-white; color: $gray; margin: .5rem; padding: .5rem; diff --git a/app/styles/modules/admin/admin-project-profile.scss b/app/styles/modules/admin/admin-project-profile.scss index ed86342f..31060629 100644 --- a/app/styles/modules/admin/admin-project-profile.scss +++ b/app/styles/modules/admin/admin-project-profile.scss @@ -68,7 +68,7 @@ display: none; } label { - background: $whitish; + background: $mass-white; color: $grayer; text-align: center; transition: all .2s linear; @@ -101,13 +101,13 @@ } .privacy-project[disabled] { + label { - background: $whitish; + background: $mass-white; box-shadow: none; color: $gray-light; cursor: not-allowed; opacity: .65; &:hover { - background: $whitish; + background: $mass-white; color: $gray-light; } } diff --git a/app/styles/modules/backlog/backlog-table.scss b/app/styles/modules/backlog/backlog-table.scss index 81288a34..9a9d2b9e 100644 --- a/app/styles/modules/backlog/backlog-table.scss +++ b/app/styles/modules/backlog/backlog-table.scss @@ -183,6 +183,9 @@ } .gu-transit { background: $whitish; + } + .sortable-placeholder { + background: $mass-white; height: 40px; width: 100%; * { diff --git a/app/styles/modules/backlog/taskboard-table.scss b/app/styles/modules/backlog/taskboard-table.scss index 16b5f1f2..54509fbb 100644 --- a/app/styles/modules/backlog/taskboard-table.scss +++ b/app/styles/modules/backlog/taskboard-table.scss @@ -73,7 +73,7 @@ $column-margin: 0 10px 0 0; .task-colum-name { @include font-size(medium); align-items: center; - background: $whitish; + background: $mass-white; border-top: 3px solid $gray-light; color: $gray; display: flex; diff --git a/app/styles/modules/common/custom-fields.scss b/app/styles/modules/common/custom-fields.scss index d0efb714..8c840263 100644 --- a/app/styles/modules/common/custom-fields.scss +++ b/app/styles/modules/common/custom-fields.scss @@ -4,7 +4,7 @@ @include font-type(bold); align-content: space-between; align-items: center; - background: $whitish; + background: $mass-white; display: flex; justify-content: space-between; padding: .5rem 1rem; diff --git a/app/styles/modules/common/history.scss b/app/styles/modules/common/history.scss index 554454a2..52736c9f 100644 --- a/app/styles/modules/common/history.scss +++ b/app/styles/modules/common/history.scss @@ -236,7 +236,7 @@ width: calc(100% - 80px); } .changes { - background: $whitish; + background: $mass-white; .change-entry { display: none; &.active { diff --git a/app/styles/modules/common/lightbox.scss b/app/styles/modules/common/lightbox.scss index 77e9644e..1d64e244 100644 --- a/app/styles/modules/common/lightbox.scss +++ b/app/styles/modules/common/lightbox.scss @@ -23,7 +23,7 @@ } label { @include font-size(xsmall); - background: $whitish; + background: $mass-white; border: 1px solid $gray-light; color: $grayer; cursor: pointer; diff --git a/app/styles/modules/common/related-tasks.scss b/app/styles/modules/common/related-tasks.scss index d34e27db..a2762c97 100644 --- a/app/styles/modules/common/related-tasks.scss +++ b/app/styles/modules/common/related-tasks.scss @@ -6,7 +6,7 @@ .related-tasks-header { align-content: center; align-items: center; - background: $whitish; + background: $mass-white; display: flex; justify-content: space-between; min-height: 36px; diff --git a/app/styles/modules/help/joyride.scss b/app/styles/modules/help/joyride.scss index 945a61cd..7a617736 100644 --- a/app/styles/modules/help/joyride.scss +++ b/app/styles/modules/help/joyride.scss @@ -51,7 +51,7 @@ color: $white; } &.introjs-disabled { - background: $whitish; + background: $mass-white; background-color: none; color: $white; } diff --git a/app/styles/modules/home-project.scss b/app/styles/modules/home-project.scss index 13e869f7..3da70541 100644 --- a/app/styles/modules/home-project.scss +++ b/app/styles/modules/home-project.scss @@ -55,7 +55,7 @@ @include font-type(text); @include font-type(bold); align-content: center; - background: $whitish; + background: $mass-white; display: flex; justify-content: space-between; margin-bottom: .5rem; diff --git a/app/styles/modules/kanban/kanban-table.scss b/app/styles/modules/kanban/kanban-table.scss index 1ea5bf03..8c1930eb 100644 --- a/app/styles/modules/kanban/kanban-table.scss +++ b/app/styles/modules/kanban/kanban-table.scss @@ -61,7 +61,7 @@ $column-margin: 0 10px 0 0; .task-colum-name { @include font-size(medium); align-items: center; - background: $whitish; + background: $mass-white; border-top: 3px solid $gray-light; color: $gray; display: flex; @@ -131,7 +131,7 @@ $column-margin: 0 10px 0 0; } } .kanban-uses-box { - background: $whitish; + background: $mass-white; } } diff --git a/app/themes/high-contrast/variables.scss b/app/themes/high-contrast/variables.scss index cd5ef6e6..1ecd40e2 100755 --- a/app/themes/high-contrast/variables.scss +++ b/app/themes/high-contrast/variables.scss @@ -16,6 +16,9 @@ $primary-light: #212121; $primary: #000; $primary-dark: #000; +// Mass white +$mass-white: #f5f5f5; + //Warning colors $red-light: #ff0062; $red: #ff2400; diff --git a/app/themes/material-design/custom.scss b/app/themes/material-design/custom.scss index 25252a9d..877e2884 100644 --- a/app/themes/material-design/custom.scss +++ b/app/themes/material-design/custom.scss @@ -79,7 +79,7 @@ input[type="date"], input[type="password"], select, textarea { - background: $whitish; + background: $mass-white; border-color: $primary; color: $grayer; @include placeholder { @@ -152,7 +152,7 @@ tg-project-menu { //Taskboard table .taskboard-table-header { .task-colum-name { - background: lighten($primary-light, 20%); + background: $mass-white; border-top: 3px solid $primary; .icon { fill: $primary; @@ -161,7 +161,7 @@ tg-project-menu { } .taskboard-table-body { .taskboard-tasks-box { - background: $whitish; + background: $mass-white; } } diff --git a/app/themes/material-design/variables.scss b/app/themes/material-design/variables.scss index c76c2e0b..f975e2c3 100755 --- a/app/themes/material-design/variables.scss +++ b/app/themes/material-design/variables.scss @@ -11,6 +11,9 @@ $gray-light: #BDBDBD; $whitish: #EEEEEE; $white: #fff; +// Mass white +$mass-white: #f5f5f5; + // Primary colors $primary-light: #8c9eff; $primary: #3f51b5; diff --git a/app/themes/taiga/custom.scss b/app/themes/taiga/custom.scss index 40fca1f4..c57561e0 100644 --- a/app/themes/taiga/custom.scss +++ b/app/themes/taiga/custom.scss @@ -15,7 +15,7 @@ body { // Secondary panel .menu-secondary { - background: $whitish; + background: $mass-white; } // Tertiary panel @@ -25,7 +25,7 @@ body { // Extra bar panel .extrabar { - background: $whitish; + background: $mass-white; } @@ -61,7 +61,7 @@ input[type="date"], input[type="password"], select, textarea { - background: lighten($whitish, 6%); + background: $mass-white; border-color: $gray-light; color: $grayer; @include placeholder { @@ -82,7 +82,7 @@ textarea { // Blockquote blockquote { - border-left: 5px solid $whitish; + border-left: 5px solid $mass-white; } blockquote, @@ -120,7 +120,7 @@ tg-project-menu { } .main-nav { - svg path { + svg { fill: $white; } } @@ -132,7 +132,7 @@ tg-project-menu { //Taskboard table .taskboard-table-header { .task-colum-name { - background: lighten($whitish, 5%); + background: $mass-white; border-top: 3px solid $gray-light; .icon { fill: $gray-light; @@ -141,7 +141,7 @@ tg-project-menu { } .taskboard-table-body { .taskboard-tasks-box { - background: lighten($whitish, 5%); + background: $mass-white; } } @@ -152,7 +152,7 @@ tg-project-menu { //Kanban table .kanban-table-header { .task-colum-name { - background: lighten($whitish, 5%); + background: $mass-white; border-top: 3px solid $gray-light; .icon { color: $gray-light; @@ -162,6 +162,6 @@ tg-project-menu { .kanban-table-body { .kanban-uses-box { - background: lighten($whitish, 5%); + background: $mass-white; } } diff --git a/app/themes/taiga/variables.scss b/app/themes/taiga/variables.scss index e8d00c10..ebc23064 100755 --- a/app/themes/taiga/variables.scss +++ b/app/themes/taiga/variables.scss @@ -11,6 +11,9 @@ $gray-light: #767676; $whitish: #e4e3e3; $white: #fff; +// Mass white +$mass-white: #f5f5f5; + // Primary colors $primary-light: #9dce0a; $primary: #5b8200; From c609825077accc61e2e0ed1544a3985144d26ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 2 Jun 2016 10:57:50 +0200 Subject: [PATCH 056/315] Refactor admin webhooks table --- .../admin/admin-third-parties-webhooks.jade | 131 +++++++++++------- app/styles/components/editor-help.scss | 2 + .../admin/admin-third-parties-webhooks.scss | 14 +- 3 files changed, 91 insertions(+), 56 deletions(-) diff --git a/app/partials/admin/admin-third-parties-webhooks.jade b/app/partials/admin/admin-third-parties-webhooks.jade index 98d68e50..ee52e09e 100644 --- a/app/partials/admin/admin-third-parties-webhooks.jade +++ b/app/partials/admin/admin-third-parties-webhooks.jade @@ -1,7 +1,9 @@ doctype html -div.wrapper.roles(ng-controller="WebhooksController as ctrl", - ng-init="section='admin'") +div.wrapper.roles( + ng-controller="WebhooksController as ctrl", + ng-init="section='admin'" +) tg-project-menu sidebar.menu-secondary.sidebar.settings-nav(tg-admin-navigation="third-parties") @@ -10,25 +12,27 @@ div.wrapper.roles(ng-controller="WebhooksController as ctrl", include ../includes/modules/admin-submenu-third-parties section.main.admin-common.admin-webhooks(tg-new-webhook) - include ../includes/components/mainTitle - - p.admin-subtitle(translate="ADMIN.WEBHOOKS.SUBTITLE") - div.webhooks-options - a.button-green.hidden.add-webhook( - href="" - title="{{'ADMIN.WEBHOOKS.ADD_NEW' | translate}}" - translate="ADMIN.WEBHOOKS.ADD_NEW" - ) + header.header-with-actions + include ../includes/components/mainTitle + .action-buttons + a.button-green.hidden.add-webhook( + href="" + title="{{'ADMIN.WEBHOOKS.ADD_NEW' | translate}}" + translate="ADMIN.WEBHOOKS.ADD_NEW" + ) section.webhooks-table.basic-table - div.table-header - div.row - div.webhook-service(translate="COMMON.FIELDS.NAME") - div.webhook-url(translate="COMMON.FIELDS.URL") - div.webhook-options - div.table-body - div.single-webhook-wrapper(tg-webhook="webhook", ng-repeat="webhook in webhooks") - div.edition-mode.hidden + .table-header + .row + .webhook-service(translate="COMMON.FIELDS.NAME") + .webhook-url(translate="COMMON.FIELDS.URL") + .webhook-options + .table-body + .single-webhook-wrapper( + tg-webhook="webhook" + ng-repeat="webhook in webhooks" + ) + .edition-mode.hidden form.row fieldset.webhook-service input( @@ -37,9 +41,9 @@ div.wrapper.roles(ng-controller="WebhooksController as ctrl", data-required="true" ng-model="webhook.name" placeholder="{{'ADMIN.WEBHOOKS.TYPE_NAME' | translate}}" - ) - div.webhook-url - div.webhook-url-inputs + ) + .webhook-url + .webhook-url-inputs fieldset input( type="text" @@ -57,7 +61,7 @@ div.wrapper.roles(ng-controller="WebhooksController as ctrl", data-required="true" ng-model="webhook.key" ) - div.webhook-options + .webhook-options a.edit-existing( href="" title="{{'ADMIN.WEBHOOKS.SAVE' | translate}}" @@ -69,11 +73,10 @@ div.wrapper.roles(ng-controller="WebhooksController as ctrl", ) tg-svg(svg-icon="icon-close") - div.visualization-mode - div.row - div.webhook-service - span(ng-bind="webhook.name") - div.webhook-url + .visualization-mode + .row + .webhook-service(ng-bind="webhook.name") + .webhook-url span(ng-bind="webhook.url") a.show-history.toggle-history( href="" @@ -82,18 +85,27 @@ div.wrapper.roles(ng-controller="WebhooksController as ctrl", translate="ADMIN.WEBHOOKS.SHOW_HISTORY" ) - div.webhook-options - div.webhook-options-wrapper - a.test-webhook(href="", title="{{'ADMIN.WEBHOOKS.TEST' | translate}}") + .webhook-options + .webhook-options-wrapper + a.test-webhook( + href="" + title="{{'ADMIN.WEBHOOKS.TEST' | translate}}" + ) tg-svg(svg-icon="icon-check-empty") - a.edit-webhook(href="", title="{{'ADMIN.WEBHOOKS.EDIT' | translate}}") + a.edit-webhook( + href="" + title="{{'ADMIN.WEBHOOKS.EDIT' | translate}}" + ) tg-svg(svg-icon="icon-edit") - a.delete-webhook(href="", title="{{'ADMIN.WEBHOOKS.DELETE' | translate}}") + a.delete-webhook( + href="" + title="{{'ADMIN.WEBHOOKS.DELETE' | translate}}" + ) tg-svg(svg-icon="icon-trash") - div.webhooks-history - div.history-single-wrapper(ng-repeat="log in webhook.logs") - div.history-single + .webhooks-history + .history-single-wrapper(ng-repeat="log in webhook.logs") + .history-single div span.history-response-icon( ng-class="log.validStatus ? 'history-success' : 'history-error'" @@ -103,8 +115,8 @@ div.wrapper.roles(ng-controller="WebhooksController as ctrl", a.toggle-log(href="") tg-svg(svg-icon="icon-arrow-down") - div.history-single-response - div.history-single-request-header + .history-single-response + .history-single-request-header span(translate="ADMIN.WEBHOOKS.REQUEST") a.resend-request( href="" @@ -113,20 +125,29 @@ div.wrapper.roles(ng-controller="WebhooksController as ctrl", ) tg-svg(svg-icon="icon-reload") span(translate="ADMIN.WEBHOOKS.RESEND_REQUEST") - div.history-single-request-body - div.response-container + .history-single-request-body + .response-container span.response-title(translate="ADMIN.WEBHOOKS.HEADERS") - textarea(name="headers", ng-bind="log.prettySentHeaders") + textarea( + name="headers" + ng-bind="log.prettySentHeaders" + ) - div.response-container + .response-container span.response-title(translate="ADMIN.WEBHOOKS.PAYLOAD") - textarea(name="payload", ng-bind="log.prettySentData") + textarea( + name="payload" + ng-bind="log.prettySentData" + ) - div.history-single-response-header + .history-single-response-header span(translate="ADMIN.WEBHOOKS.RESPONSE") - div.history-single-response-body - div.response-container - textarea(name="response-data", ng-bind="log.response_data") + .history-single-response-body + .response-container + textarea( + name="response-data" + ng-bind="log.response_data" + ) form.new-webhook-form.row.hidden fieldset.webhook-service @@ -137,8 +158,8 @@ div.wrapper.roles(ng-controller="WebhooksController as ctrl", ng-model="newValue.name" placeholder="{{'ADMIN.WEBHOOKS.TYPE_NAME' | translate}}" ) - div.webhook-url - div.webhook-url-inputs + .webhook-url + .webhook-url-inputs fieldset input( type="text" @@ -156,10 +177,16 @@ div.wrapper.roles(ng-controller="WebhooksController as ctrl", data-required="true" ng-model="newValue.key" ) - div.webhook-options - a.add-new(href="", title="{{'ADMIN.WEBHOOKS.SAVE' | translate}}") + .webhook-options + a.add-new( + href="" + title="{{'ADMIN.WEBHOOKS.SAVE' | translate}}" + ) tg-svg(svg-icon="icon-save") - a.cancel-new(href="", title="{{'ADMIN.WEBHOOKS.CANCEL' | translate}}") + a.cancel-new( + href="" + title="{{'ADMIN.WEBHOOKS.CANCEL' | translate}}" + ) tg-svg(svg-icon="icon-close") a.help-button( diff --git a/app/styles/components/editor-help.scss b/app/styles/components/editor-help.scss index 5336a078..9cd8c47d 100644 --- a/app/styles/components/editor-help.scss +++ b/app/styles/components/editor-help.scss @@ -16,6 +16,7 @@ .help-markdown, .help-button { + @include font-size(xsmall); &:hover { span { transition: color .2s linear; @@ -29,6 +30,7 @@ vertical-align: text-top; } .icon { + @include svg-size(.9rem); fill: $gray-light; margin-right: .2rem; } diff --git a/app/styles/modules/admin/admin-third-parties-webhooks.scss b/app/styles/modules/admin/admin-third-parties-webhooks.scss index 8ddaf331..cb87cc1d 100644 --- a/app/styles/modules/admin/admin-third-parties-webhooks.scss +++ b/app/styles/modules/admin/admin-third-parties-webhooks.scss @@ -31,20 +31,24 @@ } .webhook-service { flex-basis: 20%; - flex-grow: 0; + flex-shrink: 0; } .webhook-url { - flex-basis: 400px; - flex-grow: 8; + flex-basis: 60%; + flex-grow: 0; + flex-shrink: 0; + overflow: hidden; span { - @include ellipsis($width: 65%); + @include ellipsis(85%); color: $gray-light; display: inline-block; vertical-align: middle; } a { color: $primary; + cursor: pointer; margin-left: .5rem; + white-space: nowrap; &:hover { color: $primary-light; } @@ -54,7 +58,9 @@ flex-basis: 100px; flex-grow: 0; flex-shrink: 0; + margin-left: auto; a { + cursor: pointer; display: inline-block; margin-right: .5rem; } From 052aeb03bcda71ef6dcc0029b89fa3d6613510b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 3 Jun 2016 14:43:06 +0200 Subject: [PATCH 057/315] Fix promote button rounded corners --- app/partials/issue/promote-issue-to-us-button.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/partials/issue/promote-issue-to-us-button.jade b/app/partials/issue/promote-issue-to-us-button.jade index fe2bd499..bd523f5f 100644 --- a/app/partials/issue/promote-issue-to-us-button.jade +++ b/app/partials/issue/promote-issue-to-us-button.jade @@ -1,4 +1,4 @@ -a.promote-button.is-editable( +a.promote-button.button-gray.is-editable( href="" tg-check-permission="add_us" title="{{ 'ISSUES.ACTION_PROMOTE_TO_US' | translate }}" From ac3b6da30eb26ab14a77e5f28ac209286f1521f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 6 Jun 2016 08:28:06 +0200 Subject: [PATCH 058/315] Wysiwyg styles --- app/styles/components/wysiwyg.scss | 40 ++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/app/styles/components/wysiwyg.scss b/app/styles/components/wysiwyg.scss index ad26118e..d091a699 100644 --- a/app/styles/components/wysiwyg.scss +++ b/app/styles/components/wysiwyg.scss @@ -6,23 +6,40 @@ h1 { @include font-size(xlarge); @include font-type(text); - line-height: 2.5rem; + font-size: 2.25em; + line-height: 1.2; + margin-bottom: 1rem; + margin-top: 1rem; + padding-bottom: .5rem; text-transform: uppercase; } h2 { - @include font-size(large); + @include font-size(larger); @include font-type(bold); - margin-bottom: .5rem; - text-transform: uppercase; + line-height: 1.225; + margin-bottom: 1rem; + margin-top: 1rem; + padding-bottom: .5rem; } h3 { + @include font-size(large); @include font-type(bold); - text-transform: uppercase; + margin-bottom: 1rem; + margin-top: 1rem; + padding-bottom: .5rem; + } + h4 { + @include font-type(bold); + margin-bottom: 1rem; + margin-top: 1rem; } ul, ol { + line-height: 1.5; list-style-position: outside; - margin-left: 1rem; + margin-bottom: 0; + margin-top: 0; + padding-left: 2em; } ul { list-style-type: disc; @@ -53,6 +70,11 @@ .codehilite { overflow: auto; } + blockquote { + p { + margin: 0; + } + } pre, code { @include font-size(small); @@ -64,10 +86,11 @@ overflow: auto; unicode-bidi: embed; white-space: pre-wrap; + } pre { line-height: 1.4rem; - padding: .5rem; + padding: 1rem; } table { border: $gray-light 1px solid; @@ -103,4 +126,7 @@ background: $white; max-height: none; } + hr { + border: 1px solid $whitish; + } } From 393152dbd0fe367f9768db047a8cbbe840010454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Wed, 8 Jun 2016 09:46:01 +0200 Subject: [PATCH 059/315] Enhancement#3708: Remove hour and minute from created column in issues list --- app/partials/includes/modules/issues-table.jade | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/partials/includes/modules/issues-table.jade b/app/partials/includes/modules/issues-table.jade index 10f4473e..3f71abba 100644 --- a/app/partials/includes/modules/issues-table.jade +++ b/app/partials/includes/modules/issues-table.jade @@ -47,7 +47,10 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}") svg-icon="icon-arrow-down" ) - div.created-field(tg-bo-bind="issue.created_date|momentFormat:'DD MMM YYYY HH:mm'") + div.created-field( + tg-bo-bind="issue.created_date|momentFormat:'DD MMM YYYY'" + tg-bo-title="issue.created_date|momentFormat:'DD MMM YYYY HH:mm'" + ) div.assigned-field(tg-issue-assigned-to-inline-edition="issue") div.issue-assignedto(title="{{'ISSUES.TABLE.TITLE_ACTION_ASSIGNED_TO' | translate}}") From 73c7ae23ad5cf2599117c8b33fcc30876ac5dd53 Mon Sep 17 00:00:00 2001 From: Mika Andrianarijaona Date: Thu, 9 Jun 2016 17:24:49 +0300 Subject: [PATCH 060/315] fix: display current user at first in assignment popup fixes #1012 Signed-off-by: Mika Andrianarijaona --- AUTHORS.rst | 1 + CHANGELOG.md | 1 + app/coffee/modules/common/lightboxes.coffee | 1 + 3 files changed, 3 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 120d1e5b..3f669728 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -26,6 +26,7 @@ answer newbie questions, and generally made Taiga that much better: - Guilhem Got - Jordan Rinke - Miguel de la Cruz +- Mika Andrianarijaona - Pilar Esteban - Ramiro Sánchez - Ryan Swanstrom diff --git a/CHANGELOG.md b/CHANGELOG.md index c8b29a01..9f9d9827 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Errors (not found, server error, permissions and blocked project) don't change the current url. - Attachments image slider - New admin area to edit the tag colors used in your project +- Display the current user (me) at first in assignment lightbox (thanks to [@mikaoelitiana](https://github.com/mikaoelitiana)) ### Misc - Lots of small and not so small bugfixes. diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index 205b51bb..3fa982a3 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -527,6 +527,7 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic render = (selected, text) -> users = _.clone($scope.activeUsers, true) users = _.reject(users, {"id": selected.id}) if selected? + users = _.sortBy(users, (o) -> if o.id is $scope.user.id then 0 else o.id) users = _.filter(users, _.partial(filterUsers, text)) if text? ctx = { From e40a7d06e580c977e77dc59ea772a8c6aef682c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 10 Jun 2016 08:17:44 +0200 Subject: [PATCH 061/315] Remove the word 'details' from title --- app/locales/taiga/locale-en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 8904a654..a5176289 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -981,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}", - "SECTION_NAME": "User story details", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Taskboard", "TITLE_LINK_TASKBOARD": "Go to the taskboard", "TOTAL_POINTS": "total points", @@ -1198,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Task {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{taskStatus }}. Description: {{taskDescription}}", - "SECTION_NAME": "Task details", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Taskboard", "TITLE_LINK_TASKBOARD": "Go to the taskboard", "PLACEHOLDER_SUBJECT": "Type the new task subject", @@ -1247,7 +1247,7 @@ "PAGE_TITLE": "Issues - {{projectName}}", "PAGE_DESCRIPTION": "The issues list panel of the project {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Issues", - "SECTION_NAME": "Issue details", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ NEW ISSUE", "ACTION_PROMOTE_TO_US": "Promote to User Story", "PLACEHOLDER_FILTER_NAME": "Write the filter name and press enter", From 5067b0b583aa58b169eedc5c44cb5fe687e5a26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 10 Jun 2016 14:04:25 +0200 Subject: [PATCH 062/315] [i18n] Update locales --- app/locales/taiga/locale-ca.json | 27 +++++- app/locales/taiga/locale-de.json | 33 +++++-- app/locales/taiga/locale-es.json | 131 +++++++++++++++----------- app/locales/taiga/locale-fi.json | 27 +++++- app/locales/taiga/locale-fr.json | 27 +++++- app/locales/taiga/locale-it.json | 27 +++++- app/locales/taiga/locale-nl.json | 27 +++++- app/locales/taiga/locale-pl.json | 27 +++++- app/locales/taiga/locale-pt-br.json | 29 +++++- app/locales/taiga/locale-ru.json | 97 +++++++++++-------- app/locales/taiga/locale-sv.json | 27 +++++- app/locales/taiga/locale-tr.json | 27 +++++- app/locales/taiga/locale-zh-hant.json | 27 +++++- 13 files changed, 390 insertions(+), 143 deletions(-) diff --git a/app/locales/taiga/locale-ca.json b/app/locales/taiga/locale-ca.json index 5645a9b8..de55911c 100644 --- a/app/locales/taiga/locale-ca.json +++ b/app/locales/taiga/locale-ca.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Previsualitzar", "EDIT_BUTTON": "Editar", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Ajuda de Markdown" }, "PERMISIONS_CATEGORIES": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Tipus d'incidències", "ACTION_ADD": "Afegir now {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Rols - {{projectName}}", "WARNING_NO_ROLE": "Ves amb compte, cap rol en el teu projecte pot estimar punts per a les històries d'usuari", @@ -681,7 +688,8 @@ "PRIORITIES": "Prioritats", "SEVERITIES": "severitats", "TYPES": "Tipus", - "CUSTOM_FIELDS": "Camps personalitzats" + "CUSTOM_FIELDS": "Camps personalitzats", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Perfil de projecte" @@ -973,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Història d'Usuari {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estat: {{userStoryStatus }}. Completat {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tasques tancades). Punts: {{userStoryPoints}}. Descripció: {{userStoryDescription}}", - "SECTION_NAME": "Detalls de la història d'usuari", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Panell de tasques", "TITLE_LINK_TASKBOARD": "Anar a panell de tasques", "TOTAL_POINTS": "punts totals", @@ -992,6 +1000,17 @@ "ASSIGN": "Assigna història d'usuari", "NOT_ESTIMATED": "Sense estimar", "TOTAL_US_POINTS": "Punts totals d'US", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Requeriment d'equip", "CLIENT_REQUIREMENT": "Requeriment de client", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Tasca {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estat: {{taskStatus }}. Descripció: {{taskDescription}}", - "SECTION_NAME": "Detalls de la tasca", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Panell de tasques", "TITLE_LINK_TASKBOARD": "Anar a panell de tasques", "PLACEHOLDER_SUBJECT": "Afegix la descripció de la tasca", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "Incidències - {{projectName}}", "PAGE_DESCRIPTION": "El panell d'incidències de {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Incidències", - "SECTION_NAME": "Detalls d'incidència", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ NOVA INCIDÈNCIA", "ACTION_PROMOTE_TO_US": "Promocionar història d'usuari", "PLACEHOLDER_FILTER_NAME": "Escriu el filtre i pressiona Intro", diff --git a/app/locales/taiga/locale-de.json b/app/locales/taiga/locale-de.json index a4e87b4b..43949191 100644 --- a/app/locales/taiga/locale-de.json +++ b/app/locales/taiga/locale-de.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Vorschau", "EDIT_BUTTON": "Bearbeiten", "ATTACH_FILE_HELP": "Dateien per Drag & Drop auf das obere Textfeld anhängen.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Markdown syntax Hilfe" }, "PERMISIONS_CATEGORIES": { @@ -452,9 +453,9 @@ "SELECT_VIDEOCONFERENCE": "Wählen Sie ein Videokonferenzsystem", "SALT_CHAT_ROOM": "Fügen Sie ein Präfix für den Chatraum-Namen hinzu", "JITSI_CHAT_ROOM": "Jitsi", - "APPEARIN_CHAT_ROOM": "Erscheint in", - "TALKY_CHAT_ROOM": "Gesprächig", - "CUSTOM_CHAT_ROOM": "Kunde", + "APPEARIN_CHAT_ROOM": "Appear.in", + "TALKY_CHAT_ROOM": "Talky.io", + "CUSTOM_CHAT_ROOM": "Benutzerdefiniert", "URL_CHAT_ROOM": "URL Ihres Chatrooms" }, "PROJECT_PROFILE": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Ticketarten", "ACTION_ADD": "Neu hinzufügen {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Rollen - {{projectName}}", "WARNING_NO_ROLE": "Beachten Sie, keine Rolle in Ihrem Projekt wird in der Lage sein, die Punktevergabe für User-Stories einzuschätzen.", @@ -681,7 +688,8 @@ "PRIORITIES": "Prioritäten", "SEVERITIES": "Schweregrade", "TYPES": "Typen", - "CUSTOM_FIELDS": "Benutzerdefinierte Felder" + "CUSTOM_FIELDS": "Benutzerdefinierte Felder", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Projektprofil" @@ -973,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - User-Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Abgeschlossen {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} von {{userStoryTotalTasks}} Aufgaben geschlossen). Punkte: {{userStoryPoints}}. Beschreibung: {{userStoryDescription}}", - "SECTION_NAME": "User-Story Details", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Taskboard", "TITLE_LINK_TASKBOARD": "Zu Taskboard wechseln", "TOTAL_POINTS": "Gesamtpunkte", @@ -992,6 +1000,17 @@ "ASSIGN": "Zugeordnete User-Story", "NOT_ESTIMATED": "Nicht eingeschätzt", "TOTAL_US_POINTS": "User-Story-Punkte insgesamt", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Team Anforderung", "CLIENT_REQUIREMENT": "Kundenanforderung", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Aufgabe {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{taskStatus }}. Beschreibung: {{taskDescription}}", - "SECTION_NAME": "Aufgabendetails", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Taskboard", "TITLE_LINK_TASKBOARD": "Zu Taskboard wechseln", "PLACEHOLDER_SUBJECT": "Betreff...", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "Tickets - {{projectName}}", "PAGE_DESCRIPTION": "Das Ticket-Listen Panel des Projekts {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Tickets", - "SECTION_NAME": "Ticket Details", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ NEUES TICKET", "ACTION_PROMOTE_TO_US": "Zur User-Story aufwerten", "PLACEHOLDER_FILTER_NAME": "Benennen Sie den Filter und drücken Sie die Eingabetaste", diff --git a/app/locales/taiga/locale-es.json b/app/locales/taiga/locale-es.json index 08b5a14a..080ef897 100644 --- a/app/locales/taiga/locale-es.json +++ b/app/locales/taiga/locale-es.json @@ -41,9 +41,9 @@ "IOCAINE_TEXT": "¿Te sientes fuera de tu zona de confort en una tarea? Asegúrate de que los demás están al tanto de ello, marca el check de la Iocaína al editar una tarea. Igual eu era posible llegar a ser inmune a este veneno mortal a base de consumir pequeñas dosis a lo largo del tiempo, es posible conseguir mejor en lo que estás haciendo si afrontas de vez en cuando esta clase de retos!", "CLIENT_REQUIREMENT": "Requerimiento de cliente es un nuevo requisito que no se esperaba y es necesario que forme parte del proyecto.", "TEAM_REQUIREMENT": "Requerimiento del equipo es un nuevo requisito que debe existir en el proyecto pero que no conllevará ningún coste para el cliente.", - "OWNER": "Project Owner", + "OWNER": "Dueño del proyecto", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", - "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "¿Seguro que desea cerrar el modo de edición?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Este valor parece inválido.", @@ -69,8 +69,8 @@ "MAX_CHECK": "Debes seleccionar %s o menos.", "RANGE_CHECK": "Debes seleccionar de %s a %s.", "EQUAL_TO": "Este valor debe ser el mismo.", - "LINEWIDTH": "One or more lines is perhaps too long. Try to keep under %s characters.", - "PIKADAY": "Invalid date format, please use DD MMM YYYY (like 23 Mar 1984)" + "LINEWIDTH": "Una o más líneas es tal vez demasiado tiempo. Trate de mantener bajo %s caracteres.", + "PIKADAY": "Formato de fecha no válida, por favor utilice DD MMM AAAA (como 23 Mar 1984)" }, "PICKERDATE": { "FORMAT": "DD MMM YYYY", @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Previsualizar", "EDIT_BUTTON": "Editar", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Ayuda de sintaxis Markdown" }, "PERMISIONS_CATEGORIES": { @@ -322,8 +323,8 @@ "PLACEHOLDER_FIELD": "Nombre de usuario o email", "ACTION_RESET_PASSWORD": "Restablecer Contraseña", "LINK_CANCEL": "Nah, llévame de vuelta, creo que lo recordé.", - "SUCCESS_TITLE": "Check your inbox!", - "SUCCESS_TEXT": "We sent you an email with the instructions to set a new password", + "SUCCESS_TITLE": "¡Revisa tu bandeja de entrada!", + "SUCCESS_TEXT": "Te hemos enviado un correo con las instrucciones para restablecer tu contraseña", "ERROR": "Según nuestros Oompa Loompas tú no estás registrado" }, "CHANGE_PASSWORD": { @@ -359,7 +360,7 @@ "HOME": { "PAGE_TITLE": "Inicio - Taiga", "PAGE_DESCRIPTION": "Página de inicio de Taiga, con tus proyectos principales y tus historias de usuario, tareas y peticiones en progreso asignadas y las que observas.", - "EMPTY_WORKING_ON": "It feels empty, doesn't it? Start working with Taiga and you'll see here the stories, tasks and issues you are working on.", + "EMPTY_WORKING_ON": "Parece vacío, ¿no? Empieza a trabajar con Taiga y verás aquí las historias, tareas e incidentes en los que estás trabajando.", "EMPTY_WATCHING": "Sigue Historias de Usuario, Tareas y Peticiones en tus proyectos y se te notificará sobre sus cambios :)", "EMPTY_PROJECT_LIST": "Todavía no tienes ningún proyecto", "WORKING_ON_SECTION": "Trabajando en", @@ -414,8 +415,8 @@ "PAGE_TITLE": "Miembros - {{projectName}}", "ADD_BUTTON": "+ Nuevo miembro", "ADD_BUTTON_TITLE": "Añadir un nuevo miembro", - "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Unfortunately, this project has reached its limit of ({{members}}) allowed members.", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "This project has reached its limit of ({{members}}) allowed members. If you would like to increase that limit please contact the administrator." + "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Desafortunadamente, este proyecto ha alcanzado su límite de ({{members}}) miembros permitidos.", + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Este proyecto ha llegado a su límite de ({{members}}) miembros permitidos. Si se desea aumentar ese límite póngase en contacto con el administrador." }, "PROJECT_EXPORT": { "TITLE": "Exportar", @@ -448,9 +449,9 @@ "WIKI": "Wiki", "WIKI_DESCRIPTION": "Añade, modifica o borra contenido en colaboración con otros miembros. Este es el lugar adecuado para la documentación de tu proyecto.", "MEETUP": "Meet Up", - "MEETUP_DESCRIPTION": "Choose your videoconference system.", + "MEETUP_DESCRIPTION": "Elegir su sistema de videoconferencia.", "SELECT_VIDEOCONFERENCE": "Elige un sistema de videoconferencia", - "SALT_CHAT_ROOM": "Add a prefix to the chatroom name", + "SALT_CHAT_ROOM": "Agregar prefijo al nombre de la sala de chat", "JITSI_CHAT_ROOM": "Jitsi", "APPEARIN_CHAT_ROOM": "AppearIn", "TALKY_CHAT_ROOM": "Talky", @@ -474,18 +475,18 @@ "LOGO_HELP": "La imagen se escalará a 80x80px.", "CHANGE_LOGO": "Cambia el logo", "ACTION_USE_DEFAULT_LOGO": "Usar imagen por defecto", - "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects allowed by your current plan", + "MAX_PRIVATE_PROJECTS": "Has alcanzado el número máximo de proyectos privados permitidos por su actual plan", "MAX_PRIVATE_PROJECTS_MEMBERS": "The maximum number of members for private projects has been exceeded", - "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", + "MAX_PUBLIC_PROJECTS": "Desafortunadamente, usted ha alcanzado el número máximo de proyectos públicos permitidos por su plan actual", "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds your maximum number of members for public projects", - "PROJECT_OWNER": "Project owner", - "REQUEST_OWNERSHIP": "Request ownership", + "PROJECT_OWNER": "Dueño del proyecto", + "REQUEST_OWNERSHIP": "Solicitar de dueño", "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Do you want to become the new project owner?", - "REQUEST_OWNERSHIP_DESC": "Request that current project owner {{name}} transfer ownership of this project to you.", + "REQUEST_OWNERSHIP_DESC": "Solicitar que el actual project owner {{name}} te transfiera la propiedad de este proyecto a ti.", "REQUEST_OWNERSHIP_BUTTON": "Solicitud", "REQUEST_OWNERSHIP_SUCCESS": "We'll notify the project owner", - "CHANGE_OWNER": "Change owner", - "CHANGE_OWNER_SUCCESS_TITLE": "Ok, your request has been sent!", + "CHANGE_OWNER": "Cambiar dueño", + "CHANGE_OWNER_SUCCESS_TITLE": "Ok, su solicitud ha sido enviado!", "CHANGE_OWNER_SUCCESS_DESC": "We will notify you by email if the project ownership request is accepted or declined" }, "REPORTS": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Tipos de la petición", "ACTION_ADD": "Añadir nuevo {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Roles - {{projectName}}", "WARNING_NO_ROLE": "Ojo, ningún rol en tu proyecto podrá estimar historias de usuario", @@ -565,7 +572,7 @@ "COUNT_MEMBERS": "{{ role.members_count }} miembros con este rol", "TITLE_DELETE_ROLE": "Borrar Rol", "REPLACEMENT_ROLE": "Todos los usuarios con este rol serán movidos a", - "WARNING_DELETE_ROLE": "Be careful! All role estimations will be removed", + "WARNING_DELETE_ROLE": "¡Ten cuidado! Todas las estimaciones de roles serán eliminados", "ERROR_DELETE_ALL": "No puedes eliminar todos los valores", "EXTERNAL_USER": "Usuario externo" }, @@ -681,7 +688,8 @@ "PRIORITIES": "Prioridades", "SEVERITIES": "Gravedades", "TYPES": "Tipos", - "CUSTOM_FIELDS": "Atributos personalizados" + "CUSTOM_FIELDS": "Atributos personalizados", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Perfil de proyecto" @@ -695,21 +703,21 @@ "TITLE": "Servicios" }, "PROJECT_TRANSFER": { - "DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "Would you like to become the new project owner?", - "PRIVATE": "Private", - "ACCEPTED_PROJECT_OWNERNSHIP": "Congratulations! You're now the new project owner.", - "REJECTED_PROJECT_OWNERNSHIP": "OK. We'll contact the current project owner", + "DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "¿Te gustaría ser el nuevo project owner?", + "PRIVATE": "Privado", + "ACCEPTED_PROJECT_OWNERNSHIP": "¡Felicitaciones! Usted es ahora el nuevo propietario del proyecto.", + "REJECTED_PROJECT_OWNERNSHIP": "Ok. Nos pondremos en contacto con el propietario actual del proyecto", "ACCEPT": "Aceptar", - "REJECT": "Reject", + "REJECT": "Rechazar", "PROPOSE_OWNERSHIP": "{{owner}}, the current owner of the project {{project}} has asked that you become the new project owner.", - "ADD_COMMENT": "Would you like to add a comment for the project owner?", - "UNLIMITED_PROJECTS": "Unlimited", + "ADD_COMMENT": "¿Te gustaría añadir un comentario para el project owner?", + "UNLIMITED_PROJECTS": "Sin límite", "OWNER_MESSAGE": { "PRIVATE": "Please remember that you can own up to {{maxProjects}} private projects. You currently own {{currentProjects}} private projects", "PUBLIC": "Please remember that you can own up to {{maxProjects}} public projects. You currently own {{currentProjects}} public projects" }, "CANT_BE_OWNED": "At the moment you cannot become an owner of a project of this type. If you would like to become the owner of this project, please contact the administrator so they change your account settings to enable project ownership.", - "CHANGE_MY_PLAN": "Change my plan" + "CHANGE_MY_PLAN": "Cambiar mi plan" } }, "USER": { @@ -771,8 +779,8 @@ "WATCHERS_COUNTER_TITLE": "{total, plural, one{un observador} other{# observadores}}", "MEMBERS_COUNTER_TITLE": "{total, plural, one{un miembro} other{# miembros}}", "BLOCKED_PROJECT": { - "BLOCKED": "Blocked project", - "THIS_PROJECT_IS_BLOCKED": "This project is temporarily blocked", + "BLOCKED": "Proyecto bloqueado", + "THIS_PROJECT_IS_BLOCKED": "Este proyecto esta temporalmente bloqueado", "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "In order to unblock your projects, contact the administrator." }, "STATS": { @@ -840,12 +848,12 @@ "PROJECT_RESTRICTIONS": { "PROJECT_MEMBERS_DESC": "The project you are trying to import has {{members}} members, unfortunately, your current plan allows for a maximum of {{max_memberships}} members per project. If you would like to increase that limit please contact the administrator.", "PRIVATE_PROJECTS_SPACE": { - "TITLE": "Unfortunately, your current plan does not allow for additional private projects", + "TITLE": "Desafortunadamente, su plan actual no permite a los proyectos privados adicionales", "DESC": "The project you are trying to import is private. Unfortunately, your current plan does not allow for additional private projects." }, "PUBLIC_PROJECTS_SPACE": { "TITLE": "Unfortunately, your current plan does not allow for additional public projects", - "DESC": "The project you are trying to import is public. Unfortunately, your current plan does not allow additional public projects." + "DESC": "El proyecto que estás intento importar es público. Desafortunadamente, tu plan actual no permite proyectos públicos adicionales." }, "PRIVATE_PROJECTS_MEMBERS": { "TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per private project" @@ -854,12 +862,12 @@ "TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per public project." }, "PRIVATE_PROJECTS_SPACE_MEMBERS": { - "TITLE": "Unfortunately your current plan doesn't allow additional private projects or an increase of more than {{max_memberships}} members per private project", - "DESC": "The project that you are trying to import is private and has {{members}} members." + "TITLE": "Desafortunadamente tu plan actual no permite proyectos privados adicionales o un incremento de más de {{max_memberships}} miembros por proyecto privado", + "DESC": "El proyecto que estás intentando importar es privado y tiene {{members}} miembros." }, "PUBLIC_PROJECTS_SPACE_MEMBERS": { "TITLE": "Unfortunately your current plan doesn't allow additional public projects or an increase of more than {{max_memberships}} members per public project", - "DESC": "The project that you are trying to import is public and has more than {{members}} members." + "DESC": "El proyecto que estás intentando importar es público y tiene más de {{members}} miembros." } } }, @@ -890,10 +898,10 @@ "SECTION_NAME": "Eliminar cuenta de Taiga", "CONFIRM": "¿Está seguro que deseas eliminar tu cuenta de Taiga?", "NEWSLETTER_LABEL_TEXT": "No quiero recibir la newsletter nunca más.", - "CANCEL": "Back to settings", - "ACCEPT": "Delete account", + "CANCEL": "Volver a los ajustes", + "ACCEPT": "Eliminar cuenta", "BLOCK_PROJECT": "Note that all the projects you own projects will be blocked after you delete your account. If you do want a project blocked, transfer ownership to another member of each project prior to deleting your account.", - "SUBTITLE": "Sorry to see you go. We'll be here if you should ever consider us again! :(" + "SUBTITLE": "Sentimos mucho verte ir. Estaremos aquí por si alguna vez nos consideras de nuevo! :(" }, "DELETE_PROJECT": { "TITLE": "Borrar proyecto", @@ -954,26 +962,26 @@ "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { - "TITLE": "Unfortunately, this project can't be left without an owner", + "TITLE": "Por desgracia, este proyecto no puede ser dejado sin dueño", "CURRENT_USER_OWNER": { "DESC": "You are the current owner of this project. Before leaving, please transfer ownership to someone else.", - "BUTTON": "Change the project owner" + "BUTTON": "Cambiar el dueño del proyecto" }, "OTHER_USER_OWNER": { "DESC": "Unfortunately, you can't delete a member who is also the current project owner. First, please assign a new project owner.", - "BUTTON": "Request project owner change" + "BUTTON": "Solicitud del cambio del dueño del proyecto" } }, "CHANGE_OWNER": { - "TITLE": "Who do you want to be the new project owner?", - "ADD_COMMENT": "Add comment", + "TITLE": "¿A quién quiere ser el nuevo dueño del proyecto?", + "ADD_COMMENT": "Añadir comentario", "BUTTON": "Ask this project member to become the new project owner" } }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Historia de Usuario {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{userStoryStatus }}. Completado el {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tareas cerradas). Puntos: {{userStoryPoints}}. Descripción: {{userStoryDescription}}", - "SECTION_NAME": "Detalles de historia de usuario", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Panel de tareas", "TITLE_LINK_TASKBOARD": "Ir al panel de tareas", "TOTAL_POINTS": "puntos totales", @@ -992,6 +1000,17 @@ "ASSIGN": "Asignar Historia de Usuario", "NOT_ESTIMATED": "No estimada", "TOTAL_US_POINTS": "Total puntos de historia", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Requerido por el Equipo", "CLIENT_REQUIREMENT": "Requerido por el Cliente", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Tarea {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{taskStatus }}. Descripción: {{taskDescription}}", - "SECTION_NAME": "Detalles de tarea", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Panel de tareas", "TITLE_LINK_TASKBOARD": "Ir al panel de tareas", "PLACEHOLDER_SUBJECT": "Escribe el asunto de la nueva tarea", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "Peticiones - {{projectName}}", "PAGE_DESCRIPTION": "El panel de peticiones del proyecto {{projectName}}: {{projectDescription}}\n", "LIST_SECTION_NAME": "Peticiones", - "SECTION_NAME": "Detalles de petición", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ NUEVA PETICIÓN", "ACTION_PROMOTE_TO_US": "Promover a Historia de Usuario", "PLACEHOLDER_FILTER_NAME": "Escribe un nombre para el filtro y pulsa enter", @@ -1397,16 +1416,16 @@ "WIZARD": { "SECTION_TITLE_CREATE_PROJECT": "Crear Proyecto", "CREATE_PROJECT_TEXT": "Fresco y claro. ¡Es emocionante!", - "CHOOSE_TEMPLATE": "Which template fits your project best?", - "CHOOSE_TEMPLATE_TITLE": "More info about project templates", - "CHOOSE_TEMPLATE_INFO": "More info", - "PROJECT_DETAILS": "Project Details", - "PUBLIC_PROJECT": "Public Project", - "PRIVATE_PROJECT": "Private Project", + "CHOOSE_TEMPLATE": "¿Que plantilla se ajusta mejor con tu proyecto?", + "CHOOSE_TEMPLATE_TITLE": "Mas informacion acerca de la plantillas del proyecto", + "CHOOSE_TEMPLATE_INFO": "Mas información", + "PROJECT_DETAILS": "Detalles del proyecto", + "PUBLIC_PROJECT": "Proyecto público", + "PRIVATE_PROJECT": "Proyecto privado", "CREATE_PROJECT": "Crear proyecto", - "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects", - "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects", - "CHANGE_PLANS": "change plans" + "MAX_PRIVATE_PROJECTS": "Has alcanzado el número máximo de proyectos privados", + "MAX_PUBLIC_PROJECTS": "Desafortunadamente, has alcanzado el número máximo de proyectos públicos", + "CHANGE_PLANS": "cambiar planes" }, "WIKI": { "PAGE_TITLE": "{{wikiPageName}} - Wiki - {{projectName}}", @@ -1415,7 +1434,7 @@ "PLACEHOLDER_PAGE": "Escribe el contenido de tu página", "REMOVE": "Eliminar esta página del wiki", "DELETE_LIGHTBOX_TITLE": "Eliminar Página del Wiki", - "DELETE_LINK_TITLE": "Delete Wiki link", + "DELETE_LINK_TITLE": "Eliminar enlace de la Wiki", "NAVIGATION": { "SECTION_NAME": "Enlaces", "ACTION_ADD_LINK": "Añadir enlace" diff --git a/app/locales/taiga/locale-fi.json b/app/locales/taiga/locale-fi.json index 319aa4e4..960b9254 100644 --- a/app/locales/taiga/locale-fi.json +++ b/app/locales/taiga/locale-fi.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Esikatselu", "EDIT_BUTTON": "Muokkaa", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Merkintätavan ohjeet" }, "PERMISIONS_CATEGORIES": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Pyyntöjen tyypit", "ACTION_ADD": "Lisää uusi {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Roles - {{projectName}}", "WARNING_NO_ROLE": "Ole varovainen, yksikään rooli projektissasi ei voi arvioida käyttäjätarinoidesi kokoa", @@ -681,7 +688,8 @@ "PRIORITIES": "Tärkeydet", "SEVERITIES": "Vakavuudet", "TYPES": "Tyypit", - "CUSTOM_FIELDS": "Omat kentät" + "CUSTOM_FIELDS": "Omat kentät", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Projektin profiili" @@ -973,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}", - "SECTION_NAME": "Käyttäjätarinan tiedot", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Tehtävätaulu", "TITLE_LINK_TASKBOARD": "Siirry tehtävätauluun", "TOTAL_POINTS": "total points", @@ -992,6 +1000,17 @@ "ASSIGN": "Käyttäjätarinan tekijä", "NOT_ESTIMATED": "Ei arvioitu", "TOTAL_US_POINTS": "Kt pisteet yhteensä", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Tiimin vaatimus", "CLIENT_REQUIREMENT": "Asiakkaan vaatimus", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Task {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{taskStatus }}. Description: {{taskDescription}}", - "SECTION_NAME": "Tehtävän tiedot", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Tehtävätaulu", "TITLE_LINK_TASKBOARD": "Siirry tehtävätauluun", "PLACEHOLDER_SUBJECT": "Anna tehtävän aihe", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "Issues - {{projectName}}", "PAGE_DESCRIPTION": "The issues list panel of the project {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Pyynnöt", - "SECTION_NAME": "Pyynnön tiedot", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ UUSI PYYNTÖ", "ACTION_PROMOTE_TO_US": "Liitä käyttäjätarinaan", "PLACEHOLDER_FILTER_NAME": "Anna suodattimen nimi ja paina enter", diff --git a/app/locales/taiga/locale-fr.json b/app/locales/taiga/locale-fr.json index 114da4b1..f7d7cc71 100644 --- a/app/locales/taiga/locale-fr.json +++ b/app/locales/taiga/locale-fr.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Aperçu", "EDIT_BUTTON": "Modifier", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Aide sur la syntaxe Markdown" }, "PERMISIONS_CATEGORIES": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Types de tickets", "ACTION_ADD": "Ajouter un nouveau {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Rôles - {{projectName}}", "WARNING_NO_ROLE": "Attention, aucun rôle dans votre projet ne pourra estimer la valeur du point pour les récits utilisateurs", @@ -681,7 +688,8 @@ "PRIORITIES": "Priorités", "SEVERITIES": "Gravité", "TYPES": "Types", - "CUSTOM_FIELDS": "Champs personnalisés" + "CUSTOM_FIELDS": "Champs personnalisés", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Profil projet" @@ -973,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Récit utilisateur {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "État : {{userStoryStatus }}. Achevé {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} sur {{userStoryTotalTasks}} tâches fermées). Points : {{userStoryPoints}}. Description : {{userStoryDescription}}", - "SECTION_NAME": "Détails du récit utilisateur", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Tableau des tâches", "TITLE_LINK_TASKBOARD": "Aller au tableau des tâches", "TOTAL_POINTS": "total des points", @@ -992,6 +1000,17 @@ "ASSIGN": "Affecter le récit utilisateur", "NOT_ESTIMATED": "Non estimé", "TOTAL_US_POINTS": "Total des points RU", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Besoin projet", "CLIENT_REQUIREMENT": "Besoin client", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Tâche {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "État : {{taskStatus }}. Description : {{taskDescription}}", - "SECTION_NAME": "Détails de la tâche", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Tableau des tâches", "TITLE_LINK_TASKBOARD": "Aller au tableau des tâches", "PLACEHOLDER_SUBJECT": "Entrez l'objet de la nouvelle tâche", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "Tickets - {{projectName}}", "PAGE_DESCRIPTION": "Le panneau de la liste des tickets du projet {{projectName}} : {{projectDescription}}", "LIST_SECTION_NAME": "Tickets", - "SECTION_NAME": "Détails du ticket", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ NOUVEAU TICKET", "ACTION_PROMOTE_TO_US": "Promouvoir en récit utilisateur", "PLACEHOLDER_FILTER_NAME": "Écrivez le nom du filtre et appuyez sur \"Entrée\"", diff --git a/app/locales/taiga/locale-it.json b/app/locales/taiga/locale-it.json index 11b45bd6..497bd899 100644 --- a/app/locales/taiga/locale-it.json +++ b/app/locales/taiga/locale-it.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Anteprima", "EDIT_BUTTON": "Modifica", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Aiuto per la sintassi Markdown" }, "PERMISIONS_CATEGORIES": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Tipi di criticitá", "ACTION_ADD": "Aggiungi {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Ruoli - {{projectName}}", "WARNING_NO_ROLE": "Attento, nessun ruolo, all'interno del tuo progetto, potrà stimare i punti valore per le storie utente", @@ -681,7 +688,8 @@ "PRIORITIES": "priorità", "SEVERITIES": "Severitá", "TYPES": "Tipi", - "CUSTOM_FIELDS": "Campi personalizzati" + "CUSTOM_FIELDS": "Campi personalizzati", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Profilo progetto" @@ -973,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completata per il {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} di {{userStoryTotalTasks}} tasks closed). Punti: {{userStoryPoints}}. Descrizione: {{userStoryDescription}}", - "SECTION_NAME": "Dettagli della storia utente", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Pannello dei compiti", "TITLE_LINK_TASKBOARD": "Vai al pannello dei compiti", "TOTAL_POINTS": "totale punti", @@ -992,6 +1000,17 @@ "ASSIGN": "Assegna la storia utente", "NOT_ESTIMATED": "Non stimato", "TOTAL_US_POINTS": "Totale punti della storia utente", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Requisito del team", "CLIENT_REQUIREMENT": "Requisito del client", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Compiti {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Stato: {{taskStatus }}. Descrizione: {{taskDescription}}", - "SECTION_NAME": "Dettagli del compito", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Pannello dei compiti", "TITLE_LINK_TASKBOARD": "Vai al pannello dei compiti", "PLACEHOLDER_SUBJECT": "Inserisci il soggetto del nuovo compito", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "Criticitá - {{projectName}}", "PAGE_DESCRIPTION": "Il pannello con la lista dei problemi del progetto {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "problemi", - "SECTION_NAME": "Dettagli della criticitá", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ NUOVA CRITICITÁ", "ACTION_PROMOTE_TO_US": "Promuovi la storia utente", "PLACEHOLDER_FILTER_NAME": "Scrivi il nome del filtro e premi invio", diff --git a/app/locales/taiga/locale-nl.json b/app/locales/taiga/locale-nl.json index 750107c5..4fdfb575 100644 --- a/app/locales/taiga/locale-nl.json +++ b/app/locales/taiga/locale-nl.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Voorbeeld", "EDIT_BUTTON": "Bewerk", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Markdown syntax help" }, "PERMISIONS_CATEGORIES": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Issues types", "ACTION_ADD": "Voeg nieuwe {{objName}} toe" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Rollen - {{projectName}}", "WARNING_NO_ROLE": "Wees voorzichtig, geen enkele rol in je project zal de puntenwaarde van een user story kunnen estimeren", @@ -681,7 +688,8 @@ "PRIORITIES": "Prioriteiten", "SEVERITIES": "Ernst", "TYPES": "Types", - "CUSTOM_FIELDS": "Eigen velden" + "CUSTOM_FIELDS": "Eigen velden", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Project profiel" @@ -973,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Voltooid {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} van {{userStoryTotalTasks}} taken gesloten). Punten: {{userStoryPoints}}. Omschrijving: {{userStoryDescription}}", - "SECTION_NAME": "User story details", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Taakbord", "TITLE_LINK_TASKBOARD": "Ga naar het dashboard", "TOTAL_POINTS": "totaal aantal punten", @@ -992,6 +1000,17 @@ "ASSIGN": "User story toewijzen", "NOT_ESTIMATED": "Niet ingeschat", "TOTAL_US_POINTS": "Totaal US punten", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Eisen team", "CLIENT_REQUIREMENT": "Requirement van de klant", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Taak {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{taskStatus }}. Omschrijving: {{taskDescription}}", - "SECTION_NAME": "Taak details", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Taakbord", "TITLE_LINK_TASKBOARD": "Ga naar het taakbord", "PLACEHOLDER_SUBJECT": "Type het nieuwe onderwerp voor de taak", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "Issues - {{projectName}}", "PAGE_DESCRIPTION": "Het issue lijst overzicht van het project {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Issues", - "SECTION_NAME": "Issue details", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ nieuw probleem", "ACTION_PROMOTE_TO_US": "Promoveer tot User Story", "PLACEHOLDER_FILTER_NAME": "Geef de filternaam in en druk op enter", diff --git a/app/locales/taiga/locale-pl.json b/app/locales/taiga/locale-pl.json index 377fc4db..8010643a 100644 --- a/app/locales/taiga/locale-pl.json +++ b/app/locales/taiga/locale-pl.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Podgląd", "EDIT_BUTTON": "Edycja", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Składnia Markdown pomoc" }, "PERMISIONS_CATEGORIES": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Typy zgłoszeń", "ACTION_ADD": "Dodaj nowy {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Role - {{projectName}}", "WARNING_NO_ROLE": "Bez przydzielenia ról w projekcie nie ma możliwości oceniania historyjek użytkownika. Umpa Lumpy nie będą wiedziały komu wolno to zrobić :)", @@ -681,7 +688,8 @@ "PRIORITIES": "Priorytety", "SEVERITIES": "Ważność", "TYPES": "Typy", - "CUSTOM_FIELDS": "Niestandardowe pola" + "CUSTOM_FIELDS": "Niestandardowe pola", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Profil projektu" @@ -973,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Historyjka użytkownika {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Zakończono {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} z {{userStoryTotalTasks}} zadań). Punktów: {{userStoryPoints}}. Opis: {{userStoryDescription}}", - "SECTION_NAME": "Szczegóły historyjki użytkownika", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Tablica zadań", "TITLE_LINK_TASKBOARD": "Idź do listy zadań", "TOTAL_POINTS": "total points", @@ -992,6 +1000,17 @@ "ASSIGN": "Przypisz historyjkę użytkownika", "NOT_ESTIMATED": "Nie oszacowane", "TOTAL_US_POINTS": "Łącznie punktów", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Wymaganie zespołu", "CLIENT_REQUIREMENT": "Wymaganie klienta", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Zadanie {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{taskStatus }}. Opis: {{taskDescription}}", - "SECTION_NAME": "Szczegóły zadania", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Tablica zadań", "TITLE_LINK_TASKBOARD": "Idź do listy zadań", "PLACEHOLDER_SUBJECT": "Wpisz temat zadania", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "Zgłoszenia - {{projectName}}", "PAGE_DESCRIPTION": "Lista zgłoszeń w projekcie {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Zgłoszenia", - "SECTION_NAME": "Szczegóły zgłoszenia", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ NOWE ZGŁOSZENIE", "ACTION_PROMOTE_TO_US": "Awansuj na historyjkę użytkownika", "PLACEHOLDER_FILTER_NAME": "Wpisz nazwę filtru i kliknij enter", diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index b888bba8..024a91bf 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Pré Visualizar", "EDIT_BUTTON": "Editar", "ATTACH_FILE_HELP": "Anexe arquivos arrastando e soltando na área de texto acima.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Ajuda de sintaxe markdown" }, "PERMISIONS_CATEGORIES": { @@ -414,7 +415,7 @@ "PAGE_TITLE": "Filiados - {{projectName}}", "ADD_BUTTON": "+ Novo Membro", "ADD_BUTTON_TITLE": "Adicionar novo membro", - "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Unfortunately, this project has reached its limit of ({{members}}) allowed members.", + "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Infelizmente, este projeto atingiu o número máximo de ({{membros}}) membros permitidos.", "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "This project has reached its limit of ({{members}}) allowed members. If you would like to increase that limit please contact the administrator." }, "PROJECT_EXPORT": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Tipos de problemas", "ACTION_ADD": "Adicionar novo {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Funções - {{projectName}}", "WARNING_NO_ROLE": "Seja cuidadoso, nenhuma função em seu projeto será capaz de estimar o valor dos pontos para as histórias de usuários", @@ -681,7 +688,8 @@ "PRIORITIES": "Prioridades", "SEVERITIES": "Gravidades", "TYPES": "Tipos", - "CUSTOM_FIELDS": "Campos personalizados" + "CUSTOM_FIELDS": "Campos personalizados", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Perfil do Projeto" @@ -973,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - História de Usuário {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{userStoryStatus }}. Completos {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tarefas encerradas). Pontos: {{userStoryPoints}}. Descrição: {{userStoryDescription}}", - "SECTION_NAME": "Detalhes da História de Usuário", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Quadro de Tarefas", "TITLE_LINK_TASKBOARD": "Ir para o quadro de tarefas", "TOTAL_POINTS": "total de pontos", @@ -992,6 +1000,17 @@ "ASSIGN": "Atribuir História de Usuário", "NOT_ESTIMATED": "Não estimado", "TOTAL_US_POINTS": "Total de pontos de histórias", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Requisitos da Equipe", "CLIENT_REQUIREMENT": "Requisitos do Cliente", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Tarefa {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{taskStatus }}. Descrição: {{taskDescription}}", - "SECTION_NAME": "Detalhes da Tarefa", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Quadro de Tarefas", "TITLE_LINK_TASKBOARD": "Ir para o quadro de tarefas", "PLACEHOLDER_SUBJECT": "Digite um novo titulo para tarefa", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "Problemas - {{projectName}}", "PAGE_DESCRIPTION": "O painel de problemas do projeto {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Problemas", - "SECTION_NAME": "Detalhes do problema", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ NOVO PROBLEMA", "ACTION_PROMOTE_TO_US": "Promover para História de Usuário", "PLACEHOLDER_FILTER_NAME": "Digite o nome do filtro e pressione Enter", diff --git a/app/locales/taiga/locale-ru.json b/app/locales/taiga/locale-ru.json index 89e9c995..dacb6949 100644 --- a/app/locales/taiga/locale-ru.json +++ b/app/locales/taiga/locale-ru.json @@ -41,13 +41,13 @@ "IOCAINE_TEXT": "Чувствуете, что задание берет верх над вами? Дайте другим знать об этом, нажав на \"Иокаин\", когда редактируете задание. Возможно стать неуязвимым к этому (выдуманному) смертельном яду, потребляя небольшие количества время от времени, так же как возможно стать лучше в том, что вы делаете, временами беря на себя дополнительные препятствия!", "CLIENT_REQUIREMENT": "Client requirement is new requirement that was not previously expected and it is required to be part of the project", "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", - "OWNER": "Project Owner", + "OWNER": "Владелец проекта", "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Кажется, это значение некорректно.", - "TYPE_EMAIL": "Это значение должно быть корректным email-адресом.", + "TYPE_EMAIL": "Значение должно быть корректной электронной почтой.", "TYPE_URL": "Это значение должно быть корректным URL-адресом.", "TYPE_URLSTRICT": "Это значение должно быть корректным URL-адресом.", "TYPE_NUMBER": "Это значение должно быть правильным числом", @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Предварительный просмотр", "EDIT_BUTTON": "Редактировать", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Помощь по синтаксису Markdown" }, "PERMISIONS_CATEGORIES": { @@ -287,7 +288,7 @@ }, "LOGIN_COMMON": { "HEADER": "У меня уже есть логин в Taiga", - "PLACEHOLDER_AUTH_NAME": "Логин или email (с учетом регистра)", + "PLACEHOLDER_AUTH_NAME": "Имя пользователя или электронная почта (с учётом регистра)", "LINK_FORGOT_PASSWORD": "Забыли?", "TITLE_LINK_FORGOT_PASSWORD": "Вы забыли свой пароль?", "ACTION_ENTER": "Ввод", @@ -295,7 +296,7 @@ "PLACEHOLDER_AUTH_PASSWORD": "Пароль (чувствителен к регистру)" }, "LOGIN_FORM": { - "ERROR_AUTH_INCORRECT": "Oompa Loompas считает, что Ваш логин, email или пароль неправильный.", + "ERROR_AUTH_INCORRECT": "Oompa Loompas считает, что ваше имя пользователя, электронная почта или пароль неправильные.", "SUCCESS": "Oompa Loompas счастлив, добро пожаловать в Тайгу!" }, "REGISTER": { @@ -306,7 +307,7 @@ "TITLE": "Зарегистрируйте аккаунт Taiga (бесплатно)", "PLACEHOLDER_NAME": "Выберите имя учётной записи (с учётом регистра)", "PLACEHOLDER_FULL_NAME": "Введите Ваше полное имя", - "PLACEHOLDER_EMAIL": "Ваш email", + "PLACEHOLDER_EMAIL": "Ваша электронная почта", "PLACEHOLDER_PASSWORD": "Задайте новый пароль (с учетом регистра)", "ACTION_SIGN_UP": "Зарегистрироваться", "TITLE_LINK_LOGIN": "Войти", @@ -318,12 +319,12 @@ }, "FORGOT_PASSWORD_FORM": { "TITLE": "Упс, забыли пароль?", - "SUBTITLE": "Введите Ваш логин или email для получения нового пароля", - "PLACEHOLDER_FIELD": "Логин или e-mail", + "SUBTITLE": "Введите ваше имя пользователя или электронную почту, чтобы получить новый пароль", + "PLACEHOLDER_FIELD": "Имя пользователя или электронная почта", "ACTION_RESET_PASSWORD": "Сбросить пароль", "LINK_CANCEL": "Не, давай назад, думаю я вспомню.", - "SUCCESS_TITLE": "Check your inbox!", - "SUCCESS_TEXT": "We sent you an email with the instructions to set a new password", + "SUCCESS_TITLE": "Проверьте Вашу почту!", + "SUCCESS_TEXT": "Мы отправили вам письмо с инструкциями по восстановлению пароля", "ERROR": "Умпа-Лумпы говорят, что вы еще не зарегистрированы." }, "CHANGE_PASSWORD": { @@ -425,7 +426,7 @@ "LOADING_TITLE": "Мы создали ваш файл резервной копии", "DUMP_READY": "Файл резервной копии готов!", "LOADING_MESSAGE": "Пожалуйста, не закрывайте эту страницу", - "ASYNC_MESSAGE": "Мы отправим вам email когда будет готово.", + "ASYNC_MESSAGE": "Мы отправим вам письмо когда будет готово.", "SYNC_MESSAGE": "Если загрузка не начинается самостоятельно, нажмите здесь.", "ERROR": "У Oompa Loompas возникли проблемы при создании резервной копии. Повторите еще раз.", "ERROR_BUSY": "Извините, Oompa Loompas очень загружен сейчас. Повторите через несколько минут.", @@ -437,7 +438,7 @@ "DISABLE": "Выключить", "BACKLOG": "Список задач", "BACKLOG_DESCRIPTION": "Управляйте пользовательскими историями, чтобы поддерживать организованное видение важных и приоритетных задач.", - "NUMBER_SPRINTS": "Expected number of sprints", + "NUMBER_SPRINTS": "Ожидаемое количество спринтов", "NUMBER_SPRINTS_HELP": "0 for an undetermined number", "NUMBER_US_POINTS": "Expected total of story points", "NUMBER_US_POINTS_HELP": "0 for an undetermined number", @@ -448,9 +449,9 @@ "WIKI": "Вики", "WIKI_DESCRIPTION": "Добавляйте, изменяйте или удаляйте контент совместно с остальными. Это самое правильное место для документации вашего проекта.", "MEETUP": "Созвониться", - "MEETUP_DESCRIPTION": "Choose your videoconference system.", + "MEETUP_DESCRIPTION": "Выберите Вашу систему видеоконференций", "SELECT_VIDEOCONFERENCE": "Выберите систему видеоконференций", - "SALT_CHAT_ROOM": "Add a prefix to the chatroom name", + "SALT_CHAT_ROOM": "Добавить префикс к имени чата", "JITSI_CHAT_ROOM": "Jitsi", "APPEARIN_CHAT_ROOM": "AppearIn", "TALKY_CHAT_ROOM": "Talky", @@ -472,19 +473,19 @@ "PRIVATE_OR_PUBLIC": "В чём разница между публичными и приватными проектами?", "DELETE": "Удалить проект", "LOGO_HELP": "Изображение будет отмасштабировано до 80x80px.", - "CHANGE_LOGO": "Change logo", + "CHANGE_LOGO": "Изменить лого", "ACTION_USE_DEFAULT_LOGO": "Использовать картинку по умолчанию", "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects allowed by your current plan", "MAX_PRIVATE_PROJECTS_MEMBERS": "The maximum number of members for private projects has been exceeded", "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds your maximum number of members for public projects", - "PROJECT_OWNER": "Project owner", + "PROJECT_OWNER": "Владелец проекта", "REQUEST_OWNERSHIP": "Request ownership", - "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Do you want to become the new project owner?", + "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Хотите стать новым владельцем проекта?", "REQUEST_OWNERSHIP_DESC": "Request that current project owner {{name}} transfer ownership of this project to you.", "REQUEST_OWNERSHIP_BUTTON": "Запрос", - "REQUEST_OWNERSHIP_SUCCESS": "We'll notify the project owner", - "CHANGE_OWNER": "Change owner", + "REQUEST_OWNERSHIP_SUCCESS": "Мы уведомим владельца проекта", + "CHANGE_OWNER": "Сменить владельца", "CHANGE_OWNER_SUCCESS_TITLE": "Ok, your request has been sent!", "CHANGE_OWNER_SUCCESS_DESC": "We will notify you by email if the project ownership request is accepted or declined" }, @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Типы запросов", "ACTION_ADD": "Добавить новый" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Роли - {{projectName}}", "WARNING_NO_ROLE": "Осторожнее: ни с какими ролями на вашем проекте участники не смогут оценить очки для пользовательских историй.", @@ -681,7 +688,8 @@ "PRIORITIES": "Приоритет", "SEVERITIES": "Степени важности", "TYPES": "Типы", - "CUSTOM_FIELDS": "Собственные поля" + "CUSTOM_FIELDS": "Собственные поля", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Профиль проекта" @@ -697,8 +705,8 @@ "PROJECT_TRANSFER": { "DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "Would you like to become the new project owner?", "PRIVATE": "Private", - "ACCEPTED_PROJECT_OWNERNSHIP": "Congratulations! You're now the new project owner.", - "REJECTED_PROJECT_OWNERNSHIP": "OK. We'll contact the current project owner", + "ACCEPTED_PROJECT_OWNERNSHIP": "Поздравляем! Вы новый владелец проекта.", + "REJECTED_PROJECT_OWNERNSHIP": "Хорошо. Мы свяжемся с текущим владельцем проекта", "ACCEPT": "Принимаю", "REJECT": "Reject", "PROPOSE_OWNERSHIP": "{{owner}}, the current owner of the project {{project}} has asked that you become the new project owner.", @@ -771,8 +779,8 @@ "WATCHERS_COUNTER_TITLE": "{total, plural, one{один наблюдатель} other{# наблюдателя (-ей)}}", "MEMBERS_COUNTER_TITLE": "{total, plural, one{one member} other{# members}}", "BLOCKED_PROJECT": { - "BLOCKED": "Blocked project", - "THIS_PROJECT_IS_BLOCKED": "This project is temporarily blocked", + "BLOCKED": "Заблокированный проект", + "THIS_PROJECT_IS_BLOCKED": "Этот проект временно заблокирован", "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "In order to unblock your projects, contact the administrator." }, "STATS": { @@ -890,8 +898,8 @@ "SECTION_NAME": "Удалить аккаунт", "CONFIRM": "Вы уверены, что хотите удалить ваш аккаунт?", "NEWSLETTER_LABEL_TEXT": "Я больше не хочу получать вашу новостную рассылку", - "CANCEL": "Back to settings", - "ACCEPT": "Delete account", + "CANCEL": "Вернуться к настройкам", + "ACCEPT": "Удалить аккаунт", "BLOCK_PROJECT": "Note that all the projects you own projects will be blocked after you delete your account. If you do want a project blocked, transfer ownership to another member of each project prior to deleting your account.", "SUBTITLE": "Sorry to see you go. We'll be here if you should ever consider us again! :(" }, @@ -949,7 +957,7 @@ }, "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Необязательно) Добавьте персональный текст в приглашение. Скажите что-нибудь приятное вашим новым участникам ;-)", - "PLACEHOLDER_TYPE_EMAIL": "Укажите e-mail", + "PLACEHOLDER_TYPE_EMAIL": "Введите электронную почту", "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." }, @@ -957,23 +965,23 @@ "TITLE": "Unfortunately, this project can't be left without an owner", "CURRENT_USER_OWNER": { "DESC": "You are the current owner of this project. Before leaving, please transfer ownership to someone else.", - "BUTTON": "Change the project owner" + "BUTTON": "Сменить владельца проекта" }, "OTHER_USER_OWNER": { "DESC": "Unfortunately, you can't delete a member who is also the current project owner. First, please assign a new project owner.", - "BUTTON": "Request project owner change" + "BUTTON": "Запрос смены владельца проекта" } }, "CHANGE_OWNER": { - "TITLE": "Who do you want to be the new project owner?", - "ADD_COMMENT": "Add comment", - "BUTTON": "Ask this project member to become the new project owner" + "TITLE": "Кого вы хотите назначить новым владельцем проекта?", + "ADD_COMMENT": "Добавить комментарий", + "BUTTON": "Предложить участнику проекта стать его новым владельцем" } }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Пользовательская История {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Статус: {{userStoryStatus }}. Выполнено {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} из {{userStoryTotalTasks}} задач). Очки: {{userStoryPoints}}. Описание: {{userStoryDescription}}", - "SECTION_NAME": "Детали пользовательских историй", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Панель задач", "TITLE_LINK_TASKBOARD": "Перейти к панели задач", "TOTAL_POINTS": "общее число очков", @@ -992,6 +1000,17 @@ "ASSIGN": "Поручить пользовательскую историю", "NOT_ESTIMATED": "Не оценено", "TOTAL_US_POINTS": "Всего очков за ПИ", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Требование от Команды", "CLIENT_REQUIREMENT": "Требование клиента", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Задача {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Статус: {{taskStatus }}. Описание: {{taskDescription}}", - "SECTION_NAME": "Детали задачи", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Панель задач", "TITLE_LINK_TASKBOARD": "Перейти к панели задач", "PLACEHOLDER_SUBJECT": "Укажите новое название задачи", @@ -1218,17 +1237,17 @@ "SUCCESS": "Oompa Loompas удалил ваш аккаунт" }, "CHANGE_EMAIL_FORM": { - "TITLE": "Изменить e-mail", + "TITLE": "Сменить вашу электронную почту", "SUBTITLE": "Ещё один клик и Ваш email будет обновлён!", "PLACEHOLDER_INPUT_TOKEN": "изменить идентификатор email", - "ACTION_CHANGE_EMAIL": "изменить почту", - "SUCCESS": "Oompa Loompas обновил ваш e-mail" + "ACTION_CHANGE_EMAIL": "Сменить электронную почту", + "SUCCESS": "Наш Oompa Loompas обновил вашу электронную почту" }, "ISSUES": { "PAGE_TITLE": "Запросы - {{projectName}}", "PAGE_DESCRIPTION": "Панель запросов проекта {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Запросы", - "SECTION_NAME": "Детали запроса", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+НОВЫЙ ЗАПРОС", "ACTION_PROMOTE_TO_US": "Повысить до пользовательской истории", "PLACEHOLDER_FILTER_NAME": "Введите название фильтра и нажмите \"ввод\"", @@ -1383,7 +1402,7 @@ "CHANGE_PHOTO": "Изменить фото", "FIELD": { "USERNAME": "Имя пользователя", - "EMAIL": "Email", + "EMAIL": "Электронная почта", "FULL_NAME": "Полное имя", "PLACEHOLDER_FULL_NAME": "Полное имя (например, Игорь Николаев)", "BIO": "Биография (не более 210 символов)", @@ -1399,7 +1418,7 @@ "CREATE_PROJECT_TEXT": "Свежий и чистый! Так здóрово!", "CHOOSE_TEMPLATE": "Which template fits your project best?", "CHOOSE_TEMPLATE_TITLE": "More info about project templates", - "CHOOSE_TEMPLATE_INFO": "More info", + "CHOOSE_TEMPLATE_INFO": "Больше инфо", "PROJECT_DETAILS": "Project Details", "PUBLIC_PROJECT": "Public Project", "PRIVATE_PROJECT": "Private Project", diff --git a/app/locales/taiga/locale-sv.json b/app/locales/taiga/locale-sv.json index 1d63b9da..f860f7a4 100644 --- a/app/locales/taiga/locale-sv.json +++ b/app/locales/taiga/locale-sv.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Förhandsvisa", "EDIT_BUTTON": "Redigera", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Hjälp för markeringssyntax" }, "PERMISIONS_CATEGORIES": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Ärendetyper", "ACTION_ADD": "Lägg till ny {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Roller - {{projectName}}", "WARNING_NO_ROLE": "Var försiktig. Inga roller i ditt projekt kan estimera poängvärden för användarhistorier", @@ -681,7 +688,8 @@ "PRIORITIES": "Prioritet", "SEVERITIES": "Allvarsgrad", "TYPES": "Typ", - "CUSTOM_FIELDS": "Anpassade fält" + "CUSTOM_FIELDS": "Anpassade fält", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Projektprofil" @@ -973,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Användarhistorier {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. avslutad{{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} av {{userStoryTotalTasks}} tasks closed). Poäng: {{userStoryPoints}}. Beskrivning: {{userStoryDescription}}", - "SECTION_NAME": "Detaljer för användarhistorier", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Uppgiftstavla", "TITLE_LINK_TASKBOARD": "Gå till uppgiftstavlan", "TOTAL_POINTS": "totalpoäng", @@ -992,6 +1000,17 @@ "ASSIGN": "Lägg till användarhistorie", "NOT_ESTIMATED": "Ej beräknad", "TOTAL_US_POINTS": "Total US-poäng", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Teamets behov", "CLIENT_REQUIREMENT": "Kräver beställare", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Uppgift {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{taskStatus }}. Beskrivning: {{taskDescription}}", - "SECTION_NAME": "Detaljer för uppgiften", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Uppgiftstavla", "TITLE_LINK_TASKBOARD": "Gå till uppgiftstavlan", "PLACEHOLDER_SUBJECT": "Skriv in den nya uppgiftens titel", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "Ärenden - {{projectName}}", "PAGE_DESCRIPTION": "Ärenden som listas för projektet {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Ärenden", - "SECTION_NAME": "Detaljer för ärenden ", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ NYTT ÄRENDE", "ACTION_PROMOTE_TO_US": "Flytta till användarhistorie", "PLACEHOLDER_FILTER_NAME": "Skriv filternamnet och tryck på ", diff --git a/app/locales/taiga/locale-tr.json b/app/locales/taiga/locale-tr.json index 97ce06a5..7733a4db 100644 --- a/app/locales/taiga/locale-tr.json +++ b/app/locales/taiga/locale-tr.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "Ön izleme", "EDIT_BUTTON": "Düzenle", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Markdown yazım kılavuzu" }, "PERMISIONS_CATEGORIES": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "Sorun türleri", "ACTION_ADD": "Ekle yeni {{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "Roller - {{projectName}}", "WARNING_NO_ROLE": "Dikkat edin, projenizdeki rollerden hiçbiri kullanıcı hikayeleri için puan değeri kestirimi yapma yetkisine sahip değil.", @@ -681,7 +688,8 @@ "PRIORITIES": "Öncelikler", "SEVERITIES": "Önem Dereceleri", "TYPES": "Tipler", - "CUSTOM_FIELDS": "Özel alanlar" + "CUSTOM_FIELDS": "Özel alanlar", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Proje Profili" @@ -973,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Kullanıcı Hikayesi {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Durum: {{userStoryStatus }}. Tamamlanan {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Puanlar: {{userStoryPoints}}. Tanım: {{userStoryDescription}}", - "SECTION_NAME": "Kullanıcı hikayesi detayları", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "Görev Panosu", "TITLE_LINK_TASKBOARD": "Görev panosuna git", "TOTAL_POINTS": "toplam puanlar", @@ -992,6 +1000,17 @@ "ASSIGN": "Kullanıcı Hikayesini Ata", "NOT_ESTIMATED": "Kestirim yapılmamış", "TOTAL_US_POINTS": "Toplam KH puanları", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "Takım Gereksinimi", "CLIENT_REQUIREMENT": "İstemci Gereksinimi", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Görev {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Durum: {{taskStatus }}. Tanım: {{taskDescription}}", - "SECTION_NAME": "Görev detayları", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "Görev Panosu", "TITLE_LINK_TASKBOARD": "Görev panosuna git", "PLACEHOLDER_SUBJECT": "Yeni görev konusunu gir", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "Sorunlar - {{projectName}}", "PAGE_DESCRIPTION": "{{projectName}} projesinin sorun listesi paneli: {{projectDescription}}", "LIST_SECTION_NAME": "Sorunlar", - "SECTION_NAME": "Sorun detayları", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ YENİ SORUN", "ACTION_PROMOTE_TO_US": "Kullanıcı Hikayesine Terfi Ettir", "PLACEHOLDER_FILTER_NAME": "Filtre adı yazın ve enter a basın", diff --git a/app/locales/taiga/locale-zh-hant.json b/app/locales/taiga/locale-zh-hant.json index 2abcfee7..d1ca1d19 100644 --- a/app/locales/taiga/locale-zh-hant.json +++ b/app/locales/taiga/locale-zh-hant.json @@ -228,6 +228,7 @@ "PREVIEW_BUTTON": "預視 ", "EDIT_BUTTON": "編輯", "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", "MARKDOWN_HELP": "Markdown 語法協助" }, "PERMISIONS_CATEGORIES": { @@ -556,6 +557,12 @@ "ISSUE_TITLE": "問題類型", "ACTION_ADD": "新增{{objName}}" }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Tags", + "SUBTITLE": "View and edit the color of your user stories", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + }, "ROLES": { "PAGE_TITLE": "角色- {{projectName}}", "WARNING_NO_ROLE": "注意,你的專案中無角色可以評估使用者故事的點數", @@ -681,7 +688,8 @@ "PRIORITIES": "優先性", "SEVERITIES": "急迫性", "TYPES": "類型", - "CUSTOM_FIELDS": "客製化欄位" + "CUSTOM_FIELDS": "客製化欄位", + "TAGS": "Tags" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "專案檔案" @@ -973,7 +981,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - 使用者故事 {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "狀態: {{userStoryStatus }}.已完成 {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). 點數: {{userStoryPoints}}. 描述: {{userStoryDescription}}", - "SECTION_NAME": "使用者故事細節", + "SECTION_NAME": "User story", "LINK_TASKBOARD": "任務板", "TITLE_LINK_TASKBOARD": "到任務板去", "TOTAL_POINTS": "所有點數", @@ -992,6 +1000,17 @@ "ASSIGN": "指派使用者故事", "NOT_ESTIMATED": "無預估", "TOTAL_US_POINTS": "全部使用者故事點數", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "More info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, "FIELDS": { "TEAM_REQUIREMENT": "團隊要求", "CLIENT_REQUIREMENT": "客戶要求", @@ -1179,7 +1198,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - 任務 {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "狀態: {{taskStatus }}.描述: {{taskDescription}}", - "SECTION_NAME": "任務細節", + "SECTION_NAME": "Task", "LINK_TASKBOARD": "任務板", "TITLE_LINK_TASKBOARD": "到任務板去", "PLACEHOLDER_SUBJECT": "鍵入新任務主旨", @@ -1228,7 +1247,7 @@ "PAGE_TITLE": "問題 - {{projectName}}", "PAGE_DESCRIPTION": " {{projectName}}的問題清單看版: {{projectDescription}}", "LIST_SECTION_NAME": "問題 ", - "SECTION_NAME": "問題細節", + "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ 新問題 ", "ACTION_PROMOTE_TO_US": "提昇到使用者故事", "PLACEHOLDER_FILTER_NAME": "寫入過濾器名稱後按下enter ", From a30f86ac94975dfb2acf4089c6282028c3657b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 10 Jun 2016 14:58:12 +0200 Subject: [PATCH 063/315] Fix minimized task edit icon position --- app/styles/components/kanban-task.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/styles/components/kanban-task.scss b/app/styles/components/kanban-task.scss index 121bcfb2..12a0de21 100644 --- a/app/styles/components/kanban-task.scss +++ b/app/styles/components/kanban-task.scss @@ -217,4 +217,8 @@ .kanban-tag { border-top: .2rem solid; } + .edit-us { + bottom: .2rem; + right: .5rem; + } } From 775e078ae21eb62257100e12bc9d3b64601c4b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 5 May 2016 17:58:20 +0200 Subject: [PATCH 064/315] Refactor comments & history, edit comments --- CHANGELOG.md | 2 + app/coffee/app.coffee | 1 + app/coffee/modules/admin/roles.coffee | 3 + app/coffee/modules/common/history.coffee | 507 ------------------ app/coffee/modules/common/wisiwyg.coffee | 2 +- app/coffee/modules/resources/history.coffee | 19 + app/locales/taiga/locale-en.json | 37 +- .../comments/comment.controller.coffee | 64 +++ .../comments/comment.controller.spec.coffee | 134 +++++ .../history/comments/comment.directive.coffee | 44 ++ app/modules/history/comments/comment.jade | 108 ++++ app/modules/history/comments/comment.scss | 159 ++++++ .../comments/comments.controller.coffee | 28 + .../comments/comments.controller.spec.coffee | 41 ++ .../comments/comments.directive.coffee | 48 ++ app/modules/history/comments/comments.jade | 37 ++ ...comment-history-lightbox.controller.coffee | 37 ++ ...nt-history-lightbox.controller.spec.coffee | 63 +++ .../comment-history-lightbox.directive.coffee | 40 ++ .../comment-history-lightbox.jade | 9 + .../comment-history-lightbox.scss | 13 + .../history-entry.directive.coffee | 31 ++ .../history-lightbox/history-entry.jade | 19 + .../history-lightbox/history-entry.scss | 62 +++ .../history-tabs.directive.coffee | 37 ++ .../history/history-tabs/history-tabs.jade | 41 ++ .../history/history-tabs/history-tabs.scss | 32 ++ app/modules/history/history.controller.coffee | 91 ++++ .../history/history.controller.spec.coffee | 214 ++++++++ app/modules/history/history.directive.coffee | 42 ++ app/modules/history/history.jade | 29 + app/modules/history/history.module.coffee | 20 + .../history/history-diff.controller.coffee | 34 ++ .../history-diff.controller.spec.coffee | 43 ++ .../history/history-diff.directive.coffee | 38 ++ app/modules/history/history/history-diff.jade | 61 +++ .../history/history-templates/blocked.jade | 9 + .../history-templates/client-requirement.jade | 9 + .../history-templates/history-assigned.jade | 9 + .../history-attachments.jade | 37 ++ .../history-custom-attributes.jade | 19 + .../history-description.jade | 12 + .../history-templates/history-milestone.jade | 11 + .../history-templates/history-points.jade | 11 + .../history-templates/history-status.jade | 9 + .../history-templates/history-subject.jade | 9 + .../history-templates/history-tags.jade | 8 + .../history-templates/history-templates.scss | 25 + .../history-templates/team-requirement.jade | 9 + .../history/history/history.directive.coffee | 33 ++ app/modules/history/history/history.jade | 19 + app/modules/history/history/history.scss | 49 ++ .../services/check-permissions.service.coffee | 4 +- app/partials/common/components/wysiwyg.jade | 2 +- .../common/history/history-activity.jade | 41 -- .../common/history/history-base-entries.jade | 6 - app/partials/common/history/history-base.jade | 35 -- .../history/history-change-attachment.jade | 16 - .../common/history/history-change-diff.jade | 6 - .../history/history-change-generic.jade | 12 - .../common/history/history-change-list.jade | 17 - .../common/history/history-change-points.jade | 14 - .../history/history-deleted-comment.jade | 18 - app/partials/issue/issues-detail.jade | 8 +- app/partials/task/task-detail.jade | 9 +- app/partials/us/us-detail.jade | 7 +- app/styles/components/wysiwyg.scss | 4 + app/styles/modules/common/history.scss | 278 ---------- e2e/helpers/detail-helper.js | 98 +++- e2e/shared/detail.js | 34 +- e2e/suites/issues/issue-detail.e2e.js | 3 +- e2e/suites/tasks/task-detail.e2e.js | 2 +- .../user-stories/user-story-detail.e2e.js | 2 +- 73 files changed, 2078 insertions(+), 1006 deletions(-) delete mode 100644 app/coffee/modules/common/history.coffee create mode 100644 app/modules/history/comments/comment.controller.coffee create mode 100644 app/modules/history/comments/comment.controller.spec.coffee create mode 100644 app/modules/history/comments/comment.directive.coffee create mode 100644 app/modules/history/comments/comment.jade create mode 100644 app/modules/history/comments/comment.scss create mode 100644 app/modules/history/comments/comments.controller.coffee create mode 100644 app/modules/history/comments/comments.controller.spec.coffee create mode 100644 app/modules/history/comments/comments.directive.coffee create mode 100644 app/modules/history/comments/comments.jade create mode 100644 app/modules/history/history-lightbox/comment-history-lightbox.controller.coffee create mode 100644 app/modules/history/history-lightbox/comment-history-lightbox.controller.spec.coffee create mode 100644 app/modules/history/history-lightbox/comment-history-lightbox.directive.coffee create mode 100644 app/modules/history/history-lightbox/comment-history-lightbox.jade create mode 100644 app/modules/history/history-lightbox/comment-history-lightbox.scss create mode 100644 app/modules/history/history-lightbox/history-entry.directive.coffee create mode 100644 app/modules/history/history-lightbox/history-entry.jade create mode 100644 app/modules/history/history-lightbox/history-entry.scss create mode 100644 app/modules/history/history-tabs/history-tabs.directive.coffee create mode 100644 app/modules/history/history-tabs/history-tabs.jade create mode 100644 app/modules/history/history-tabs/history-tabs.scss create mode 100644 app/modules/history/history.controller.coffee create mode 100644 app/modules/history/history.controller.spec.coffee create mode 100644 app/modules/history/history.directive.coffee create mode 100644 app/modules/history/history.jade create mode 100644 app/modules/history/history.module.coffee create mode 100644 app/modules/history/history/history-diff.controller.coffee create mode 100644 app/modules/history/history/history-diff.controller.spec.coffee create mode 100644 app/modules/history/history/history-diff.directive.coffee create mode 100644 app/modules/history/history/history-diff.jade create mode 100644 app/modules/history/history/history-templates/blocked.jade create mode 100644 app/modules/history/history/history-templates/client-requirement.jade create mode 100644 app/modules/history/history/history-templates/history-assigned.jade create mode 100644 app/modules/history/history/history-templates/history-attachments.jade create mode 100644 app/modules/history/history/history-templates/history-custom-attributes.jade create mode 100644 app/modules/history/history/history-templates/history-description.jade create mode 100644 app/modules/history/history/history-templates/history-milestone.jade create mode 100644 app/modules/history/history/history-templates/history-points.jade create mode 100644 app/modules/history/history/history-templates/history-status.jade create mode 100644 app/modules/history/history/history-templates/history-subject.jade create mode 100644 app/modules/history/history/history-templates/history-tags.jade create mode 100644 app/modules/history/history/history-templates/history-templates.scss create mode 100644 app/modules/history/history/history-templates/team-requirement.jade create mode 100644 app/modules/history/history/history.directive.coffee create mode 100644 app/modules/history/history/history.jade create mode 100644 app/modules/history/history/history.scss delete mode 100644 app/partials/common/history/history-activity.jade delete mode 100644 app/partials/common/history/history-base-entries.jade delete mode 100644 app/partials/common/history/history-base.jade delete mode 100644 app/partials/common/history/history-change-attachment.jade delete mode 100644 app/partials/common/history/history-change-diff.jade delete mode 100644 app/partials/common/history/history-change-generic.jade delete mode 100644 app/partials/common/history/history-change-list.jade delete mode 100644 app/partials/common/history/history-change-points.jade delete mode 100644 app/partials/common/history/history-deleted-comment.jade delete mode 100644 app/styles/modules/common/history.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f9d9827..61c8a86f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - Attachments image slider - New admin area to edit the tag colors used in your project - Display the current user (me) at first in assignment lightbox (thanks to [@mikaoelitiana](https://github.com/mikaoelitiana)) +- Add a new permissions to allow add comments instead of use the existent modify permission for this purpose. +- Ability to edit comments, view edition history and redesign comments module UI ### Misc - Lots of small and not so small bugfixes. diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 9db8b891..e5d828bf 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -771,6 +771,7 @@ modules = [ "taigaUserTimeline", "taigaExternalApps", "taigaDiscover", + "taigaHistory", # template cache "templates", diff --git a/app/coffee/modules/admin/roles.coffee b/app/coffee/modules/admin/roles.coffee index a8142907..6262fcbc 100644 --- a/app/coffee/modules/admin/roles.coffee +++ b/app/coffee/modules/admin/roles.coffee @@ -367,6 +367,7 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm, $compile) -> { key: "view_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.VIEW_USER_STORIES"} { key: "add_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.ADD_USER_STORIES"} { key: "modify_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.MODIFY_USER_STORIES"} + { key: "comment_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.COMMENT_USER_STORIES"} { key: "delete_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.DELETE_USER_STORIES"} ] categories.push({ @@ -378,6 +379,7 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm, $compile) -> { key: "view_tasks", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.VIEW_TASKS"} { key: "add_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.ADD_TASKS"} { key: "modify_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.MODIFY_TASKS"} + { key: "comment_task", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.COMMENT_TASKS"} { key: "delete_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.DELETE_TASKS"} ] categories.push({ @@ -389,6 +391,7 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm, $compile) -> { key: "view_issues", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.VIEW_ISSUES"} { key: "add_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.ADD_ISSUES"} { key: "modify_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.MODIFY_ISSUES"} + { key: "comment_issue", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.COMMENT_ISSUES"} { key: "delete_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.DELETE_ISSUES"} ] categories.push({ diff --git a/app/coffee/modules/common/history.coffee b/app/coffee/modules/common/history.coffee deleted file mode 100644 index c8017ed2..00000000 --- a/app/coffee/modules/common/history.coffee +++ /dev/null @@ -1,507 +0,0 @@ -### -# Copyright (C) 2014-2016 Andrey Antukh -# Copyright (C) 2014-2016 Jesús Espino Garcia -# Copyright (C) 2014-2016 David Barragán Merino -# Copyright (C) 2014-2016 Alejandro Alonso -# Copyright (C) 2014-2016 Juan Francisco Alcántara -# Copyright (C) 2014-2016 Xavi Julian -# -# 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/history.coffee -### - -taiga = @.taiga -trim = @.taiga.trim -bindOnce = @.taiga.bindOnce -debounce = @.taiga.debounce - -module = angular.module("taigaCommon") - -IGNORED_FIELDS = { - "userstories.userstory": [ - "watchers", "kanban_order", "backlog_order", "sprint_order", "finish_date", "tribe_gig" - ], - "tasks.task": [ - "watchers", "us_order", "taskboard_order" - ], - "issues.issue": [ - "watchers" - ] -} - -############################################################################# -## History Directive (Main) -############################################################################# - - -class HistoryController extends taiga.Controller - @.$inject = ["$scope", "$tgRepo", "$tgResources"] - - constructor: (@scope, @repo, @rs) -> - - initialize: (type, objectId) -> - @.type = type - @.objectId = objectId - - loadHistory: (type, objectId) -> - return @rs.history.get(type, objectId).then (history) => - for historyResult in history - # If description was modified take only the description_html field - if historyResult.values_diff.description_diff? - historyResult.values_diff.description = historyResult.values_diff.description_diff - - delete historyResult.values_diff.description_html - delete historyResult.values_diff.description_diff - - # If block note was modified take only the blocked_note_html field - if historyResult.values_diff.blocked_note_diff? - historyResult.values_diff.blocked_note = historyResult.values_diff.blocked_note_diff - - delete historyResult.values_diff.blocked_note_html - delete historyResult.values_diff.blocked_note_diff - - for historyEntry in history - changeModel = historyEntry.key.split(":")[0] - if IGNORED_FIELDS[changeModel]? - historyEntry.values_diff = _.removeKeys(historyEntry.values_diff, IGNORED_FIELDS[changeModel]) - - @scope.history = _.filter(history, (item) -> Object.keys(item.values_diff).length > 0) - - @scope.comments = _.filter(history, (item) -> item.comment != "") - - deleteComment: (type, objectId, activityId) -> - return @rs.history.deleteComment(type, objectId, activityId).then => @.loadHistory(type, objectId) - - undeleteComment: (type, objectId, activityId) -> - return @rs.history.undeleteComment(type, objectId, activityId).then => @.loadHistory(type, objectId) - - -HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $compile, $navUrls, $rootScope, checkPermissionsService) -> - templateChangeDiff = $template.get("common/history/history-change-diff.html", true) - templateChangePoints = $template.get("common/history/history-change-points.html", true) - templateChangeGeneric = $template.get("common/history/history-change-generic.html", true) - templateChangeAttachment = $template.get("common/history/history-change-attachment.html", true) - templateChangeList = $template.get("common/history/history-change-list.html", true) - templateDeletedComment = $template.get("common/history/history-deleted-comment.html", true) - templateActivity = $template.get("common/history/history-activity.html", true) - templateBaseEntries = $template.get("common/history/history-base-entries.html", true) - templateBase = $template.get("common/history/history-base.html", true) - - link = ($scope, $el, $attrs, $ctrl) -> - # Bootstraping - type = $attrs.type - objectId = null - - showAllComments = false - showAllActivity = false - - getPrettyDateFormat = -> - return $translate.instant("ACTIVITY.DATETIME") - - bindOnce $scope, $attrs.ngModel, (model) -> - type = $attrs.type - objectId = model.id - - $ctrl.initialize(type, objectId) - $ctrl.loadHistory(type, objectId) - - # Helpers - getHumanizedFieldName = (field) -> - humanizedFieldNames = { - subject : $translate.instant("ACTIVITY.FIELDS.SUBJECT") - name: $translate.instant("ACTIVITY.FIELDS.NAME") - description : $translate.instant("ACTIVITY.FIELDS.DESCRIPTION") - content: $translate.instant("ACTIVITY.FIELDS.CONTENT") - status: $translate.instant("ACTIVITY.FIELDS.STATUS") - is_closed : $translate.instant("ACTIVITY.FIELDS.IS_CLOSED") - finish_date : $translate.instant("ACTIVITY.FIELDS.FINISH_DATE") - type: $translate.instant("ACTIVITY.FIELDS.TYPE") - priority: $translate.instant("ACTIVITY.FIELDS.PRIORITY") - severity: $translate.instant("ACTIVITY.FIELDS.SEVERITY") - assigned_to : $translate.instant("ACTIVITY.FIELDS.ASSIGNED_TO") - watchers : $translate.instant("ACTIVITY.FIELDS.WATCHERS") - milestone : $translate.instant("ACTIVITY.FIELDS.MILESTONE") - user_story: $translate.instant("ACTIVITY.FIELDS.USER_STORY") - project: $translate.instant("ACTIVITY.FIELDS.PROJECT") - is_blocked: $translate.instant("ACTIVITY.FIELDS.IS_BLOCKED") - blocked_note: $translate.instant("ACTIVITY.FIELDS.BLOCKED_NOTE") - points: $translate.instant("ACTIVITY.FIELDS.POINTS") - client_requirement : $translate.instant("ACTIVITY.FIELDS.CLIENT_REQUIREMENT") - team_requirement : $translate.instant("ACTIVITY.FIELDS.TEAM_REQUIREMENT") - is_iocaine: $translate.instant("ACTIVITY.FIELDS.IS_IOCAINE") - tags: $translate.instant("ACTIVITY.FIELDS.TAGS") - attachments : $translate.instant("ACTIVITY.FIELDS.ATTACHMENTS") - is_deprecated: $translate.instant("ACTIVITY.FIELDS.IS_DEPRECATED") - blocked_note: $translate.instant("ACTIVITY.FIELDS.BLOCKED_NOTE") - is_blocked: $translate.instant("ACTIVITY.FIELDS.IS_BLOCKED") - order: $translate.instant("ACTIVITY.FIELDS.ORDER") - backlog_order: $translate.instant("ACTIVITY.FIELDS.BACKLOG_ORDER") - sprint_order: $translate.instant("ACTIVITY.FIELDS.SPRINT_ORDER") - kanban_order: $translate.instant("ACTIVITY.FIELDS.KANBAN_ORDER") - taskboard_order: $translate.instant("ACTIVITY.FIELDS.TASKBOARD_ORDER") - us_order: $translate.instant("ACTIVITY.FIELDS.US_ORDER") - } - - return humanizedFieldNames[field] or field - - countChanges = (comment) -> - return _.keys(comment.values_diff).length - - formatChange = (change) -> - if _.isArray(change) - if change.length == 0 - return $translate.instant("ACTIVITY.VALUES.EMPTY") - return change.join(", ") - - if change == "" - return $translate.instant("ACTIVITY.VALUES.EMPTY") - - if not change? or change == false - return $translate.instant("ACTIVITY.VALUES.NO") - - if change == true - return $translate.instant("ACTIVITY.VALUES.YES") - - return change - - # Render into string (operations without mutability) - - renderAttachmentEntry = (value) -> - attachments = _.map value, (changes, type) -> - if type == "new" - return _.map changes, (change) -> - return templateChangeDiff({ - name: $translate.instant("ACTIVITY.NEW_ATTACHMENT"), - diff: change.filename - }) - else if type == "deleted" - return _.map changes, (change) -> - return templateChangeDiff({ - name: $translate.instant("ACTIVITY.DELETED_ATTACHMENT"), - diff: change.filename - }) - else - return _.map changes, (change) -> - name = $translate.instant("ACTIVITY.UPDATED_ATTACHMENT", {filename: change.filename}) - - diff = _.map change.changes, (values, name) -> - return { - name: getHumanizedFieldName(name) - from: formatChange(values[0]) - to: formatChange(values[1]) - } - - return templateChangeAttachment({name: name, diff: diff}) - - return _.flatten(attachments).join("\n") - - renderCustomAttributesEntry = (value) -> - customAttributes = _.map value, (changes, type) -> - if type == "new" - return _.map changes, (change) -> - html = templateChangeGeneric({ - name: change.name, - from: formatChange(""), - to: formatChange(change.value) - }) - - html = $compile(html)($scope) - - return html[0].outerHTML - else if type == "deleted" - return _.map changes, (change) -> - return templateChangeDiff({ - name: $translate.instant("ACTIVITY.DELETED_CUSTOM_ATTRIBUTE") - diff: change.name - }) - else - return _.map changes, (change) -> - customAttrsChanges = _.map change.changes, (values) -> - return templateChangeGeneric({ - name: change.name - from: formatChange(values[0]) - to: formatChange(values[1]) - }) - return _.flatten(customAttrsChanges).join("\n") - - return _.flatten(customAttributes).join("\n") - - renderChangeEntry = (field, value) -> - if field == "description" - return templateChangeDiff({name: getHumanizedFieldName("description"), diff: value[1]}) - else if field == "blocked_note" - return templateChangeDiff({name: getHumanizedFieldName("blocked_note"), diff: value[1]}) - else if field == "points" - html = templateChangePoints({points: value}) - - html = $compile(html)($scope) - - return html[0].outerHTML - else if field == "attachments" - return renderAttachmentEntry(value) - else if field == "custom_attributes" - return renderCustomAttributesEntry(value) - else if field in ["tags", "watchers"] - name = getHumanizedFieldName(field) - removed = _.difference(value[0], value[1]) - added = _.difference(value[1], value[0]) - html = templateChangeList({name:name, removed:removed, added: added}) - - html = $compile(html)($scope) - - return html[0].outerHTML - else if field == "assigned_to" - name = getHumanizedFieldName(field) - from = formatChange(value[0] or $translate.instant("ACTIVITY.VALUES.UNASSIGNED")) - to = formatChange(value[1] or $translate.instant("ACTIVITY.VALUES.UNASSIGNED")) - return templateChangeGeneric({name:name, from:from, to: to}) - else - name = getHumanizedFieldName(field) - from = formatChange(value[0]) - to = formatChange(value[1]) - return templateChangeGeneric({name:name, from:from, to: to}) - - renderChangeEntries = (change) -> - return _.map(change.values_diff, (value, field) -> renderChangeEntry(field, value)) - - renderChangesHelperText = (change) -> - size = countChanges(change) - return $translate.instant("ACTIVITY.SIZE_CHANGE", {size: size}, 'messageformat') - - renderComment = (comment) -> - if (comment.delete_comment_date or comment.delete_comment_user?.name) - html = templateDeletedComment({ - deleteCommentDate: moment(comment.delete_comment_date).format(getPrettyDateFormat()) if comment.delete_comment_date - deleteCommentUser: comment.delete_comment_user.name - deleteComment: comment.comment_html - activityId: comment.id - canRestoreComment: ($scope.user and - (comment.delete_comment_user.pk == $scope.user.id or - $scope.project.my_permissions.indexOf("modify_project") > -1)) - }) - - html = $compile(html)($scope) - - return html[0].outerHTML - - html = templateActivity({ - avatar: comment.user.photo - userFullName: comment.user.name - userProfileUrl: if comment.user.is_active then $navUrls.resolve("user-profile", {username: comment.user.username}) else "" - creationDate: moment(comment.created_at).format(getPrettyDateFormat()) - comment: comment.comment_html - changesText: renderChangesHelperText(comment) - changes: renderChangeEntries(comment) - mode: "comment" - deleteCommentActionTitle: $translate.instant("COMMENTS.DELETE") - deleteCommentDate: moment(comment.delete_comment_date).format(getPrettyDateFormat()) if comment.delete_comment_date - deleteCommentUser: comment.delete_comment_user.name if comment.delete_comment_user?.name - activityId: comment.id - canDeleteComment: comment.user.pk == $scope.user?.id or $scope.project.my_permissions.indexOf("modify_project") > -1 - }) - - html = $compile(html)($scope) - - return html[0].outerHTML - - renderChange = (change) -> - return templateActivity({ - avatar: change.user.photo - userFullName: change.user.name - userProfileUrl: if change.user.is_active then $navUrls.resolve("user-profile", {username: change.user.username}) else "" - creationDate: moment(change.created_at).format(getPrettyDateFormat()) - comment: change.comment_html - changes: renderChangeEntries(change) - changesText: "" - mode: "activity" - deleteCommentDate: moment(change.delete_comment_date).format(getPrettyDateFormat()) if change.delete_comment_date - deleteCommentUser: change.delete_comment_user.name if change.delete_comment_user?.name - activityId: change.id - }) - - renderHistory = (entries, totalEntries) -> - if entries.length == totalEntries - showMore = 0 - else - showMore = totalEntries - entries.length - - html = templateBaseEntries({entries: entries, showMore:showMore}) - html = $compile(html)($scope) - return html - - # Render into DOM (operations with dom mutability) - - renderBase = -> - comments = $scope.comments or [] - changes = $scope.history or [] - - historyVisible = !!changes.length - commentsVisible = (!!comments.length) || checkPermissionsService.check('modify_' + $attrs.type) - - html = templateBase({ - ngmodel: $attrs.ngModel, - type: $attrs.type, - mode: $attrs.mode, - historyVisible: historyVisible, - commentsVisible: commentsVisible - }) - - html = $compile(html)($scope) - - $el.html(html) - - rerender = -> - renderBase() - renderComments() - renderActivity() - - renderComments = -> - comments = $scope.comments or [] - totalComments = comments.length - - if not showAllComments - comments = _.takeRight(comments, 4) - - comments = _.map comments, (x) -> renderComment(x) - - html = renderHistory(comments, totalComments) - $el.find(".comments-list").html(html) - - renderActivity = -> - changes = $scope.history or [] - totalChanges = changes.length - if not showAllActivity - changes = _.takeRight(changes, 4) - - changes = _.map(changes, (x) -> renderChange(x)) - html = renderHistory(changes, totalChanges) - $el.find(".changes-list").html(html) - - save = $qqueue.bindAdd (target) => - $scope.$broadcast("markdown-editor:submit") - - $el.find(".comment-list").addClass("activeanimation") - - currentLoading = $loading() - .target(target) - .start() - - onSuccess = -> - $rootScope.$broadcast("comment:new") - - $ctrl.loadHistory(type, objectId).finally -> - currentLoading.finish() - - onError = -> - currentLoading.finish() - $confirm.notify("error") - - model = $scope.$eval($attrs.ngModel) - - $ctrl.repo.save(model).then(onSuccess, onError) - - # Watchers - - $scope.$watch("comments", rerender) - $scope.$watch("history", rerender) - - $scope.$on("object:updated", -> $ctrl.loadHistory(type, objectId)) - - # Events - - $el.on "click", ".add-comment .button-green", debounce 2000, (event) -> - event.preventDefault() - - target = angular.element(event.currentTarget) - save(target) - - $el.on "click", "a", (event) -> - target = angular.element(event.target) - href = target.attr('href') - if href && href.indexOf("#") == 0 - event.preventDefault() - $('body').scrollTop($(href).offset().top) - - $el.on "click", ".show-more", (event) -> - event.preventDefault() - - target = angular.element(event.currentTarget) - if target.parent().is(".changes-list") - showAllActivity = not showAllActivity - renderActivity() - else - showAllComments = not showAllComments - renderComments() - - $el.on "click", ".show-deleted-comment", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget) - target.parents('.activity-single').find('.hide-deleted-comment').show() - target.parents('.activity-single').find('.show-deleted-comment').hide() - target.parents('.activity-single').find('.comment-body').show() - - $el.on "click", ".hide-deleted-comment", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget) - target.parents('.activity-single').find('.hide-deleted-comment').hide() - target.parents('.activity-single').find('.show-deleted-comment').show() - target.parents('.activity-single').find('.comment-body').hide() - - $el.on "click", ".changes-title", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget) - target.parent().find(".change-entry").toggleClass("active") - - $el.on "focus", ".add-comment textarea", (event) -> - $(this).addClass('active') - - $el.on "click", ".history-tabs a", (event) -> - target = angular.element(event.currentTarget) - - $el.find(".history-tabs li").removeClass("active") - target.parent().addClass("active") - - $el.find(".history section").addClass("hidden") - $el.find(".history section.#{target.data('section-class')}").removeClass("hidden") - - $el.on "click", ".comment-delete", debounce 2000, (event) -> - event.preventDefault() - - target = angular.element(event.currentTarget) - activityId = target.data('activity-id') - $ctrl.deleteComment(type, objectId, activityId) - - $el.on "click", ".comment-restore", debounce 2000, (event) -> - event.preventDefault() - - target = angular.element(event.currentTarget) - activityId = target.data('activity-id') - $ctrl.undeleteComment(type, objectId, activityId) - - $scope.$on "$destroy", -> - $el.off() - - renderBase() - - return { - controller: HistoryController - restrict: "AE" - link: link - # require: ["ngModel", "tgHistory"] - } - - -module.directive("tgHistory", ["$log", "$tgLoading", "$tgQqueue", "$tgTemplate", "$tgConfirm", "$translate", - "$compile", "$tgNavUrls", "$rootScope", "tgCheckPermissionsService", HistoryDirective]) diff --git a/app/coffee/modules/common/wisiwyg.coffee b/app/coffee/modules/common/wisiwyg.coffee index e6fe3b0c..40f53264 100644 --- a/app/coffee/modules/common/wisiwyg.coffee +++ b/app/coffee/modules/common/wisiwyg.coffee @@ -83,7 +83,7 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans markdownDomNode = element.parents(".markdown") markItUpDomNode = element.parents(".markItUp") - $rs.mdrender.render($scope.projectId, $model.$modelValue).then (data) -> + $rs.mdrender.render($scope.projectId || $scope.vm.projectId, $model.$modelValue).then (data) -> html = previewTemplate({data: data.data}) html = $compile(html)($scope) diff --git a/app/coffee/modules/resources/history.coffee b/app/coffee/modules/resources/history.coffee index 5ea6886b..6e0942b1 100644 --- a/app/coffee/modules/resources/history.coffee +++ b/app/coffee/modules/resources/history.coffee @@ -31,6 +31,25 @@ resourceProvider = ($repo, $http, $urls) -> service.get = (type, objectId) -> return $repo.queryOneRaw("history/#{type}", objectId) + service.editComment = (type, objectId, activityId, comment) -> + url = $urls.resolve("history/#{type}") + url = "#{url}/#{objectId}/edit_comment" + params = { + id: activityId + } + commentData = { + comment: comment + } + return $http.post(url, commentData, params).then (data) => + return data.data + + service.getCommentHistory = (type, objectId, activityId) -> + url = $urls.resolve("history/#{type}") + url = "#{url}/#{objectId}/comment_versions" + params = {id: activityId} + return $http.get(url, params).then (data) => + return data.data + service.deleteComment = (type, objectId, activityId) -> url = $urls.resolve("history/#{type}") url = "#{url}/#{objectId}/delete_comment" diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index a5176289..0263a4f1 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "View user stories", "ADD_USER_STORIES": "Add user stories", "MODIFY_USER_STORIES": "Modify user stories", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Delete user stories" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "View tasks", "ADD_TASKS": "Add tasks", "MODIFY_TASKS": "Modify tasks", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Delete tasks" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "View issues", "ADD_ISSUES": "Add issues", "MODIFY_ISSUES": "Modify issues", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Delete issues" }, "WIKI": { @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comment deleted by {{user}} on {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Comments", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Comment", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Type a new comment here", "SHOW_DELETED": "Show deleted comment", "HIDE_DELETED": "Hide deleted comment", "DELETE": "Delete comment", - "RESTORE": "Restore comment" + "RESTORE": "Restore comment", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Show activity", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Show previous entries ({{showMore}} more)", "TITLE": "Activity", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "removed", "ADDED": "added", - "US_POINTS": "US points ({{name}})", - "NEW_ATTACHMENT": "new attachment", - "DELETED_ATTACHMENT": "deleted attachment", - "UPDATED_ATTACHMENT": "updated attachment {{filename}}", - "DELETED_CUSTOM_ATTRIBUTE": "deleted custom attribute", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}): ", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Made {size, plural, one{one change} other{# changes}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "yes", "NO": "no", @@ -1071,6 +1093,7 @@ "TAGS": "tags", "ATTACHMENTS": "attachments", "IS_DEPRECATED": "is deprecated", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "order", "BACKLOG_ORDER": "backlog order", "SPRINT_ORDER": "sprint order", diff --git a/app/modules/history/comments/comment.controller.coffee b/app/modules/history/comments/comment.controller.coffee new file mode 100644 index 00000000..c137af11 --- /dev/null +++ b/app/modules/history/comments/comment.controller.coffee @@ -0,0 +1,64 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: history.controller.coffee +### + +module = angular.module("taigaHistory") + +class CommentController + @.$inject = [ + "tgCurrentUserService", + "tgCheckPermissionsService", + "tgLightboxFactory" + ] + + constructor: (@currentUserService, @permissionService, @lightboxFactory) -> + @.hiddenDeletedComment = true + @.toggleEditComment = false + @.commentContent = angular.copy(@.comment) + + showDeletedComment: () -> + @.hiddenDeletedComment = false + + hideDeletedComment: () -> + @.hiddenDeletedComment = true + + toggleCommentEditor: () -> + @.toggleEditComment = !@.toggleEditComment + + checkCancelComment: (event) -> + if event.keyCode == 27 + @.toggleCommentEditor() + + canEditDeleteComment: () -> + if @currentUserService.getUser() + @.user = @currentUserService.getUser() + return @.user.get('id') == @.comment.user.pk || @permissionService.check('modify_project') + + displayCommentHistory: () -> + @lightboxFactory.create('tg-lb-display-historic', { + "class": "lightbox lightbox-display-historic" + "comment": "comment" + "name": "name" + "object": "object" + }, { + "comment": @.comment + "name": @.name + "object": @.object + }) + +module.controller("CommentCtrl", CommentController) diff --git a/app/modules/history/comments/comment.controller.spec.coffee b/app/modules/history/comments/comment.controller.spec.coffee new file mode 100644 index 00000000..9765fe1f --- /dev/null +++ b/app/modules/history/comments/comment.controller.spec.coffee @@ -0,0 +1,134 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: subscriptions.controller.spec.coffee +### + +describe "CommentController", -> + provide = null + controller = null + mocks = {} + + _mockTgCurrentUserService = () -> + mocks.tgCurrentUserService = { + getUser: sinon.stub() + } + + provide.value "tgCurrentUserService", mocks.tgCurrentUserService + + _mockTgCheckPermissionsService = () -> + mocks.tgCheckPermissionsService = { + check: sinon.stub() + } + + provide.value "tgCheckPermissionsService", mocks.tgCheckPermissionsService + + _mockTgLightboxFactory = () -> + mocks.tgLightboxFactory = { + create: sinon.stub() + } + + provide.value "tgLightboxFactory", mocks.tgLightboxFactory + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgCurrentUserService() + _mockTgCheckPermissionsService() + _mockTgLightboxFactory() + return null + + beforeEach -> + module "taigaHistory" + _mocks() + + inject ($controller) -> + controller = $controller + + commentsCtrl = controller "CommentCtrl" + + commentsCtrl.comment = "comment" + commentsCtrl.hiddenDeletedComment = true + commentsCtrl.toggleEditComment = false + commentsCtrl.commentContent = commentsCtrl.comment + + it "show deleted Comment", () -> + commentsCtrl = controller "CommentCtrl" + commentsCtrl.showDeletedComment() + expect(commentsCtrl.hiddenDeletedComment).to.be.false + + it "hide deleted Comment", () -> + commentsCtrl = controller "CommentCtrl" + + commentsCtrl.hiddenDeletedComment = false + commentsCtrl.hideDeletedComment() + expect(commentsCtrl.hiddenDeletedComment).to.be.true + + it "toggle deleted Comment", () -> + commentsCtrl = controller "CommentCtrl" + + commentsCtrl.toggleEditComment = false + commentsCtrl.toggleCommentEditor() + expect(commentsCtrl.toggleEditComment).to.be.true + + it "cancel comment on keyup", () -> + commentsCtrl = controller "CommentCtrl" + commentsCtrl.toggleCommentEditor = sinon.stub() + event = { + keyCode: 27 + } + commentsCtrl.checkCancelComment(event) + expect(commentsCtrl.toggleCommentEditor).have.been.called + + it "can Edit Comment", () -> + commentsCtrl = controller "CommentCtrl" + + commentsCtrl.user = Immutable.fromJS({ + id: 7 + }) + + mocks.tgCurrentUserService.getUser.returns(commentsCtrl.user) + + commentsCtrl.comment = { + user: { + pk: 7 + } + } + + mocks.tgCheckPermissionsService.check.withArgs('modify_project').returns(true) + + canEdit = commentsCtrl.canEditDeleteComment() + expect(canEdit).to.be.true + + it "cannot Edit Comment", () -> + commentsCtrl = controller "CommentCtrl" + + commentsCtrl.user = Immutable.fromJS({ + id: 8 + }) + + mocks.tgCurrentUserService.getUser.returns(commentsCtrl.user) + + commentsCtrl.comment = { + user: { + pk: 7 + } + } + + mocks.tgCheckPermissionsService.check.withArgs('modify_project').returns(false) + + canEdit = commentsCtrl.canEditDeleteComment() + expect(canEdit).to.be.false diff --git a/app/modules/history/comments/comment.directive.coffee b/app/modules/history/comments/comment.directive.coffee new file mode 100644 index 00000000..cb3c2bb4 --- /dev/null +++ b/app/modules/history/comments/comment.directive.coffee @@ -0,0 +1,44 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: comment.directive.coffee +### + +module = angular.module('taigaHistory') + +CommentDirective = () -> + + return { + scope: { + name: "@", + object: "@", + comment: "<", + type: "<", + loading: "<", + editing: "<", + deleting: "<", + objectId: "<", + onDeleteComment: "&", + onRestoreDeletedComment: "&", + onEditComment: "&" + }, + templateUrl:"history/comments/comment.html", + bindToController: true, + controller: 'CommentCtrl', + controllerAs: "vm", + } + +module.directive("tgComment", CommentDirective) diff --git a/app/modules/history/comments/comment.jade b/app/modules/history/comments/comment.jade new file mode 100644 index 00000000..38d8a2a3 --- /dev/null +++ b/app/modules/history/comments/comment.jade @@ -0,0 +1,108 @@ +include ../../../partials/common/components/wysiwyg.jade + +.comment-wrapper(ng-if="!vm.comment.delete_comment_date") + img.comment-avatar( + ng-src="{{vm.comment.user.photo}}" + ng-alt="{{vm.comment.user.name}}" + ) + .comment-main + .comment-data + span.comment-creator {{vm.comment.user.name}} + span.comment-date {{vm.comment.created_at | momentFormat:'DD MMM YYYY HH:mm'}} + .comment-edited(ng-if="vm.comment.edit_comment_date") + span(translate="COMMENTS.EDITED_COMMENT") + span {{vm.comment.edit_comment_date | momentFormat:'DD MMM YYYY HH:mm'}} + span.separator - + a( + href="" + title="COMMENTS.SHOW_HISTORY" + ng-click="vm.displayCommentHistory()" + ) + span(translate="COMMENTS.SHOW_HISTORY") + tg-svg(svg-icon="icon-bulk") + .comment-container + .comment-text.wysiwyg( + ng-if="!vm.toggleEditComment" + ng-bind-html="vm.comment.comment_html" + ) + .comment-editor( + ng-if="vm.toggleEditComment" + ng-keyup="vm.checkCancelComment($event)" + ) + .edit-comment(ng-model="vm.type") + textarea( + ng-model="vm.commentContent.comment" + ) + .save-comment-wrapper + button.button-green.save-comment( + type="button" + title="{{'COMMENTS.EDIT_COMMENT' | translate}}" + translate="COMMENTS.EDIT_COMMENT" + ng-disabled="!vm.commentContent.comment.length || vm.editing" + ng-click="vm.onEditComment({commentId: vm.comment.id, commentData: vm.commentContent.comment})" + tg-loading="vm.editing" + ) + .comment-options(ng-if="::vm.canEditDeleteComment()") + tg-svg.comment-option( + svg-icon="icon-edit" + svg-title-translate="COMMON.EDIT" + ng-click="vm.toggleCommentEditor()" + ng-if="!vm.toggleEditComment" + ) + tg-svg.comment-option( + svg-icon="icon-close" + svg-title-translate="COMMON.CANCEL" + ng-click="vm.toggleCommentEditor()" + ng-if="vm.toggleEditComment" + ) + tg-svg.comment-option( + svg-icon="icon-trash" + svg-title-translate="COMMON.DELETE" + ng-click="vm.onDeleteComment({commentId: vm.comment.id})" + ng-if="!vm.toggleEditComment" + tg-loading="vm.deleting" + ) + +.deleted-comment-wrapper( + ng-if="vm.comment.delete_comment_date" +) + .deleted-comment-main + span( + translate="COMMENTS.DELETED_INFO" + translate-values="{user: vm.comment.delete_comment_user.name }" + ) + span - {{vm.comment.delete_comment_date | momentFormat:'DD MMM YYYY HH:mm'}} + a.toggle-deleted-comment( + href="" + ng-click="vm.showDeletedComment()" + ng-if="vm.hiddenDeletedComment" + ) + span(translate="COMMENTS.SHOW_DELETED") + tg-svg( + svg-icon="icon-arrow-down" + svg-title-translate="COMMENTS.SHOW_DELETED" + ) + a.toggle-deleted-comment( + href="" + ng-click="vm.hideDeletedComment()" + ng-if="!vm.hiddenDeletedComment" + ) + span(translate="COMMENTS.HIDE_DELETED") + tg-svg( + svg-icon="icon-arrow-up" + svg-title-translate="COMMENTS.HIDE_DELETED" + ) + a.restore-comment( + href="" + ng-click="vm.onRestoreDeletedComment({commentId: vm.comment.id})" + tg-loading="vm.editing" + ) + tg-svg( + svg-icon="icon-reload" + svg-title-translate="COMMENTS.RESTORE" + ) + span(translate="COMMENTS.RESTORE") + p.deleted-comment-comment( + ng-if="!vm.hiddenDeletedComment" + ng-bind-html="vm.comment.comment_html" + ) diff --git a/app/modules/history/comments/comment.scss b/app/modules/history/comments/comment.scss new file mode 100644 index 00000000..c5a68912 --- /dev/null +++ b/app/modules/history/comments/comment.scss @@ -0,0 +1,159 @@ +.comments { + clear: both; + .add-comment { + margin-top: 1rem; + textarea { + height: 3rem; + } + .preview-icon, + .edit { + position: absolute; + right: 1rem; + } + } + .save-comment-wrapper { + align-items: flex-end; + display: flex; + flex-direction: column; + } + .save-comment { + margin-top: 1rem; + padding: .5rem 4rem; + } + +} +.comment { + display: block; + .comment-wrapper { + align-items: flex-start; + border-bottom: 1px solid $whitish; + display: flex; + padding: 2rem 0; + &:hover { + .comment-option { + opacity: 1; + } + } + } + .comment-main { + width: 100%; + } + .comment-avatar { + flex-basis: 50px; + flex-shrink: 0; + margin-right: 1.5rem; + } + .comment-data { + align-items: center; + display: flex; + justify-content: flex-start; + margin-bottom: 1rem; + } + .comment-creator { + color: $primary; + margin-right: .5rem; + } + .comment-date { + @include font-size(small); + color: $gray-light; + } + .comment-edited { + @include font-size(small); + background: $whitish; + margin: 0 .5rem; + padding: .25rem; + .separator { + margin: 0 .25rem; + } + a { + color: $primary; + fill: $primary; + } + svg { + @include svg-size(.75rem); + margin: 0 0 0 .25rem; + } + } + .comment-options { + align-items: center; + align-self: stretch; + display: flex; + flex-basis: 50px; + flex-shrink: 0; + margin-left: 1.5rem; + .comment-option { + cursor: pointer; + opacity: 0; + } + .icon-edit { + fill: $gray-light; + margin-right: .5rem; + &:hover { + fill: $gray; + } + } + .icon-close { + fill: $gray-light; + margin-right: .5rem; + &:hover { + fill: $red; + } + } + .icon-trash { + fill: $red-light; + &:hover { + fill: $red; + } + } + } + .deleted-comment-wrapper { + border-bottom: 1px solid $whitish; + padding: 1rem 0; + width: 100%; + } + .deleted-comment-main { + @include font-size(xsmall); + color: $gray-light; + display: flex; + width: 100%; + } + .toggle-deleted-comment { + color: $primary; + fill: $primary; + margin: 0 1rem; + transition: none; + .icon-arrow-down, + .icon-arrow-up { + @include svg-size(.8rem); + margin-left: .25rem; + } + } + .restore-comment { + margin-left: auto; + transition: all .2s; + &:hover { + color: $primary; + fill: $primary; + } + .icon-reload { + @include svg-size(.8rem); + margin-right: .25rem; + } + } + .deleted-comment-comment { + margin-top: 1rem; + } + .comment-editor { + textarea { + height: 5rem; + min-height: 5rem; + } + } +} + +.comment-text { + &.wysiwyg { + margin-bottom: 0; + padding: 0; + } +} diff --git a/app/modules/history/comments/comments.controller.coffee b/app/modules/history/comments/comments.controller.coffee new file mode 100644 index 00000000..6a986321 --- /dev/null +++ b/app/modules/history/comments/comments.controller.coffee @@ -0,0 +1,28 @@ +### +# 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: comments.controller.coffee +### + +module = angular.module("taigaHistory") + +class CommentsController + @.$inject = [] + + constructor: () -> + + initializePermissions: () -> + @.canAddCommentPermission = 'comment_' + @.name + +module.controller("CommentsCtrl", CommentsController) diff --git a/app/modules/history/comments/comments.controller.spec.coffee b/app/modules/history/comments/comments.controller.spec.coffee new file mode 100644 index 00000000..dff1233a --- /dev/null +++ b/app/modules/history/comments/comments.controller.spec.coffee @@ -0,0 +1,41 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: comments.controller.spec.coffee +### + +describe "CommentsController", -> + provide = null + controller = null + mocks = {} + + _mocks = () -> + module ($provide) -> + provide = $provide + return null + + beforeEach -> + module "taigaHistory" + _mocks() + + inject ($controller) -> + controller = $controller + + it "set can add comment permission", () -> + commentsCtrl = controller "CommentsCtrl" + commentsCtrl.name = "us" + commentsCtrl.initializePermissions() + expect(commentsCtrl.canAddCommentPermission).to.be.equal("comment_us") diff --git a/app/modules/history/comments/comments.directive.coffee b/app/modules/history/comments/comments.directive.coffee new file mode 100644 index 00000000..cb14c234 --- /dev/null +++ b/app/modules/history/comments/comments.directive.coffee @@ -0,0 +1,48 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: comments.directive.coffee +### + +module = angular.module('taigaHistory') + +CommentsDirective = () -> + link = (scope, el, attrs, ctrl) -> + ctrl.initializePermissions() + + return { + scope: { + type: "<", + name: "@", + object: "@", + comments: "<", + onDeleteComment: "&", + onRestoreDeletedComment: "&", + onAddComment: "&", + onEditComment: "&", + loading: "<", + deleting: "<", + editing: "<", + projectId: "=" + }, + templateUrl:"history/comments/comments.html", + bindToController: true, + controller: 'CommentsCtrl', + controllerAs: "vm" + link: link + } + +module.directive("tgComments", CommentsDirective) diff --git a/app/modules/history/comments/comments.jade b/app/modules/history/comments/comments.jade new file mode 100644 index 00000000..00d2f4f5 --- /dev/null +++ b/app/modules/history/comments/comments.jade @@ -0,0 +1,37 @@ +include ../../../partials/common/components/wysiwyg.jade + +section.comments + .comments-wrapper + tg-comment.comment( + ng-repeat="comment in vm.comments track by comment.id" + ng-class="{'deleted-comment': comment.delete_comment_date}" + comment="comment" + name="{{vm.name}}" + loading="vm.loading" + editing="vm.editing" + deleting="vm.deleting" + object="{{vm.object}}" + on-delete-comment="vm.onDeleteComment({commentId: commentId})" + on-restore-deleted-comment="vm.onRestoreDeletedComment({commentId: commentId})" + on-edit-comment="vm.onEditComment({commentId: commentId, commentData: commentData})" + ) + tg-editable-wysiwyg.add-comment( + ng-model="vm.type" + tg-check-permission="{{::vm.canAddCommentPermission}}" + tg-toggle-comment + ) + textarea( + ng-attr-placeholder="{{'COMMENTS.TYPE_NEW_COMMENT' | translate}}" + tg-markitup="tg-markitup" + ng-model="vm.type.comment" + ) + +wysihelp + .save-comment-wrapper + button.button-green.save-comment( + type="button" + title="{{'COMMENTS.COMMENT' | translate}}" + translate="COMMENTS.COMMENT" + ng-disabled="!vm.type.comment.length || vm.loading" + ng-click="vm.onAddComment()" + tg-loading="vm.loading" + ) diff --git a/app/modules/history/history-lightbox/comment-history-lightbox.controller.coffee b/app/modules/history/history-lightbox/comment-history-lightbox.controller.coffee new file mode 100644 index 00000000..93f014f3 --- /dev/null +++ b/app/modules/history/history-lightbox/comment-history-lightbox.controller.coffee @@ -0,0 +1,37 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: history.controller.coffee +### + +module = angular.module("taigaHistory") + +class LightboxDisplayHistoricController + @.$inject = [ + "$tgResources", + ] + + constructor: (@rs) -> + + _loadHistoric: () -> + type = @.name + objectId = @.object + activityId = @.comment.id + + @rs.history.getCommentHistory(type, objectId, activityId).then (data) => + @.commentHistoryEntries = data + +module.controller("LightboxDisplayHistoricCtrl", LightboxDisplayHistoricController) diff --git a/app/modules/history/history-lightbox/comment-history-lightbox.controller.spec.coffee b/app/modules/history/history-lightbox/comment-history-lightbox.controller.spec.coffee new file mode 100644 index 00000000..92167c96 --- /dev/null +++ b/app/modules/history/history-lightbox/comment-history-lightbox.controller.spec.coffee @@ -0,0 +1,63 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: subscriptions.controller.spec.coffee +### + +describe "LightboxDisplayHistoricController", -> + provide = null + controller = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + history: { + getCommentHistory: sinon.stub() + } + } + + provide.value "$tgResources", mocks.tgResources + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + return null + + beforeEach -> + module "taigaHistory" + _mocks() + + inject ($controller) -> + controller = $controller + + it "load historic", (done) -> + historicLbCtrl = controller "LightboxDisplayHistoricCtrl" + + historicLbCtrl.name = "type" + historicLbCtrl.object = 1 + historicLbCtrl.comment = {} + historicLbCtrl.comment.id = 1 + + type = historicLbCtrl.name + objectId = historicLbCtrl.object + activityId = historicLbCtrl.comment.id + + promise = mocks.tgResources.history.getCommentHistory.withArgs(type, objectId, activityId).promise().resolve() + + historicLbCtrl._loadHistoric().then (data) -> + expect(historicLbCtrl.commentHistoryEntries).is.equal(data) + done() diff --git a/app/modules/history/history-lightbox/comment-history-lightbox.directive.coffee b/app/modules/history/history-lightbox/comment-history-lightbox.directive.coffee new file mode 100644 index 00000000..b09c99a0 --- /dev/null +++ b/app/modules/history/history-lightbox/comment-history-lightbox.directive.coffee @@ -0,0 +1,40 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: comment.directive.coffee +### + +module = angular.module('taigaHistory') + +LightboxDisplayHistoricDirective = (lightboxService) -> + link = (scope, el, attrs, ctrl) -> + ctrl._loadHistoric() + lightboxService.open(el) + + return { + scope: {}, + bindToController: { + name: '=', + object: '=', + comment: '=' + }, + templateUrl:"history/history-lightbox/comment-history-lightbox.html", + controller: "LightboxDisplayHistoricCtrl", + controllerAs: "vm", + link: link + } + +module.directive("tgLbDisplayHistoric", LightboxDisplayHistoricDirective) diff --git a/app/modules/history/history-lightbox/comment-history-lightbox.jade b/app/modules/history/history-lightbox/comment-history-lightbox.jade new file mode 100644 index 00000000..32127f9b --- /dev/null +++ b/app/modules/history/history-lightbox/comment-history-lightbox.jade @@ -0,0 +1,9 @@ +tg-lightbox-close + +.history-container + h2.title(translate="COMMENTS.HISTORY.TITLE") + .history-wrapper + tg-history-entry.entry( + ng-repeat="entry in vm.commentHistoryEntries" + entry="entry" + ) diff --git a/app/modules/history/history-lightbox/comment-history-lightbox.scss b/app/modules/history/history-lightbox/comment-history-lightbox.scss new file mode 100644 index 00000000..47e79868 --- /dev/null +++ b/app/modules/history/history-lightbox/comment-history-lightbox.scss @@ -0,0 +1,13 @@ +.lightbox-display-historic { + display: none; + .history-container { + max-width: 800px; + width: 90%; + } + .history-wrapper { + max-height: 600px; + overflow-x: hidden; + overflow-y: auto; + padding: 2rem; + } +} diff --git a/app/modules/history/history-lightbox/history-entry.directive.coffee b/app/modules/history/history-lightbox/history-entry.directive.coffee new file mode 100644 index 00000000..42814d29 --- /dev/null +++ b/app/modules/history/history-lightbox/history-entry.directive.coffee @@ -0,0 +1,31 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: comment.directive.coffee +### + +module = angular.module('taigaHistory') + +HistoryEntryDirective = (lightboxService) -> + + return { + scope: { + entry: "<" + }, + templateUrl:"history/history-lightbox/history-entry.html", + } + +module.directive("tgHistoryEntry", HistoryEntryDirective) diff --git a/app/modules/history/history-lightbox/history-entry.jade b/app/modules/history/history-lightbox/history-entry.jade new file mode 100644 index 00000000..4df2d67b --- /dev/null +++ b/app/modules/history/history-lightbox/history-entry.jade @@ -0,0 +1,19 @@ +.entry-wrapper + img.entry-avatar( + ng-src="{{entry.user.photo}}" + ng-alt="{{entry.user.name}}" + ) + .entry-main + .entry-data + span.entry-creator {{entry.user.full_name_display}} + span.entry-date {{entry.date | momentFormat:'DD MMM YYYY HH:mm'}} + tg-svg.display-full-entry( + svg-icon="icon-arrow-down" + ng-class="{'inactive': !displayFullEntry}" + ng-click="displayFullEntry=!displayFullEntry" + ng-show="entry.comment.length >= 75" + ) + .entry-text( + ng-class="{'ellipsed': !displayFullEntry && entry.comment.length >= 75, 'blurry': entry.comment.length >= 75 && !displayFullEntry}" + ng-bind-html="entry.comment_html" + ) diff --git a/app/modules/history/history-lightbox/history-entry.scss b/app/modules/history/history-lightbox/history-entry.scss new file mode 100644 index 00000000..763921aa --- /dev/null +++ b/app/modules/history/history-lightbox/history-entry.scss @@ -0,0 +1,62 @@ +.entry { + display: block; + .entry-wrapper { + align-items: flex-start; + border-bottom: 1px solid $whitish; + display: flex; + padding: 2rem 0; + } + .entry-avatar { + flex-basis: 50px; + flex-grow: 0; + flex-shrink: 0; + margin-right: 1.5rem; + } + .entry-main { + flex: 1; + max-width: calc(100% - 100px); + } + .entry-data { + align-items: flex-start; + display: flex; + margin-bottom: 1rem; + } + .entry-creator { + color: $primary; + margin-right: .5rem; + } + .entry-date { + @include font-size(small); + color: $gray-light; + } + .display-full-entry { + @include svg-size(1.25rem); + cursor: pointer; + fill: $primary; + margin-left: auto; + transform: rotate(0); + transition: transform .2s; + &.inactive { + transform: rotate(180deg); + } + } + .entry-text { + margin-bottom: 0; + &.ellipsed { + max-height: 3rem; + overflow: hidden; + } + &.blurry { + position: relative; + &::after { + background-image: linear-gradient(to top, $white, transparent); + content: ''; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + } + } + } +} diff --git a/app/modules/history/history-tabs/history-tabs.directive.coffee b/app/modules/history/history-tabs/history-tabs.directive.coffee new file mode 100644 index 00000000..fdadb250 --- /dev/null +++ b/app/modules/history/history-tabs/history-tabs.directive.coffee @@ -0,0 +1,37 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: history-tabs.directive.coffee +### + +module = angular.module('taigaHistory') + +HistoryTabsDirective = () -> + + return { + templateUrl:"history/history-tabs/history-tabs.html", + scope: { + onActiveComments: "&", + onActiveActivities: "&", + onOrderComments: "&" + activeTab: "<", + commentsNum: "<", + activitiesNum: "<", + onReverse: "<" + } + } + +module.directive("tgHistoryTabs", HistoryTabsDirective) diff --git a/app/modules/history/history-tabs/history-tabs.jade b/app/modules/history/history-tabs/history-tabs.jade new file mode 100644 index 00000000..8b0c175a --- /dev/null +++ b/app/modules/history/history-tabs/history-tabs.jade @@ -0,0 +1,41 @@ +nav.history-tabs + a.history-tab.e2e-comments-tab( + href="" + title="Comments" + ng-click="onActiveComments()" + ng-class="{active: activeTab}" + translate="COMMENTS.COMMENTS_COUNT" + translate-values="{comments: commentsNum}" + ) + a.history-tab.e2e-activity-tab( + href="" + title="Activities" + ng-click="onActiveActivities()" + ng-class="{active: !activeTab}" + translate="ACTIVITY.ACTIVITIES_COUNT" + translate-values="{activities: activitiesNum}" + ) + a.order-comments( + href="" + title="Order Comments" + ng-click="onOrderComments()" + ng-class="{'new-first': top, 'old-first': !top}" + ng-if="commentsNum > 1 && activeTab" + ) + + span( + translate="COMMENTS.OLDER_FIRST" + ng-if="onReverse" + ) + tg-svg( + svg-icon="icon-arrow-down" + ng-if="onReverse" + ) + span( + translate="COMMENTS.RECENT_FIRST" + ng-if="!onReverse" + ) + tg-svg( + svg-icon="icon-arrow-up" + ng-if="!onReverse" + ) diff --git a/app/modules/history/history-tabs/history-tabs.scss b/app/modules/history/history-tabs/history-tabs.scss new file mode 100644 index 00000000..48cb8471 --- /dev/null +++ b/app/modules/history/history-tabs/history-tabs.scss @@ -0,0 +1,32 @@ +.history-tabs { + background: $whitish; + display: flex; + flex-direction: row; + a { + color: $grayer; + display: inline-block; + padding: .75rem 1rem; + &:hover { + color: $primary; + } + } + .history-tab { + @include font-type(bold); + border-bottom: 3px solid transparent; + color: $gray-light; + transition: all .1s linear; + &.active { + border-bottom: 3px solid $grayer; + color: $grayer; + } + } + .order-comments { + @include font-type(light); + margin-left: auto; + transition: none; + } + .icon-arrow-up, + .icon-arrow-down { + @include svg-size(.75rem); + } +} diff --git a/app/modules/history/history.controller.coffee b/app/modules/history/history.controller.coffee new file mode 100644 index 00000000..3eff5130 --- /dev/null +++ b/app/modules/history/history.controller.coffee @@ -0,0 +1,91 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: history.controller.coffee +### + +module = angular.module("taigaHistory") + +class HistorySectionController + @.$inject = [ + "$tgResources", + "$tgRepo", + "$tgStorage", + ] + + constructor: (@rs, @repo, @storage) -> + @.viewComments = true + @._loadHistory() + @.reverse = @storage.get("orderComments") + + _loadHistory: () -> + @rs.history.get(@.name, @.id).then (history) => + @._getComments(history) + @._getActivities(history) + + _getComments: (comments) -> + @.comments = _.filter(comments, (item) -> item.comment != "") + if @.reverse + @.comments - _.reverse(@.comments) + @.commentsNum = @.comments.length + + _getActivities: (activities) -> + @.activities = _.filter(activities, (item) -> Object.keys(item.values_diff).length > 0) + @.activitiesNum = @.activities.length + + onActiveHistoryTab: (active) -> + @.viewComments = active + + deleteComment: (commentId) -> + type = @.name + objectId = @.id + activityId = commentId + @.deleting = true + return @rs.history.deleteComment(type, objectId, activityId).then => + @._loadHistory() + @.deleting = false + + editComment: (commentId, comment) -> + type = @.name + objectId = @.id + activityId = commentId + @.editing = true + return @rs.history.editComment(type, objectId, activityId, comment).then => + @._loadHistory() + @.editing = false + + restoreDeletedComment: (commentId) -> + type = @.name + objectId = @.id + activityId = commentId + @.editing = true + return @rs.history.undeleteComment(type, objectId, activityId).then => + @._loadHistory() + @.editing = false + + addComment: () -> + type = @.type + @.loading = true + @repo.save(@.type).then => + @._loadHistory() + @.loading = false + + onOrderComments: () -> + @.reverse = !@.reverse + @storage.set("orderComments", @.reverse) + @._loadHistory() + +module.controller("HistorySection", HistorySectionController) diff --git a/app/modules/history/history.controller.spec.coffee b/app/modules/history/history.controller.spec.coffee new file mode 100644 index 00000000..bd77887f --- /dev/null +++ b/app/modules/history/history.controller.spec.coffee @@ -0,0 +1,214 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: subscriptions.controller.spec.coffee +### + +describe "HistorySection", -> + provide = null + controller = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + history: { + get: sinon.stub() + deleteComment: sinon.stub() + undeleteComment: sinon.stub() + editComment: sinon.stub() + } + } + + provide.value "$tgResources", mocks.tgResources + + _mockTgRepo = () -> + mocks.tgRepo = { + save: sinon.stub() + } + + provide.value "$tgRepo", mocks.tgRepo + + _mocktgStorage = () -> + mocks.tgStorage = { + get: sinon.stub() + set: sinon.stub() + } + provide.value "$tgStorage", mocks.tgStorage + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + _mockTgRepo() + _mocktgStorage() + return null + + beforeEach -> + module "taigaHistory" + + _mocks() + + inject ($controller) -> + controller = $controller + promise = mocks.tgResources.history.get.promise().resolve() + + it "load historic", (done) -> + historyCtrl = controller "HistorySection" + + historyCtrl._getComments = sinon.stub() + historyCtrl._getActivities = sinon.stub() + + name = "name" + id = 4 + + promise = mocks.tgResources.history.get.withArgs(name, id).promise().resolve() + historyCtrl._loadHistory().then (data) -> + expect(historyCtrl._getComments).have.been.calledWith(data) + expect(historyCtrl._getActivities).have.been.calledWith(data) + done() + + it "get Comments older first", () -> + historyCtrl = controller "HistorySection" + + comments = ['comment3', 'comment2', 'comment1'] + historyCtrl.reverse = false + + historyCtrl._getComments(comments) + expect(historyCtrl.comments).to.be.eql(['comment3', 'comment2', 'comment1']) + expect(historyCtrl.commentsNum).to.be.equal(3) + + it "get Comments newer first", () -> + historyCtrl = controller "HistorySection" + + comments = ['comment3', 'comment2', 'comment1'] + historyCtrl.reverse = true + + historyCtrl._getComments(comments) + expect(historyCtrl.comments).to.be.eql(['comment1', 'comment2', 'comment3']) + expect(historyCtrl.commentsNum).to.be.equal(3) + + it "get activities", () -> + historyCtrl = controller "HistorySection" + activities = { + 'activity1': { + 'values_diff': {"k1": [0, 1]} + }, + 'activity2': { + 'values_diff': {"k2": [0, 1]} + }, + 'activity3': { + 'values_diff': {"k3": [0, 1]} + }, + } + + historyCtrl._getActivities(activities) + + historyCtrl.activities = activities + expect(historyCtrl.activitiesNum).to.be.equal(3) + + it "on active history tab", () -> + historyCtrl = controller "HistorySection" + active = true + historyCtrl.onActiveHistoryTab(active) + expect(historyCtrl.viewComments).to.be.true + + it "on inactive history tab", () -> + historyCtrl = controller "HistorySection" + active = false + historyCtrl.onActiveHistoryTab(active) + expect(historyCtrl.viewComments).to.be.false + + it "delete comment", () -> + historyCtrl = controller "HistorySection" + historyCtrl._loadHistory = sinon.stub() + + historyCtrl.name = "type" + historyCtrl.id = 1 + + type = historyCtrl.name + objectId = historyCtrl.id + commentId = 7 + + promise = mocks.tgResources.history.deleteComment.withArgs(type, objectId, commentId).promise().resolve() + + historyCtrl.deleting = true + historyCtrl.deleteComment(commentId).then () -> + expect(historyCtrl._loadHistory).have.been.called + expect(historyCtrl.deleting).to.be.false + + it "edit comment", () -> + historyCtrl = controller "HistorySection" + historyCtrl._loadHistory = sinon.stub() + + historyCtrl.name = "type" + historyCtrl.id = 1 + activityId = 7 + comment = "blablabla" + + type = historyCtrl.name + objectId = historyCtrl.id + commentId = activityId + + promise = mocks.tgResources.history.editComment.withArgs(type, objectId, activityId, comment).promise().resolve() + + historyCtrl.editing = true + historyCtrl.editComment(commentId, comment).then () -> + expect(historyCtrl._loadHistory).has.been.called + expect(historyCtrl.editing).to.be.false + + it "restore comment", () -> + historyCtrl = controller "HistorySection" + historyCtrl._loadHistory = sinon.stub() + + historyCtrl.name = "type" + historyCtrl.id = 1 + activityId = 7 + + type = historyCtrl.name + objectId = historyCtrl.id + commentId = activityId + + promise = mocks.tgResources.history.undeleteComment.withArgs(type, objectId, activityId).promise().resolve() + + historyCtrl.editing = true + historyCtrl.restoreDeletedComment(commentId).then () -> + expect(historyCtrl._loadHistory).has.been.called + expect(historyCtrl.editing).to.be.false + + it "add comment", () -> + historyCtrl = controller "HistorySection" + historyCtrl._loadHistory = sinon.stub() + + historyCtrl.type = "type" + type = historyCtrl.type + historyCtrl.loading = true + + promise = mocks.tgRepo.save.withArgs(type).promise().resolve() + + historyCtrl.addComment().then () -> + expect(historyCtrl._loadHistory).has.been.called + expect(historyCtrl.loading).to.be.false + + it "order comments", () -> + historyCtrl = controller "HistorySection" + historyCtrl._loadHistory = sinon.stub() + + historyCtrl.reverse = false + + historyCtrl.onOrderComments() + expect(historyCtrl.reverse).to.be.true + expect(mocks.tgStorage.set).has.been.calledWith("orderComments", historyCtrl.reverse) + expect(historyCtrl._loadHistory).has.been.called diff --git a/app/modules/history/history.directive.coffee b/app/modules/history/history.directive.coffee new file mode 100644 index 00000000..eebaf063 --- /dev/null +++ b/app/modules/history/history.directive.coffee @@ -0,0 +1,42 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: history.directive.coffee +### + +module = angular.module('taigaHistory') + +HistorySectionDirective = () -> + link = (scope, el, attr, ctrl) -> + scope.$on "object:updated", -> ctrl._loadHistory(scope.type, scope.id) + + return { + link: link, + templateUrl:"history/history.html", + controller: "HistorySection", + controllerAs: "vm", + bindToController: true, + scope: { + type: "=", + name: "@", + id: "=", + projectId: "=" + } + } + +HistorySectionDirective.$inject = [] + +module.directive("tgHistorySection", HistorySectionDirective) diff --git a/app/modules/history/history.jade b/app/modules/history/history.jade new file mode 100644 index 00000000..e5fb19d3 --- /dev/null +++ b/app/modules/history/history.jade @@ -0,0 +1,29 @@ +section.history + tg-history-tabs( + on-active-comments="vm.onActiveHistoryTab(true)" + on-active-activities="vm.onActiveHistoryTab(false)" + active-tab="vm.viewComments", + on-order-comments="vm.onOrderComments()" + comments-num="vm.commentsNum" + activities-num="vm.activitiesNum" + on-reverse="vm.reverse" + ) + tg-comments( + ng-if="vm.viewComments" + comments="vm.comments" + on-delete-comment="vm.deleteComment(commentId)" + on-restore-deleted-comment="vm.restoreDeletedComment(commentId)" + on-add-comment="vm.addComment()" + on-edit-comment="vm.editComment(commentId, commentData)" + object="{{vm.id}}" + type="vm.type" + name="{{vm.name}}" + loading="vm.loading" + editing="vm.editing" + deleting="vm.deleting" + project-id="vm.projectId" + ) + tg-history( + ng-if="!vm.viewComments" + activities="vm.activities" + ) diff --git a/app/modules/history/history.module.coffee b/app/modules/history/history.module.coffee new file mode 100644 index 00000000..6089087a --- /dev/null +++ b/app/modules/history/history.module.coffee @@ -0,0 +1,20 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: history.module.coffee +### + +angular.module("taigaHistory", []) diff --git a/app/modules/history/history/history-diff.controller.coffee b/app/modules/history/history/history-diff.controller.coffee new file mode 100644 index 00000000..4096f44d --- /dev/null +++ b/app/modules/history/history/history-diff.controller.coffee @@ -0,0 +1,34 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: history.controller.coffee +### + +module = angular.module("taigaHistory") + +class ActivitiesDiffController + @.$inject = [ + ] + + constructor: () -> + + diffTags: () -> + if @.type == 'tags' + @.diffRemoveTags = _.difference(@.diff[0], @.diff[1]).toString() + @.diffAddTags = _.difference(@.diff[1], @.diff[0]).toString() + + +module.controller("ActivitiesDiffCtrl", ActivitiesDiffController) diff --git a/app/modules/history/history/history-diff.controller.spec.coffee b/app/modules/history/history/history-diff.controller.spec.coffee new file mode 100644 index 00000000..c04900d9 --- /dev/null +++ b/app/modules/history/history/history-diff.controller.spec.coffee @@ -0,0 +1,43 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: subscriptions.controller.spec.coffee +### + +describe "ActivitiesDiffController", -> + provide = null + controller = null + mocks = {} + + beforeEach -> + module "taigaHistory" + + inject ($controller) -> + controller = $controller + + it "Check diff between tags", () -> + activitiesDiffCtrl = controller "ActivitiesDiffCtrl" + + activitiesDiffCtrl.type = "tags" + + activitiesDiffCtrl.diff = [ + ["architecto", "perspiciatis", "testafo"], + ["architecto", "perspiciatis", "testafo", "fasto"] + ] + + activitiesDiffCtrl.diffTags() + expect(activitiesDiffCtrl.diffRemoveTags).to.be.equal('') + expect(activitiesDiffCtrl.diffAddTags).to.be.equal('fasto') diff --git a/app/modules/history/history/history-diff.directive.coffee b/app/modules/history/history/history-diff.directive.coffee new file mode 100644 index 00000000..481c27ec --- /dev/null +++ b/app/modules/history/history/history-diff.directive.coffee @@ -0,0 +1,38 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: history.directive.coffee +### + +module = angular.module('taigaHistory') + +HistoryDiffDirective = () -> + link = (scope, el, attrs, ctrl) -> + ctrl.diffTags() + + return { + scope: { + type: "<", + diff: "<" + }, + templateUrl:"history/history/history-diff.html", + controller: "ActivitiesDiffCtrl", + controllerAs: 'vm', + bindToController: true, + link: link + } + +module.directive("tgHistoryDiff", HistoryDiffDirective) diff --git a/app/modules/history/history/history-diff.jade b/app/modules/history/history/history-diff.jade new file mode 100644 index 00000000..aa6d53c8 --- /dev/null +++ b/app/modules/history/history/history-diff.jade @@ -0,0 +1,61 @@ +.diff-wrapper( + ng-if="vm.type == 'points'" +) + include history-templates/history-points + +.diff-wrapper( + ng-if="vm.type == 'attachments'" +) + include history-templates/history-attachments + +.diff-wrapper( + ng-if="vm.type == 'milestone'" +) + include history-templates/history-milestone + +.diff-wrapper( + ng-if="vm.type == 'status'" +) + include history-templates/history-status + +.diff-wrapper( + ng-if="vm.type == 'subject'" +) + include history-templates/history-subject + +.diff-wrapper( + ng-if="vm.type == 'description_diff'" +) + include history-templates/history-description + +.diff-wrapper( + ng-if="vm.type == 'assigned_to'" +) + include history-templates/history-assigned + +.diff-wrapper( + ng-if="vm.type == 'tags'" +) + include history-templates/history-tags + +.diff-wrapper( + ng-if="vm.type == 'custom_attributes'" +) + include history-templates/history-custom-attributes + +.diff-wrapper( + ng-if="vm.type == 'team_requirement'" +) + include history-templates/team-requirement + +.diff-wrapper( + ng-if="vm.type == 'client_requirement'" +) + include history-templates/client-requirement + +.diff-wrapper( + ng-if="vm.type == 'is_blocked'" +) + include history-templates/blocked + + diff --git a/app/modules/history/history/history-templates/blocked.jade b/app/modules/history/history/history-templates/blocked.jade new file mode 100644 index 00000000..7a2fb580 --- /dev/null +++ b/app/modules/history/history/history-templates/blocked.jade @@ -0,0 +1,9 @@ +.diff-status-wrapper + span.key( + translate="ACTIVITY.BLOCKED" + ) + span.diff {{vm.diff[0]}} + tg-svg( + svg-icon="icon-arrow-right" + ) + span.diff {{vm.diff[1]}} diff --git a/app/modules/history/history/history-templates/client-requirement.jade b/app/modules/history/history/history-templates/client-requirement.jade new file mode 100644 index 00000000..10649a6a --- /dev/null +++ b/app/modules/history/history/history-templates/client-requirement.jade @@ -0,0 +1,9 @@ +.diff-status-wrapper + span.key( + translate="ACTIVITY.CLIENT_REQUIREMENT" + ) + span.diff {{vm.diff[0]}} + tg-svg( + svg-icon="icon-arrow-right" + ) + span.diff {{vm.diff[1]}} diff --git a/app/modules/history/history/history-templates/history-assigned.jade b/app/modules/history/history/history-templates/history-assigned.jade new file mode 100644 index 00000000..ab57ac18 --- /dev/null +++ b/app/modules/history/history/history-templates/history-assigned.jade @@ -0,0 +1,9 @@ +.diff-status-wrapper + span.key( + translate="ACTIVITY.FIELDS.ASSIGNED_TO" + ) + span.diff(ng-if="vm.diff[0]") {{vm.diff[0]}} + tg-svg( + svg-icon="icon-arrow-right" + ) + span.diff(ng-if="vm.diff[1]") {{vm.diff[1]}} diff --git a/app/modules/history/history/history-templates/history-attachments.jade b/app/modules/history/history/history-templates/history-attachments.jade new file mode 100644 index 00000000..6deebc32 --- /dev/null +++ b/app/modules/history/history/history-templates/history-attachments.jade @@ -0,0 +1,37 @@ +.diff-attachments-new( + ng-if="vm.diff.new.length" + ng-repeat="newAttachment in vm.diff.new" +) + span.key(translate="ACTIVITY.NEW_ATTACHMENT") + span.diff {{newAttachment.filename}} +.diff-attachments-update( + ng-if="vm.diff.changed.length" + ng-repeat="editAttachment in vm.diff.changed" +) + span.key( + translate="ACTIVITY.UPDATED_ATTACHMENT" + translate-values="{filename: editAttachment.filename}" + ) + span.diff(ng-if="editAttachment.changes.is_deprecated") + span( + ng-if="editAttachment.changes.is_deprecated[1] == false" + translate="ACTIVITY.BECAME_UNDEPRECATED" + ) + span( + ng-if="editAttachment.changes.is_deprecated[1] == true" + translate="ACTIVITY.BECAME_DEPRECATED" + ) + span.diff(ng-if="editAttachment.changes.description") + span(ng-if='editAttachment.changes.description[0].length') {{editAttachment.changes.description[0]}} + span(ng-if='!editAttachment.changes.description[0].length') ... + tg-svg( + svg-icon="icon-arrow-right" + ) + span {{editAttachment.changes.description[1]}} + +.diff-attachments-deleted( + ng-if="vm.diff.deleted.length" + ng-repeat="deletedAttachment in vm.diff.deleted" +) + span.key(translate="ACTIVITY.DELETED_ATTACHMENT") + span.diff {{deletedAttachment.filename}} diff --git a/app/modules/history/history/history-templates/history-custom-attributes.jade b/app/modules/history/history/history-templates/history-custom-attributes.jade new file mode 100644 index 00000000..69d6a1bf --- /dev/null +++ b/app/modules/history/history/history-templates/history-custom-attributes.jade @@ -0,0 +1,19 @@ +.diff-custom-new( + ng-if="vm.diff.new.length" + ng-repeat="newCustom in vm.diff.new" +) + span.key(translate="ACTIVITY.CREATED_CUSTOM_ATTRIBUTE") + span.diff ({{newCustom.name}}) + span.diff {{newCustom.value}} + +.diff-custom-new( + ng-if="vm.diff.changed.length" + ng-repeat="changeCustom in vm.diff.changed" +) + span.key(translate="ACTIVITY.UPDATED_CUSTOM_ATTRIBUTE") + span.diff ({{changeCustom.name}}) + span.diff {{changeCustom.changes.value[0]}} + tg-svg( + svg-icon="icon-arrow-right" + ) + span.diff {{changeCustom.changes.value[1]}} diff --git a/app/modules/history/history/history-templates/history-description.jade b/app/modules/history/history/history-templates/history-description.jade new file mode 100644 index 00000000..9c4dafb8 --- /dev/null +++ b/app/modules/history/history/history-templates/history-description.jade @@ -0,0 +1,12 @@ +.diff-status-wrapper + p.key( + translate="ACTIVITY.FIELDS.DESCRIPTION" + ) + p.diff( + ng-if="vm.diff[0]" + ng-bind-html="vm.diff[0]" + ) + p.diff( + ng-if="vm.diff[1]" + ng-bind-html="vm.diff[1]" + ) diff --git a/app/modules/history/history/history-templates/history-milestone.jade b/app/modules/history/history/history-templates/history-milestone.jade new file mode 100644 index 00000000..61b78d3d --- /dev/null +++ b/app/modules/history/history/history-templates/history-milestone.jade @@ -0,0 +1,11 @@ +.diff-milestone-wrapper + span.key( + translate="ACTIVITY.FIELDS.MILESTONE" + ) + span.diff(ng-if="vm.diff[0] != null") {{vm.diff[0]}} + span.diff(ng-if="vm.diff[0] == null") ... + tg-svg( + svg-icon="icon-arrow-right" + ) + span.diff(ng-if="vm.diff[1] != null") {{vm.diff[1]}} + span.diff(ng-if="vm.diff[1] == null") ... diff --git a/app/modules/history/history/history-templates/history-points.jade b/app/modules/history/history/history-templates/history-points.jade new file mode 100644 index 00000000..85c99704 --- /dev/null +++ b/app/modules/history/history/history-templates/history-points.jade @@ -0,0 +1,11 @@ +.diff-points-wrapper(ng-repeat="(key, diff) in vm.diff") + span.key( + translate="ACTIVITY.US_POINTS" + translate-values="{role: vm.diff.key}" + ) + span.diff {{diff[0]}} + tg-svg.comment-option( + svg-icon="icon-arrow-right" + svg-title-translate="COMMON.EDIT" + ) + span.diff {{diff[1]}} diff --git a/app/modules/history/history/history-templates/history-status.jade b/app/modules/history/history/history-templates/history-status.jade new file mode 100644 index 00000000..33af2ea2 --- /dev/null +++ b/app/modules/history/history/history-templates/history-status.jade @@ -0,0 +1,9 @@ +.diff-status-wrapper + span.key( + translate="ACTIVITY.FIELDS.STATUS" + ) + span.diff(ng-if="vm.diff[0]") {{vm.diff[0]}} + tg-svg( + svg-icon="icon-arrow-right" + ) + span.diff(ng-if="vm.diff[1]") {{vm.diff[1]}} diff --git a/app/modules/history/history/history-templates/history-subject.jade b/app/modules/history/history/history-templates/history-subject.jade new file mode 100644 index 00000000..e038ba01 --- /dev/null +++ b/app/modules/history/history/history-templates/history-subject.jade @@ -0,0 +1,9 @@ +.diff-subject-wrapper + span.key( + translate="ACTIVITY.FIELDS.SUBJECT" + ) + span.diff(ng-if="vm.diff[0]") {{vm.diff[0]}} + tg-svg( + svg-icon="icon-arrow-right" + ) + span.diff(ng-if="vm.diff[1]") {{vm.diff[1]}} diff --git a/app/modules/history/history/history-templates/history-tags.jade b/app/modules/history/history/history-templates/history-tags.jade new file mode 100644 index 00000000..32ec884b --- /dev/null +++ b/app/modules/history/history/history-templates/history-tags.jade @@ -0,0 +1,8 @@ +.diff-tags-wrapper + p(ng-if="vm.diffRemoveTags") + span.key(translate="ACTIVITY.TAGS_REMOVED") + span.diff {{vm.diffRemoveTags}} + + p(ng-if="vm.diffAddTags") + span.key(translate="ACTIVITY.TAGS_ADDED") + span.diff {{vm.diffAddTags}} diff --git a/app/modules/history/history/history-templates/history-templates.scss b/app/modules/history/history/history-templates/history-templates.scss new file mode 100644 index 00000000..69773c26 --- /dev/null +++ b/app/modules/history/history/history-templates/history-templates.scss @@ -0,0 +1,25 @@ +.activity-diff { + .key { + @include font-type(bold); + background: $whitish; + margin-right: .5rem; + padding: .25rem; + } + .diff { + line-height: 1.6; + } + .icon-arrow-right { + @include svg-size(.75rem); + fill: $gray-light; + margin: 0 .5rem; + } + .diff-status-wrapper { + p { + display: inline-block; + } + del { + background: lighten(rgba($primary-light, .3), 20%); + text-decoration: underline; + } + } +} diff --git a/app/modules/history/history/history-templates/team-requirement.jade b/app/modules/history/history/history-templates/team-requirement.jade new file mode 100644 index 00000000..592d1e50 --- /dev/null +++ b/app/modules/history/history/history-templates/team-requirement.jade @@ -0,0 +1,9 @@ +.diff-status-wrapper + span.key( + translate="ACTIVITY.TEAM_REQUIREMENT" + ) + span.diff {{vm.diff[0]}} + tg-svg( + svg-icon="icon-arrow-right" + ) + span.diff {{vm.diff[1]}} diff --git a/app/modules/history/history/history.directive.coffee b/app/modules/history/history/history.directive.coffee new file mode 100644 index 00000000..40862178 --- /dev/null +++ b/app/modules/history/history/history.directive.coffee @@ -0,0 +1,33 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: history.directive.coffee +### + +module = angular.module('taigaHistory') + +HistoryDirective = () -> + link = (scope, el, attrs) -> + + return { + scope: { + activities: "<" + }, + templateUrl:"history/history/history.html", + link: link + } + +module.directive("tgHistory", HistoryDirective) diff --git a/app/modules/history/history/history.jade b/app/modules/history/history/history.jade new file mode 100644 index 00000000..d53d022c --- /dev/null +++ b/app/modules/history/history/history.jade @@ -0,0 +1,19 @@ +section.activities + .activities-wrapper + .activity(ng-repeat="activity in activities track by activity.id") + img.activity-avatar( + ng-src="{{activity.user.photo}}" + ng-alt="{{activity.user.name}}" + ) + .activity-main + .activity-data + span.activity-creator {{activity.user.name}} + span.activity-date {{activity.created_at | momentFormat:'DD MMM YYYY HH:mm'}} + p.activity-text(ng-if="activity.comment") {{activity.comment}} + + .activity-diff( + ng-repeat="(key, diff) in activity.values_diff" + tg-history-diff + type='key' + diff='diff' + ) diff --git a/app/modules/history/history/history.scss b/app/modules/history/history/history.scss new file mode 100644 index 00000000..840e79f2 --- /dev/null +++ b/app/modules/history/history/history.scss @@ -0,0 +1,49 @@ +.activities { + .activity { + align-items: flex-start; + border-bottom: 1px solid $whitish; + display: flex; + padding: 2rem 0; + } + .activity-avatar { + flex-basis: 50px; + flex-shrink: 0; + margin-right: 1.5rem; + } + .activity-data { + margin-bottom: 1rem; + } + .activity-creator { + color: $primary; + margin-right: .5rem; + } + .activity-date { + color: $gray-light; + } + .comment-options { + align-items: center; + align-self: stretch; + display: flex; + flex-basis: 50px; + flex-shrink: 0; + margin-left: 1.5rem; + .comment-option { + cursor: pointer; + opacity: 0; + transition: opacity .2s; + } + .icon-edit { + fill: $gray-light; + margin-right: .5rem; + &:hover { + fill: $gray; + } + } + .icon-trash { + fill: $red-light; + &:hover { + fill: $red; + } + } + } +} diff --git a/app/modules/services/check-permissions.service.coffee b/app/modules/services/check-permissions.service.coffee index 5ca652e3..ad0cd7e9 100644 --- a/app/modules/services/check-permissions.service.coffee +++ b/app/modules/services/check-permissions.service.coffee @@ -19,7 +19,7 @@ taiga = @.taiga -class ChekcPermissionsService +class CheckPermissionsService @.$inject = [ "tgProjectService" ] @@ -31,4 +31,4 @@ class ChekcPermissionsService return @projectService.project.get('my_permissions').indexOf(permission) != -1 -angular.module("taigaCommon").service("tgCheckPermissionsService", ChekcPermissionsService) +angular.module("taigaCommon").service("tgCheckPermissionsService", CheckPermissionsService) diff --git a/app/partials/common/components/wysiwyg.jade b/app/partials/common/components/wysiwyg.jade index 97a62681..f68d4467 100644 --- a/app/partials/common/components/wysiwyg.jade +++ b/app/partials/common/components/wysiwyg.jade @@ -1,5 +1,5 @@ mixin wysihelp - div.wysiwyg-help + .wysiwyg-help span.drag-drop-help(ng-if="wiki.id", translate="COMMON.WYSIWYG.ATTACH_FILE_HELP") span.drag-drop-help(ng-if="!wiki.id", translate="COMMON.WYSIWYG.ATTACH_FILE_HELP_SAVE_FIRST") a.help-markdown( diff --git a/app/partials/common/history/history-activity.jade b/app/partials/common/history/history-activity.jade deleted file mode 100644 index 3b58f956..00000000 --- a/app/partials/common/history/history-activity.jade +++ /dev/null @@ -1,41 +0,0 @@ -.activity-single(class!="<%- mode %>") - .activity-user - a.avatar(href!="<%- userProfileUrl %>", title!="<%- userFullName %>") - img(src!="<%- avatar %>", alt!="<%- userFullName %>") - .activity-content - .activity-username - a.username(href!="<%- userProfileUrl %>", title!="<%- userFullName %>") - | <%- userFullName %> - span.date - | <%- creationDate %> - - <% if (comment.length > 0) { %> - <% if ((deleteCommentDate || deleteCommentUser)) { %> - .deleted-comment - span(translate="COMMENTS.DELETED_INFO", - translate-values!="{ user: '<%- deleteCommentUser %>', date: '<%- deleteCommentDate %>'}") - <% } %> - .comment.wysiwyg - div(ng-non-bindable) - | <%= comment %> - <% if (!deleteCommentDate && mode !== "activity" && canDeleteComment) { %> - a.comment-delete( - href="", - title!="<%- deleteCommentActionTitle %>", - data-activity-id!="<%- activityId %>" - ) - tg-svg(svg-icon="icon-trash") - <% } %> - <% } %> - - <% if(changes.length > 0) { %> - .changes - <% if (mode != "activity") { %> - a.changes-title(href="", title="{{'ACTIVITY.SHOW_ACTIVITY' | translate}}") - span <%- changesText %> - tg-svg(svg-icon="icon-arrow-right") - <% } %> - <% _.each(changes, function(change) { %> - | <%= change %> - <% }) %> - <% } %> diff --git a/app/partials/common/history/history-base-entries.jade b/app/partials/common/history/history-base-entries.jade deleted file mode 100644 index fe662b98..00000000 --- a/app/partials/common/history/history-base-entries.jade +++ /dev/null @@ -1,6 +0,0 @@ -<% if (showMore > 0) { %> -a(href="" title="{{ 'ACTIVITY.SHOW_MORE' | translate}}" class="show-more show-more-comments", translate="ACTIVITY.SHOW_MORE", translate-values!="{showMore: '<%- showMore %>'}") -<% } %> -<% _.each(entries, function(entry) { %> -<%= entry %> -<% }) %> \ No newline at end of file diff --git a/app/partials/common/history/history-base.jade b/app/partials/common/history/history-base.jade deleted file mode 100644 index 1fddd9c4..00000000 --- a/app/partials/common/history/history-base.jade +++ /dev/null @@ -1,35 +0,0 @@ -include ../components/wysiwyg.jade - -section.history - <% if (commentsVisible || historyVisible) { %> - ul.history-tabs - <% if (commentsVisible) { %> - li.active - a( - href="", - data-section-class="history-comments" - ) - tg-svg(svg-icon="icon-writer") - span.tab-title(translate="COMMENTS.TITLE") - <% } %> - <% if (historyVisible) { %> - li - a( - href="", - data-section-class="history-activity" - ) - tg-svg(svg-icon="icon-timeline") - span.tab-title(translate="ACTIVITY.TITLE") - <% } %> - <% } %> - section.history-comments - .comments-list - div(tg-editable-wysiwyg, ng-model!="<%- ngmodel %>") - div(tg-check-permission!="modify_<%- type %>", tg-toggle-comment, class="add-comment") - textarea(ng-attr-placeholder="{{'COMMENTS.TYPE_NEW_COMMENT' | translate}}", ng-model!="<%- ngmodel %>.comment", tg-markitup="tg-markitup") - <% if (mode !== "edit") { %> - +wysihelp - button(type="button", ng-disabled!="!<%- ngmodel %>.comment.length" title="{{'COMMENTS.COMMENT' | translate}}", translate="COMMENTS.COMMENT", class="button button-green save-comment") - <% } %> - section.history-activity.hidden - .changes-list diff --git a/app/partials/common/history/history-change-attachment.jade b/app/partials/common/history/history-change-attachment.jade deleted file mode 100644 index 6b12816f..00000000 --- a/app/partials/common/history/history-change-attachment.jade +++ /dev/null @@ -1,16 +0,0 @@ -.change-entry - .activity-changed - span <%- name %> - .activity-fromto - <% _.each(diff, function(change) { %> - p - strong <%- change.name %>  - strong(translate="COMMON.FROM") - br - span <%- change.from %> - p - strong <%- change.name %>  - strong(translate="COMMON.TO") - br - span <%- change.to %> - <% }) %> diff --git a/app/partials/common/history/history-change-diff.jade b/app/partials/common/history/history-change-diff.jade deleted file mode 100644 index 13e137bd..00000000 --- a/app/partials/common/history/history-change-diff.jade +++ /dev/null @@ -1,6 +0,0 @@ -.change-entry - .activity-changed - span <%- name %> - .activity-fromto - p - span <%= diff %> diff --git a/app/partials/common/history/history-change-generic.jade b/app/partials/common/history/history-change-generic.jade deleted file mode 100644 index 8c29b5c1..00000000 --- a/app/partials/common/history/history-change-generic.jade +++ /dev/null @@ -1,12 +0,0 @@ -.change-entry - .activity-changed - span <%- name %> - .activity-fromto - p - strong(translate="COMMON.FROM") - br - span <%- from %> - p - strong(translate="COMMON.TO") - br - span <%- to %> diff --git a/app/partials/common/history/history-change-list.jade b/app/partials/common/history/history-change-list.jade deleted file mode 100644 index 038cfc88..00000000 --- a/app/partials/common/history/history-change-list.jade +++ /dev/null @@ -1,17 +0,0 @@ -.change-entry - .activity-changed - span <%- name %> - .activity-fromto - <% if (removed.length > 0) { %> - p - strong(translate="ACTIVITY.REMOVED") - br - span <%- removed %> - <% } %> - - <% if (added.length > 0) { %> - p - strong(translate="ACTIVITY.ADDED") - br - span <%- added %> - <% } %> diff --git a/app/partials/common/history/history-change-points.jade b/app/partials/common/history/history-change-points.jade deleted file mode 100644 index 89429a93..00000000 --- a/app/partials/common/history/history-change-points.jade +++ /dev/null @@ -1,14 +0,0 @@ -<% _.each(points, function(point, name) { %> -.change-entry - .activity-changed - span(translate="ACTIVITY.US_POINTS", translate-values!="{name: '<%- name %>'}") - .activity-fromto - p - strong(translate="COMMON.FROM") - br - span <%- point[0] %> - p - strong(translate="COMMON.TO") - br - span <%- point[1] %> -<% }); %> diff --git a/app/partials/common/history/history-deleted-comment.jade b/app/partials/common/history/history-deleted-comment.jade deleted file mode 100644 index 73a2f2e2..00000000 --- a/app/partials/common/history/history-deleted-comment.jade +++ /dev/null @@ -1,18 +0,0 @@ -.activity-single.comment.deleted-comment - div - span(translate="COMMENTS.DELETED_INFO", - translate-values!="{user: '<%- deleteCommentUser %>', date: '<%- deleteCommentDate %>'}") - a(href="", title="{{'COMMENTS.SHOW_DELETED' | translate}}", - class="show-deleted-comment", translate="COMMENTS.SHOW_DELETED") - a(href="", title="{{'COMMENTS.HIDE_DELETED' | translate}}", - class="hide-deleted-comment hidden", translate="COMMENTS.HIDE_DELETED") - .comment-body.wysiwyg <%= deleteComment %> - <% if (canRestoreComment) { %> - a.comment-restore( - href="" - data-activity-id!="<%- activityId %>" - title="{{ 'COMMENTS.RESTORE' | translate }}" - ) - tg-svg(svg-icon="icon-reload") - span(translate="COMMENTS.RESTORE") - <% } %> diff --git a/app/partials/issue/issues-detail.jade b/app/partials/issue/issues-detail.jade index aa8dfefa..ab8bce2c 100644 --- a/app/partials/issue/issues-detail.jade +++ b/app/partials/issue/issues-detail.jade @@ -92,10 +92,12 @@ div.wrapper( project-id="projectId" edit-permission = "modify_issue" ) - - tg-history( - ng-model="issue" + + tg-history-section( + ng-if="issue" type="issue" + name="issue" + id="issue.id" ) sidebar.menu-secondary.sidebar.ticket-data diff --git a/app/partials/task/task-detail.jade b/app/partials/task/task-detail.jade index a0d00f8d..68d7dff9 100644 --- a/app/partials/task/task-detail.jade +++ b/app/partials/task/task-detail.jade @@ -94,8 +94,13 @@ div.wrapper( project-id="projectId" edit-permission = "modify_task" ) - - tg-history(ng-model="task", type="task") + + tg-history-section( + ng-if="task" + type="task" + name="task" + id="task.id" + ) sidebar.menu-secondary.sidebar.ticket-data diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index 0896a232..54976f35 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -90,9 +90,12 @@ div.wrapper( edit-permission = "modify_us" ) - tg-history( - ng-model="us" + tg-history-section( + ng-if="us" type="us" + name="us" + id="us.id" + project-id="projectId" ) sidebar.menu-secondary.sidebar.ticket-data diff --git a/app/styles/components/wysiwyg.scss b/app/styles/components/wysiwyg.scss index d091a699..dbf1220d 100644 --- a/app/styles/components/wysiwyg.scss +++ b/app/styles/components/wysiwyg.scss @@ -40,6 +40,10 @@ margin-bottom: 0; margin-top: 0; padding-left: 2em; + ul, + ol { + padding-left: 1rem; + } } ul { list-style-type: disc; diff --git a/app/styles/modules/common/history.scss b/app/styles/modules/common/history.scss deleted file mode 100644 index 52736c9f..00000000 --- a/app/styles/modules/common/history.scss +++ /dev/null @@ -1,278 +0,0 @@ -.history { - margin-bottom: 1rem; -} -.changes-title { - display: block; - padding: .5rem; - &:hover { - .icon { - color: $primary; - transform: rotate(90deg); - transition: all .2s linear; - } - } - .icon { - color: $grayer; - float: right; - transform: rotate(0); - transition: all .2s linear; - } -} -.change-entry { - border-bottom: 1px solid $gray-light; - display: flex; - padding: .5rem; - &:last-child { - border-bottom: 0; - } - .activity-changed, - .activity-fromto { - flex-basis: 50px; - flex-grow: 1; - } - .activity-changed { - @include font-type(bold); - } - .activity-fromto { - @include font-size(small); - word-wrap: break-word; - } -} -.history-tabs { - @include font-type(light); - border-bottom: 1px solid $whitish; - border-top: 1px solid $whitish; - margin-bottom: 0; - li { - background: $white; - display: inline-block; - position: relative; - &.active { - border-left: 1px solid $whitish; - border-right: 1px solid $whitish; - color: $primary; - top: 1px; - } - &:hover { - color: $grayer; - transition: color .2s ease-in; - } - } - a { - color: $gray-light; - display: block; - padding: .5rem 2rem; - transition: color .2s ease-in; - } - .icon { - fill: currentColor; - height: .75rem; - margin-right: .5rem; - width: .75rem; - } -} -.add-comment { - @include cursor-progress; - @include clearfix; - margin-top: 1rem; - &.active { - .button-green { - display: block; - margin-top: .5rem; - } - textarea { - height: 6rem; - transition: height .3s ease-in; - } - .help-markdown { - opacity: 1; - transition: opacity .3s linear; - } - .preview-icon { - opacity: 1; - position: absolute; - right: 1rem; - } - } - textarea { - background: $white; - height: 5rem; - min-height: 41px; - } - .help-markdown { - opacity: 0; - } - .save-comment { - color: $white; - float: right; - } - .button-green { - display: none; - } - .edit, - .preview-icon { - position: absolute; - right: 1rem; - top: .5rem; - } - .edit { - fill: $gray-light; - &:hover { - cursor: pointer; - fill: $primary; - } - } - .preview-icon { - opacity: 0; - } -} -.show-more-comments { - @include font-size(small); - border-bottom: 1px solid $gray-light; - border-top: 1px solid $gray-light; - color: $gray-light; - display: block; - padding: 1rem 0 1rem 1rem; - &:hover { - background: lighten($primary, 60%); - transition: background .2s ease-in; - } -} -.comment-list { - &.activeanimation { - .comment-single.ng-enter:last-child, - .comment-single.ng-leave:last-child { - transition: all .3s ease-in; - } - .comment-single.ng-enter:last-child, - .comment-single.ng-leave.ng-leave-active:last-child { - opacity: 0; - } - .comment-single.ng-leave:last-child, - .comment-single.ng-enter.ng-enter-active:last-child { - opacity: 1; - } - } -} -.activity-single { - border-bottom: 1px solid $gray-light; - display: flex; - padding: 2rem 0; - position: relative; - &:hover { - .comment-delete { - opacity: 1; - transition: opacity .2s linear; - } - .comment-restore { - opacity: 1; - transition: opacity .2s linear; - } - } - &:first-child { - margin-top: 0; - } - &:last-child { - border-bottom: 0; - } - &.deleted-comment, - .deleted-comment { - @include font-size(small); - color: $gray-light; - padding: .5rem; - a { - color: $gray-light; - margin-left: .3rem; - &:hover { - color: $primary; - transition: color .2s linear; - } - } - img { - filter: grayscale(100%); - opacity: .5; - } - .comment-body { - display: none; - margin: .2rem 0 .5rem; - p { - @include font-size(medium); - } - } - } - .comment-restore { - @include font-size(small); - color: $gray-light; - display: block; - position: absolute; - right: 0; - top: .4rem; - .icon { - vertical-align: baseline; - } - &:hover { - color: $primary; - transition: color .2s linear; - } - } - .username { - color: $primary; - margin-bottom: .5rem; - } - .activity-user { - flex-basis: 60px; - flex-shrink: 0; - margin-right: 1rem; - img { - width: 100%; - } - } - .activity-username { - color: $primary; - margin-bottom: .5rem; - } - .activity-content { - flex-shrink: 0; - width: calc(100% - 80px); - } - .changes { - background: $mass-white; - .change-entry { - display: none; - &.active { - display: flex; - } - } - } - .date { - @include font-size(small); - color: $gray-light; - margin-left: 1rem; - } - .wysiwyg { - margin-bottom: 0; - } - .comment-delete { - cursor: pointer; - display: block; - opacity: 0; - position: absolute; - right: .5rem; - top: 2rem; - svg { - fill: $red-light; - transition: all .2s linear; - } - &:hover { - svg { - fill: $red; - transition: color .2s linear; - } - } - } - &.activity { - .change-entry { - display: flex; - } - } -} diff --git a/e2e/helpers/detail-helper.js b/e2e/helpers/detail-helper.js index eb6c77f9..d0d33f7a 100644 --- a/e2e/helpers/detail-helper.js +++ b/e2e/helpers/detail-helper.js @@ -16,8 +16,9 @@ helper.title = function() { el.$('.edit-subject input').clear().sendKeys(title); }, - save: function() { + save: async function() { el.$('.save').click(); + await browser.waitForAngular(); } }; @@ -144,17 +145,37 @@ helper.assignedTo = function() { return obj; }; +helper.editComment = function() { + let el = $('.comment-editor'); + let obj = { + el:el, + + updateText: function (text) { + el.$('textarea').sendKeys(text); + }, + + saveComment: async function () { + el.$('.save-comment').click() + await browser.waitForAngular(); + } + } + return obj; + +}; + helper.history = function() { let el = $('section.history'); let obj = { el:el, - selectCommentsTab: function() { - el.$$('.history-tabs li a').first().click(); + selectCommentsTab: async function() { + el.$('.e2e-comments-tab').click(); + await browser.waitForAngular(); }, - selectActivityTab: function() { - el.$$('.history-tabs li a').last().click(); + selectActivityTab: async function() { + el.$('.e2e-activity-tab').click(); + await browser.waitForAngular(); }, addComment: async function(comment) { @@ -168,46 +189,65 @@ helper.history = function() { }, countComments: async function() { - let moreComments = el.$('.comments-list .show-more-comments'); - let moreCommentsIsPresent = await moreComments.isPresent(); - if (moreCommentsIsPresent){ - moreComments.click(); - } - await browser.waitForAngular(); - let comments = await el.$$(".activity-single.comment"); + let comments = await el.$$(".comment-wrapper"); return comments.length; }, countActivities: async function() { - let moreActivities = el.$('.changes-list .show-more-comments'); - let selectActivityTabIsPresent = await moreActivities.isPresent(); - if (selectActivityTabIsPresent){ - utils.common.link(moreActivities); - // moreActivities.click(); - } - await browser.waitForAngular(); - let activities = await el.$$(".activity-single.activity"); + let activities = await el.$$(".activity"); return activities.length; }, countDeletedComments: async function() { - let moreComments = el.$('.comments-list .show-more-comments'); - let moreCommentsIsPresent = await moreComments.isPresent(); - if (moreCommentsIsPresent){ - moreComments.click(); - } - await browser.waitForAngular(); - let comments = await el.$$(".activity-single.comment.deleted-comment"); + let comments = await el.$$(".deleted-comment-wrapper"); return comments.length; }, + editLastComment: async function() { + let lastComment = el.$$(".comment-wrapper").last() + browser + .actions() + .mouseMove(lastComment) + .perform(); + + lastComment.$$(".comment-option").first().click(); + await browser.waitForAngular(); + }, + deleteLastComment: async function() { - el.$$(".activity-single.comment .comment-delete").last().click(); + let lastComment = el.$$(".comment-wrapper").last() + browser + .actions() + .mouseMove(lastComment) + .perform(); + + lastComment.$$(".comment-option").last().click(); + await browser.waitForAngular(); + }, + + showVersionsLastComment: async function() { + el.$$(".comment-edited a").last().click(); + await browser.waitForAngular(); + }, + + closeVersionsLastComment: async function() { + $(".lightbox-display-historic .close").click(); + await browser.waitForAngular(); + }, + + enableEditModeLastComment: async function() { + let lastComment = el.$$(".comment-wrapper").last() + browser + .actions() + .mouseMove(lastComment) + .perform(); + + lastComment.$$(".comment-option").last().click(); await browser.waitForAngular(); }, restoreLastComment: async function() { - el.$$(".activity-single.comment.deleted-comment .comment-restore").last().click(); + el.$$(".deleted-comment-wrapper .restore-comment").last().click(); await browser.waitForAngular(); } } diff --git a/e2e/shared/detail.js b/e2e/shared/detail.js index 43f2525f..abfde5d2 100644 --- a/e2e/shared/detail.js +++ b/e2e/shared/detail.js @@ -1,4 +1,5 @@ var path = require('path'); +var utils = require('../utils'); var detailHelper = require('../helpers').detail; var commonHelper = require('../helpers').common; var customFieldsHelper = require('../helpers/custom-fields-helper'); @@ -191,29 +192,49 @@ shared.assignedToTesting = function() { }); } -shared.historyTesting = async function() { +shared.historyTesting = async function(screenshotsFolder) { let historyHelper = detailHelper.history(); + + //Adding a comment historyHelper.selectCommentsTab(); + await utils.common.takeScreenshot(screenshotsFolder, "show comments tab"); let commentsCounter = await historyHelper.countComments(); let date = Date.now(); - await historyHelper.addComment("New comment " + date); - let newCommentsCounter = await historyHelper.countComments(); + await historyHelper.addComment("New comment " + date); + await utils.common.takeScreenshot(screenshotsFolder, "new coment"); + + let newCommentsCounter = await historyHelper.countComments(); expect(newCommentsCounter).to.be.equal(commentsCounter+1); + //Edit last comment + historyHelper.editLastComment(); + let editComment = detailHelper.editComment(); + editComment.updateText("This is the new and updated text"); + editComment.saveComment(); + await utils.common.takeScreenshot(screenshotsFolder, "edit comment"); + + //Show versions from last comment edited + historyHelper.showVersionsLastComment(); + await utils.common.takeScreenshot(screenshotsFolder, "show comment versions"); + + historyHelper.closeVersionsLastComment(); + //Deleting last comment let deletedCommentsCounter = await historyHelper.countDeletedComments(); await historyHelper.deleteLastComment(); let newDeletedCommentsCounter = await historyHelper.countDeletedComments(); expect(newDeletedCommentsCounter).to.be.equal(deletedCommentsCounter+1); + await utils.common.takeScreenshot(screenshotsFolder, "deleted comment"); //Restore last comment deletedCommentsCounter = await historyHelper.countDeletedComments(); await historyHelper.restoreLastComment(); newDeletedCommentsCounter = await historyHelper.countDeletedComments(); expect(newDeletedCommentsCounter).to.be.equal(deletedCommentsCounter-1); + await utils.common.takeScreenshot(screenshotsFolder, "restored comment"); //Store comment with a modification commentsCounter = await historyHelper.countComments(); @@ -221,18 +242,19 @@ shared.historyTesting = async function() { historyHelper.writeComment("New comment " + date); let title = detailHelper.title(); title.setTitle('changed'); - title.save(); - + await title.save(); newCommentsCounter = await historyHelper.countComments(); expect(newCommentsCounter).to.be.equal(commentsCounter+1); //Check activity await historyHelper.selectActivityTab(); + await utils.common.takeScreenshot(screenshotsFolder, "show activity tab"); let activitiesCounter = await historyHelper.countActivities(); - expect(activitiesCounter).to.be.least(newCommentsCounter); + expect(newCommentsCounter).to.be.least(activitiesCounter); + } shared.blockTesting = async function() { diff --git a/e2e/suites/issues/issue-detail.e2e.js b/e2e/suites/issues/issue-detail.e2e.js index af466e1c..a1674ccf 100644 --- a/e2e/suites/issues/issue-detail.e2e.js +++ b/e2e/suites/issues/issue-detail.e2e.js @@ -37,7 +37,7 @@ describe('Issue detail', async function(){ describe('watchers edition', sharedDetail.watchersTesting); - it('history', sharedDetail.historyTesting); + it('history', sharedDetail.historyTesting.bind(this, "issues")); it('block', sharedDetail.blockTesting); @@ -57,4 +57,5 @@ describe('Issue detail', async function(){ expect(url).not.to.be.equal(issueUrl); }); }); + }); diff --git a/e2e/suites/tasks/task-detail.e2e.js b/e2e/suites/tasks/task-detail.e2e.js index 74fab211..31dca89c 100644 --- a/e2e/suites/tasks/task-detail.e2e.js +++ b/e2e/suites/tasks/task-detail.e2e.js @@ -53,7 +53,7 @@ describe('Task detail', function(){ expect(newIsIocaine).to.be.equal(isIocaine); }); - it('history', sharedDetail.historyTesting); + it('history', sharedDetail.historyTesting.bind(this, "tasks")); it('block', sharedDetail.blockTesting); diff --git a/e2e/suites/user-stories/user-story-detail.e2e.js b/e2e/suites/user-stories/user-story-detail.e2e.js index e3e21339..9727efa6 100644 --- a/e2e/suites/user-stories/user-story-detail.e2e.js +++ b/e2e/suites/user-stories/user-story-detail.e2e.js @@ -68,7 +68,7 @@ describe('User story detail', function(){ describe('watchers edition', sharedDetail.watchersTesting); - it('history', sharedDetail.historyTesting); + it('history', sharedDetail.historyTesting.bind(this, "user-stories")); it('block', sharedDetail.blockTesting); From 45d618ca132392324f9fd3d766834dfb6d7dbaca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 13 Jun 2016 09:59:03 +0200 Subject: [PATCH 065/315] Upvote and Downvote issues from listing --- app/coffee/modules/issues/list.coffee | 22 +++++++++++++++ .../includes/modules/issues-table.jade | 16 +++++++++-- app/styles/modules/issues/issues-table.scss | 28 +++++++++++++++++-- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/app/coffee/modules/issues/list.coffee b/app/coffee/modules/issues/list.coffee index b555393a..6dee03b5 100644 --- a/app/coffee/modules/issues/list.coffee +++ b/app/coffee/modules/issues/list.coffee @@ -62,6 +62,7 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi @navUrls, @events, @analytics, @translate, @errorHandlingService) -> @scope.sectionName = "Issues" @scope.filters = {} + @.voting = false if _.isEmpty(@location.search()) filters = @rs.issues.getFilters(@params.pslug) @@ -315,6 +316,27 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi addIssuesInBulk: -> @rootscope.$broadcast("issueform:bulk", @scope.projectId) + upVoteIssue: (issueId) -> + @.voting = issueId + onSuccess = => + @.loadIssues() + @.voting = null + onError = => + @confirm.notify("error") + @.voting = null + + return @rs.issues.upvote(issueId).then(onSuccess, onError) + + downVoteIssue: (issueId) -> + @.voting = issueId + onSuccess = => + @.loadIssues() + @.voting = null + onError = => + @confirm.notify("error") + @.voting = null + + return @rs.issues.downvote(issueId).then(onSuccess, onError) module.controller("IssuesController", IssuesController) diff --git a/app/partials/includes/modules/issues-table.jade b/app/partials/includes/modules/issues-table.jade index 3f71abba..9ed33e55 100644 --- a/app/partials/includes/modules/issues-table.jade +++ b/app/partials/includes/modules/issues-table.jade @@ -17,11 +17,23 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}") div.level-field(tg-listitem-severity="issue") div.level-field(tg-listitem-priority="issue") div.votes( - ng-class="{'inactive': !issue.total_voters, 'is-voted': issue.is_voter}" + ng-class="{'inactive': !issue.total_voters}" + ng-if="!issue.is_voter" title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:issue.total_voters||0}:'messageformat' }}" + ng-click="ctrl.upVoteIssue(issue.id)" + tg-loading="ctrl.voting == issue.id" ) tg-svg(svg-icon="icon-upvote") - span {{ ::issue.total_voters }} + span {{ issue.total_voters }} + div.votes( + ng-class="{'is-voted': issue.is_voter}" + ng-if="issue.is_voter" + title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:issue.total_voters||0}:'messageformat' }}" + ng-click="ctrl.downVoteIssue(issue.id)" + tg-loading="ctrl.voting == issue.id" + ) + tg-svg(svg-icon="icon-upvote") + span {{ issue.total_voters }} div.subject a( href="" diff --git a/app/styles/modules/issues/issues-table.scss b/app/styles/modules/issues/issues-table.scss index d641bbd9..be04768d 100644 --- a/app/styles/modules/issues/issues-table.scss +++ b/app/styles/modules/issues/issues-table.scss @@ -59,16 +59,22 @@ } .votes { color: $gray; + cursor: pointer; flex-basis: 75px; flex-shrink: 0; text-align: center; width: 75px; + &:hover { + color: $primary-light; + transition: all .2s linear; + svg { + fill: $primary-light; + transition: all .2s linear; + } + } &.inactive { color: $gray-light; } - &.is-voted { - color: $primary-light; - } svg { @include svg-size(.75rem); fill: $gray; @@ -76,6 +82,22 @@ vertical-align: middle; } } + .is-voted { + color: $primary-light; + transition: all .2s linear; + svg { + fill: $primary-light; + transition: all .2s linear; + } + &:hover { + color: $red-light; + svg { + fill: $red-light; + transform: rotate(180deg); + } + + } + } .subject { overflow: hidden; padding-right: 1rem; From c175e4d1da34d73d9aa7e6efa2d4eef8bf63d2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 13 Jun 2016 10:01:54 +0200 Subject: [PATCH 066/315] Add to changelog --- CHANGELOG.md | 1 + app/partials/includes/modules/issues-table.jade | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c8a86f..ca4d3c29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Display the current user (me) at first in assignment lightbox (thanks to [@mikaoelitiana](https://github.com/mikaoelitiana)) - Add a new permissions to allow add comments instead of use the existent modify permission for this purpose. - Ability to edit comments, view edition history and redesign comments module UI +- Upvote and downvote issues from the issues list. ### Misc - Lots of small and not so small bugfixes. diff --git a/app/partials/includes/modules/issues-table.jade b/app/partials/includes/modules/issues-table.jade index 9ed33e55..06b0a9f2 100644 --- a/app/partials/includes/modules/issues-table.jade +++ b/app/partials/includes/modules/issues-table.jade @@ -16,7 +16,7 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}") div.level-field(tg-listitem-type="issue") div.level-field(tg-listitem-severity="issue") div.level-field(tg-listitem-priority="issue") - div.votes( + div.votes.ng-animate-disabled( ng-class="{'inactive': !issue.total_voters}" ng-if="!issue.is_voter" title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:issue.total_voters||0}:'messageformat' }}" @@ -25,7 +25,7 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}") ) tg-svg(svg-icon="icon-upvote") span {{ issue.total_voters }} - div.votes( + div.votes.ng-animate-disabled( ng-class="{'is-voted': issue.is_voter}" ng-if="issue.is_voter" title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:issue.total_voters||0}:'messageformat' }}" From 87fafdd1b177bf223f23533aed57164d1d3f2270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 9 Jun 2016 10:30:02 +0200 Subject: [PATCH 067/315] Two columns in dashboard --- app/modules/home/home.jade | 4 ++- app/modules/home/home.scss | 27 +++++++++++++++++++-- app/modules/home/working-on/working-on.jade | 3 --- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/app/modules/home/home.jade b/app/modules/home/home.jade index d1af66bd..c712c3d8 100644 --- a/app/modules/home/home.jade +++ b/app/modules/home/home.jade @@ -2,6 +2,8 @@ doctype html div.home-wrapper.centered div.duty-summary - div(tg-working-on) + h1 + span.green {{"HOME.DASHBOARD" | translate}} + tg-working-on.dashboard-container aside.project-list(tg-home-project-list) diff --git a/app/modules/home/home.scss b/app/modules/home/home.scss index 5d1d28f4..3aff4057 100644 --- a/app/modules/home/home.scss +++ b/app/modules/home/home.scss @@ -1,9 +1,32 @@ .home-wrapper { display: flex; + @include breakpoint(tablet) { + flex-direction: column; + } + @include breakpoint(mobile) { + flex-direction: column; + } .duty-summary { flex: 1; margin-right: 2rem; } + .dashboard-container { + display: flex; + flex-direction: row; + @include breakpoint(laptop) { + flex-direction: column; + } + @include breakpoint(tablet) { + flex-direction: column; + } + @include breakpoint(mobile) { + flex-direction: column; + } + } + .working-on-container { + margin-right: .5rem; + padding-right: .5rem; + } .project-list { width: 250px; } @@ -12,11 +35,11 @@ } .title-bar { @include font-type(light); - @include font-size(larger); + @include font-size(large); align-content: center; background: $mass-white; display: flex; margin: 0 0 .5rem; - padding: .9rem 1rem; + padding: .5rem 1rem; } } diff --git a/app/modules/home/working-on/working-on.jade b/app/modules/home/working-on/working-on.jade index e434bfa5..22b032de 100644 --- a/app/modules/home/working-on/working-on.jade +++ b/app/modules/home/working-on/working-on.jade @@ -1,6 +1,3 @@ -h1 - span.green {{"HOME.DASHBOARD" | translate}} - section.working-on-container header h1.title-bar.working-on-title(translate="HOME.WORKING_ON_SECTION") From 5f52e6dbe1acc005405c08bdfafa668cb1d399a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 9 Jun 2016 11:04:08 +0200 Subject: [PATCH 068/315] Add to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca4d3c29..3863c3f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ - Add a new permissions to allow add comments instead of use the existent modify permission for this purpose. - Ability to edit comments, view edition history and redesign comments module UI - Upvote and downvote issues from the issues list. +- Divide dashboard in two columns in large screens + ### Misc - Lots of small and not so small bugfixes. From 47d4c8599d72c41e8afc371829227c95f91361b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Wed, 8 Jun 2016 16:57:59 +0200 Subject: [PATCH 069/315] Enhancement #4339: Add wiki links drag and drop ordering --- app/coffee/modules/wiki/main.coffee | 11 +++++++ app/coffee/modules/wiki/nav.coffee | 43 +++++++++++++++++++++++++-- app/partials/wiki/wiki-nav.jade | 11 ++++--- app/styles/modules/wiki/wiki-nav.scss | 12 +++++++- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/app/coffee/modules/wiki/main.coffee b/app/coffee/modules/wiki/main.coffee index 7d237ea2..e2bf7749 100644 --- a/app/coffee/modules/wiki/main.coffee +++ b/app/coffee/modules/wiki/main.coffee @@ -57,6 +57,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin) constructor: (@scope, @rootscope, @repo, @model, @confirm, @rs, @params, @q, @location, @filter, @log, @appMetaService, @navUrls, @analytics, @translate, @errorHandlingService) -> + @scope.$on("wiki:links:move", @.moveLink) @scope.projectSlug = @params.pslug @scope.wikiSlug = @params.slug @scope.wikiTitle = @scope.wikiSlug @@ -156,6 +157,16 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin) @repo.remove(@scope.wiki).then onSuccess, onError + moveLink: (ctx, item, itemIndex) => + values = @scope.wikiLinks + r = values.indexOf(item) + values.splice(r, 1) + values.splice(itemIndex, 0, item) + _.each values, (value, index) -> + value.order = index + + @repo.saveAll(values) + module.controller("WikiDetailController", WikiDetailController) diff --git a/app/coffee/modules/wiki/nav.coffee b/app/coffee/modules/wiki/nav.coffee index 31ecf4ab..ad61c141 100644 --- a/app/coffee/modules/wiki/nav.coffee +++ b/app/coffee/modules/wiki/nav.coffee @@ -38,7 +38,40 @@ module = angular.module("taigaWiki") WikiNavDirective = ($tgrepo, $log, $location, $confirm, $analytics, $loading, $template, $compile, $translate) -> template = $template.get("wiki/wiki-nav.html", true) - link = ($scope, $el, $attrs) -> + + linkDragAndDrop = ($scope, $el, $attrs) -> + oldParentScope = null + newParentScope = null + itemEl = null + tdom = $el.find(".sortable") + + drake = dragula([tdom[0]], { + direction: 'vertical', + copySortSource: false, + copy: false, + mirrorContainer: tdom[0], + moves: (item) -> return $(item).is('li') + }) + + drake.on 'dragend', (item) -> + itemEl = $(item) + item = itemEl.scope().link + itemIndex = itemEl.index() + $scope.$emit("wiki:links:move", item, itemIndex) + + scroll = autoScroll(window, { + margin: 20, + pixels: 30, + scrollWhenOutside: true, + autoScroll: () -> + return this.down && drake.dragging; + }) + + $scope.$on "$destroy", -> + $el.off() + drake.destroy() + + linkWikiLinks = ($scope, $el, $attrs) -> $ctrl = $el.controller() if not $attrs.ngModel? @@ -130,9 +163,15 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $analytics, $loading, $t $el.find(".new input").val('') $el.find(".add-button").show() - bindOnce($scope, $attrs.ngModel, render) + link = ($scope, $el, $attrs) -> + linkWikiLinks($scope, $el, $attrs) + linkDragAndDrop($scope, $el, $attrs) + + $scope.$on "$destroy", -> + $el.off() + return {link:link} module.directive("tgWikiNav", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", "$tgAnalytics", diff --git a/app/partials/wiki/wiki-nav.jade b/app/partials/wiki/wiki-nav.jade index f1686f03..258afc42 100644 --- a/app/partials/wiki/wiki-nav.jade +++ b/app/partials/wiki/wiki-nav.jade @@ -2,10 +2,10 @@ header h1(translate="WIKI.NAVIGATION.SECTION_NAME") nav - ul - <% _.each(wikiLinks, function(link, index) { %> - li.wiki-link(data-id!="<%- index %>") - a.link-title(title!="<%- link.title %>", href!="<%- link.url %>")<%- link.title %> + ul.sortable + li.wiki-link(ng-repeat="link in wikiLinks", data-id!="{{ $index }}", tg-bind-scope) + tg-svg.dragger(svg-icon="icon-drag") + a.link-title(title!="{{ link.title }}", href!="{{ link.url }}") {{ link.title }} <% if (deleteWikiLinkPermission) { %> a.js-delete-link.remove-wiki-page(title!="{{'WIKI.DELETE_LINK_TITLE' | translate}}") @@ -15,9 +15,8 @@ nav input.hidden( type="text" placeholder="{{'COMMON.FIELDS.NAME' | translate}}" - value!="<%- link.title %>" + value!="{{ link.title }}" ) - <% }) %> li.new.hidden input( diff --git a/app/styles/modules/wiki/wiki-nav.scss b/app/styles/modules/wiki/wiki-nav.scss index ef09edb9..7765495c 100644 --- a/app/styles/modules/wiki/wiki-nav.scss +++ b/app/styles/modules/wiki/wiki-nav.scss @@ -4,7 +4,7 @@ border-bottom: 1px solid $gray-light; display: flex; justify-content: space-between; - padding: 1rem 0 1rem 1rem; + padding: 1rem 0; text-transform: uppercase; &:hover { .remove-wiki-page { @@ -13,6 +13,15 @@ transition: opacity .2s linear; transition-delay: .2s; } + .dragger { + fill: $gray-light; + opacity: 1; + transition: opacity .2s linear; + transition-delay: .2s; + } + } + .dragger { + opacity: 0; } .remove-wiki-page { opacity: 0; @@ -24,6 +33,7 @@ } .link-title { cursor: pointer; + flex-grow: 1; } .icon-trash { fill: $gray-light; From 50d9633851f3667a3d9f5b062d05fec4c0211aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 10 Jun 2016 13:50:35 +0200 Subject: [PATCH 070/315] Enhancement #4339: Improve drag and drop style and visualization --- app/styles/modules/wiki/wiki-nav.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/styles/modules/wiki/wiki-nav.scss b/app/styles/modules/wiki/wiki-nav.scss index 7765495c..c19ee691 100644 --- a/app/styles/modules/wiki/wiki-nav.scss +++ b/app/styles/modules/wiki/wiki-nav.scss @@ -2,6 +2,7 @@ @include font-type(text); align-items: center; border-bottom: 1px solid $gray-light; + cursor: move; display: flex; justify-content: space-between; padding: 1rem 0; @@ -14,6 +15,7 @@ transition-delay: .2s; } .dragger { + cursor: move; fill: $gray-light; opacity: 1; transition: opacity .2s linear; @@ -21,7 +23,11 @@ } } .dragger { + margin-right: .5rem; opacity: 0; + svg { + @include svg-size(.9rem); + } } .remove-wiki-page { opacity: 0; From 0db876c82a02913929c0a19be0d425165bee4715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Tue, 14 Jun 2016 11:41:36 +0200 Subject: [PATCH 071/315] Enhancement #4339: Add changelog entry --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3863c3f6..97295de2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,14 @@ - Show a confirmation notice when you exit edit mode by pressing ESC in the markdown inputs. - Add the tribe button to link stories from tree.taiga.io with gigs in tribe.taiga.io. - Errors (not found, server error, permissions and blocked project) don't change the current url. -- Attachments image slider -- New admin area to edit the tag colors used in your project +- Neew Attachments image slider in preview mode. +- New admin area to edit the tag colors used in your project. - Display the current user (me) at first in assignment lightbox (thanks to [@mikaoelitiana](https://github.com/mikaoelitiana)) - Add a new permissions to allow add comments instead of use the existent modify permission for this purpose. -- Ability to edit comments, view edition history and redesign comments module UI +- Ability to edit comments, view edition history and redesign comments module UI. - Upvote and downvote issues from the issues list. -- Divide dashboard in two columns in large screens - +- Divide dashboard in two columns in large screens, +- Drag & Drop ordering for wiki links. ### Misc - Lots of small and not so small bugfixes. From 1fe663fb52d1ad2702434fbbf935d87355c90f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 15 Jun 2016 11:45:45 +0200 Subject: [PATCH 072/315] Enhancement #4339: Add e2e tests --- e2e/helpers/wiki-helper.js | 15 ++++++++++++++- e2e/suites/wiki.e2e.js | 11 +++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/e2e/helpers/wiki-helper.js b/e2e/helpers/wiki-helper.js index 214bf9b6..cca4a598 100644 --- a/e2e/helpers/wiki-helper.js +++ b/e2e/helpers/wiki-helper.js @@ -17,10 +17,17 @@ helper.links = function() { return newLink; }, - get: function() { + get: function(index) { + if(index !== null && index !== undefined) + return el.$$(".wiki-link a.link-title").get(index) return el.$$(".wiki-link a.link-title"); }, + getNameOf: async function(index) { + let item = await obj.get(index) + return item.getText() + }, + deleteLink: async function(link){ link.click(); await utils.lightbox.confirm.ok(); @@ -32,6 +39,12 @@ helper.links = function() { return obj; }; +helper.dragAndDropLinks = async function(indexFrom, indexTo) { + let selectedLink = helper.links().get(indexFrom); + let newPosition = helper.links().get(indexTo); + return utils.common.drag(selectedLink, newPosition); +}; + helper.editor = function(){ let el = $('.main.wiki'); diff --git a/e2e/suites/wiki.e2e.js b/e2e/suites/wiki.e2e.js index 0429e0be..8c91ef74 100644 --- a/e2e/suites/wiki.e2e.js +++ b/e2e/suites/wiki.e2e.js @@ -20,6 +20,17 @@ describe('wiki', function() { await utils.common.takeScreenshot("wiki", "empty"); }); + it("drag & drop links", async function() { + let nameOld = await wikiHelper.links().getNameOf(0); + + await wikiHelper.dragAndDropLinks(0, 1); + + let nameNew = await wikiHelper.links().getNameOf(4); + + expect(nameNew).to.be.equal(nameOld); + + }); + it('add link', async function(){ let timestamp = new Date().getTime(); currentWiki.slug = "test-link" + timestamp; From fcc27bdf566c51baea2f8b4e0c2bc1177063634e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Tue, 14 Jun 2016 17:09:06 +0200 Subject: [PATCH 073/315] Reconnect on websocket close or error --- app/coffee/modules/events.coffee | 5 ++++- conf/conf.example.json | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/events.coffee b/app/coffee/modules/events.coffee index ef0bb491..d43eb12f 100644 --- a/app/coffee/modules/events.coffee +++ b/app/coffee/modules/events.coffee @@ -96,6 +96,7 @@ class EventsService maxMissedHeartbeats = @config.get("eventsMaxMissedHeartbeats", 5) heartbeatIntervalTime = @config.get("eventsHeartbeatIntervalTime", 60000) + reconnectTryInterval = @config.get("eventsReconnectTryInterval", 10000) @.missedHeartbeats = 0 @.heartbeatInterval = setInterval(() => @@ -108,7 +109,7 @@ class EventsService @log.debug("HeartBeat send PING") catch e @log.error("HeartBeat error: " + e.message) - @.stopHeartBeatMessages() + @.setupConnection() , heartbeatIntervalTime) @log.debug("HeartBeat enabled") @@ -228,11 +229,13 @@ class EventsService onError: (error) -> @log.error("WebSocket error: #{error}") @.error = true + setTimeout(@.setupConnection, @.reconnectTryInterval) onClose: -> @log.debug("WebSocket closed.") @.connected = false @.stopHeartBeatMessages() + setTimeout(@.setupConnection, @.reconnectTryInterval) class EventsProvider diff --git a/conf/conf.example.json b/conf/conf.example.json index 7868fbf1..325001cb 100644 --- a/conf/conf.example.json +++ b/conf/conf.example.json @@ -3,6 +3,7 @@ "eventsUrl": null, "eventsMaxMissedHeartbeats": 5, "eventsHeartbeatIntervalTime": 60000, + "eventsReconnectTryInterval": 10000, "debug": true, "debugInfo": false, "defaultLanguage": "en", From 858fdeb5b8e5729622be2860518572b4aeb4b22a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 16 Jun 2016 14:21:47 +0200 Subject: [PATCH 074/315] Add a note to a strange test --- e2e/helpers/wiki-helper.js | 2 +- e2e/suites/wiki.e2e.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/e2e/helpers/wiki-helper.js b/e2e/helpers/wiki-helper.js index cca4a598..e9737056 100644 --- a/e2e/helpers/wiki-helper.js +++ b/e2e/helpers/wiki-helper.js @@ -41,7 +41,7 @@ helper.links = function() { helper.dragAndDropLinks = async function(indexFrom, indexTo) { let selectedLink = helper.links().get(indexFrom); - let newPosition = helper.links().get(indexTo); + let newPosition = helper.links().get(indexTo).getLocation(); return utils.common.drag(selectedLink, newPosition); }; diff --git a/e2e/suites/wiki.e2e.js b/e2e/suites/wiki.e2e.js index 8c91ef74..a75a418c 100644 --- a/e2e/suites/wiki.e2e.js +++ b/e2e/suites/wiki.e2e.js @@ -25,6 +25,8 @@ describe('wiki', function() { await wikiHelper.dragAndDropLinks(0, 1); + // NOTE: Thre is a strange scroll and we have to take the + // fifth element instead of the second. let nameNew = await wikiHelper.links().getNameOf(4); expect(nameNew).to.be.equal(nameOld); From 7c87a3c671988ec7d20e37216a040210c358b013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Wed, 8 Jun 2016 18:56:01 +0200 Subject: [PATCH 075/315] Add list all wiki pages --- app/coffee/app.coffee | 7 ++ app/coffee/modules/base.coffee | 1 + app/coffee/modules/common/components.coffee | 35 ++++++ app/coffee/modules/resources/wiki.coffee | 3 + app/coffee/modules/wiki/pages-list.coffee | 100 ++++++++++++++++++ app/locales/taiga/locale-en.json | 12 ++- .../common/components/user-display.jade | 13 +++ app/partials/wiki/wiki-list.jade | 44 ++++++++ app/partials/wiki/wiki-nav.jade | 1 + app/styles/layout/wiki.scss | 90 ++++++++++++++++ 10 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 app/coffee/modules/wiki/pages-list.coffee create mode 100644 app/partials/common/components/user-display.jade create mode 100644 app/partials/wiki/wiki-list.jade diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index e5d828bf..c212486b 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -188,6 +188,13 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven # Wiki $routeProvider.when("/project/:pslug/wiki", {redirectTo: (params) -> "/project/#{params.pslug}/wiki/home"}, ) + $routeProvider.when("/project/:pslug/wiki-list", + { + templateUrl: "wiki/wiki-list.html", + loader: true, + section: "wiki" + } + ) $routeProvider.when("/project/:pslug/wiki/:slug", { templateUrl: "wiki/wiki.html", diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index e593f3c6..75b0e23e 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -80,6 +80,7 @@ urls = { "project-issues-detail": "/project/:project/issue/:ref" "project-wiki": "/project/:project/wiki" + "project-wiki-list": "/project/:project/wiki-list" "project-wiki-page": "/project/:project/wiki/:slug" # Team diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index 6f769312..8514be37 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -165,6 +165,41 @@ CreatedByDisplayDirective = ($template, $compile, $translate, $navUrls)-> module.directive("tgCreatedByDisplay", ["$tgTemplate", "$compile", "$translate", "$tgNavUrls", CreatedByDisplayDirective]) + +UserDisplayDirective = ($template, $compile, $translate, $navUrls)-> + # Display the user information (full name and photo). + # + # Example: + # div.creator(tg-user-display, tg-user-id="{{ user.id }}") + # + # Requirements: + # - model object must have the attributes 'created_date' and + # 'owner'(ng-model) + # - scope.usersById object is required. + + link = ($scope, $el, $attrs) -> + id = $attrs.tgUserId + console.log($scope.usersById[id]) + $scope.user = $scope.usersById[id] or { + full_name_display: $translate.instant("COMMON.EXTERNAL_USER") + photo: "/" + window._version + "/images/user-noimage.png" + } + + $scope.url = if $scope.owner?.is_active then $navUrls.resolve("user-profile", {username: $scope.owner.username}) else "" + + $scope.$on "$destroy", -> + $el.off() + + return { + link: link + restrict: "EA" + scope: true, + templateUrl: "common/components/user-display.html" + } + +module.directive("tgUserDisplay", ["$tgTemplate", "$compile", "$translate", "$tgNavUrls", + UserDisplayDirective]) + ############################################################################# ## Watchers directive ############################################################################# diff --git a/app/coffee/modules/resources/wiki.coffee b/app/coffee/modules/resources/wiki.coffee index c333d9cb..8c7beee4 100644 --- a/app/coffee/modules/resources/wiki.coffee +++ b/app/coffee/modules/resources/wiki.coffee @@ -34,6 +34,9 @@ resourceProvider = ($repo, $http, $urls) -> service.getBySlug = (projectId, slug) -> return $repo.queryOne("wiki", "by_slug?project=#{projectId}&slug=#{slug}") + service.list = (projectId) -> + return $repo.queryMany("wiki", {project: projectId}) + service.listLinks = (projectId) -> return $repo.queryMany("wiki-links", {project: projectId}) diff --git a/app/coffee/modules/wiki/pages-list.coffee b/app/coffee/modules/wiki/pages-list.coffee new file mode 100644 index 00000000..5aaddb8c --- /dev/null +++ b/app/coffee/modules/wiki/pages-list.coffee @@ -0,0 +1,100 @@ +### +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino Garcia +# Copyright (C) 2014-2016 David Barragán Merino +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Juan Francisco Alcántara +# Copyright (C) 2014-2016 Xavi Julian +# +# 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/wiki/pages-list.coffee +### + +taiga = @.taiga + +mixOf = @.taiga.mixOf + +module = angular.module("taigaWiki") + +############################################################################# +## Wiki Pages List Controller +############################################################################# + +class WikiPagesListController extends mixOf(taiga.Controller, taiga.PageMixin) + @.$inject = [ + "$scope", + "$rootScope", + "$tgRepo", + "$tgModel", + "$tgConfirm", + "$tgResources", + "$routeParams", + "$q", + "$tgNavUrls", + "tgErrorHandlingService" + ] + + constructor: (@scope, @rootscope, @repo, @model, @confirm, @rs, @params, @q, + @navUrls, @errorHandlingService) -> + @scope.projectSlug = @params.pslug + @scope.wikiSlug = @params.slug + @scope.wikiTitle = @scope.wikiSlug + @scope.sectionName = "Wiki" + @scope.linksVisible = false + + promise = @.loadInitialData() + + # On Error + promise.then null, @.onInitialDataError.bind(@) + + loadProject: -> + return @rs.projects.getBySlug(@params.pslug).then (project) => + if not project.is_wiki_activated + @errorHandlingService.permissionDenied() + + @scope.projectId = project.id + @scope.project = project + @scope.$emit('project:loaded', project) + return project + + loadWikiPages: -> + promise = @rs.wiki.list(@scope.projectId).then (wikipages) => + @scope.wikipages = wikipages + + loadWikiLinks: -> + return @rs.wiki.listLinks(@scope.projectId).then (wikiLinks) => + @scope.wikiLinks = wikiLinks + + for link in @scope.wikiLinks + link.url = @navUrls.resolve("project-wiki-page", { + project: @scope.projectSlug + slug: link.href + }) + + selectedWikiLink = _.find(wikiLinks, {href: @scope.wikiSlug}) + @scope.wikiTitle = selectedWikiLink.title if selectedWikiLink? + + loadInitialData: -> + promise = @.loadProject() + return promise.then (project) => + @.fillUsersAndRoles(project.members, project.roles) + @q.all([@.loadWikiLinks(), @.loadWikiPages()]).then @.checkLinksPerms.bind(this) + + checkLinksPerms: -> + if @scope.project.my_permissions.indexOf("add_wiki_link") != -1 || + (@scope.project.my_permissions.indexOf("view_wiki_links") != -1 && @scope.wikiLinks.length) + @scope.linksVisible = true + +module.controller("WikiPagesListController", WikiPagesListController) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 0263a4f1..687c484a 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1460,12 +1460,22 @@ "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { "SECTION_NAME": "Links", - "ACTION_ADD_LINK": "Add link" + "ACTION_ADD_LINK": "Add link", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "times
edited", "LAST_EDIT": "last
edit", "LAST_MODIFICATION": "last modification" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/partials/common/components/user-display.jade b/app/partials/common/components/user-display.jade new file mode 100644 index 00000000..e3263d3b --- /dev/null +++ b/app/partials/common/components/user-display.jade @@ -0,0 +1,13 @@ +.user-avatar + a( + href="{{url}}" + title="{{user.full_name_display}}" + ) + img( + src="{{user.photo}}" + alt="{{user.full_name_display}}" + ) +a.user-full-name( + href="{{url}}" + title="{{user.full_name_display}}" +) {{user.full_name_display}} diff --git a/app/partials/wiki/wiki-list.jade b/app/partials/wiki/wiki-list.jade new file mode 100644 index 00000000..cc5e71c0 --- /dev/null +++ b/app/partials/wiki/wiki-list.jade @@ -0,0 +1,44 @@ +doctype html + +div.wrapper(ng-controller="WikiPagesListController as ctrl", + ng-init="section='wiki'") + tg-project-menu + sidebar.menu-secondary.extrabar(ng-if="linksVisible") + section.wiki-nav(tg-wiki-nav, ng-model="wikiLinks") + section.main.wiki + header + h1 + span(tg-bo-bind="project.name") + span.green(translate="PROJECT.SECTION.WIKI") + span.date(translate="WIKI.SECTION_PAGES_LIST") + + section.wiki-pages-table.basic-table + .row.title + .title-field( + translate="WIKI.PAGES_LIST_COLUMNS.TITLE" + ) + .editions-field( + translate="WIKI.PAGES_LIST_COLUMNS.EDITIONS" + ) + .creator-field( + translate="WIKI.PAGES_LIST_COLUMNS.CREATOR" + ) + .created-field( + translate="WIKI.PAGES_LIST_COLUMNS.CREATED" + ) + .last-modifier-field( + translate="WIKI.PAGES_LIST_COLUMNS.LAST_MODIFIER" + ) + .modified-field( + translate="WIKI.PAGES_LIST_COLUMNS.MODIFIED" + ) + + div.row.table-main(ng-repeat="wikipage in wikipages track by wikipage.slug") + div.title-field + a(href="", tg-nav="project-wiki-page:project=project.slug,slug=wikipage.slug") + | {{wikipage.slug}} + div.editions-field {{wikipage.editions}} + div.creator-field(tg-user-display, tg-user-id="{{wikipage.owner}}") + div.created-field(tg-bo-bind="wikipage.created_date|momentFormat:'DD MMM YYYY HH:mm'") + div.last-modifier-field(tg-user-display, tg-user-id="{{wikipage.last_modifier}}") + div.modified-field(tg-bo-bind="wikipage.modified_date|momentFormat:'DD MMM YYYY HH:mm'") diff --git a/app/partials/wiki/wiki-nav.jade b/app/partials/wiki/wiki-nav.jade index 258afc42..7137e16f 100644 --- a/app/partials/wiki/wiki-nav.jade +++ b/app/partials/wiki/wiki-nav.jade @@ -1,5 +1,6 @@ header h1(translate="WIKI.NAVIGATION.SECTION_NAME") + a(href="", tg-nav="project-wiki-list:project=project.slug", translate="WIKI.NAVIGATION.ALL_PAGES") nav ul.sortable diff --git a/app/styles/layout/wiki.scss b/app/styles/layout/wiki.scss index 6c661125..36b3b20f 100644 --- a/app/styles/layout/wiki.scss +++ b/app/styles/layout/wiki.scss @@ -91,3 +91,93 @@ top: .4rem; } } + +.wiki-pages-table { + display: flex; + margin-bottom: 2rem; + &.empty { + display: none; + } + .row { + &:hover { + background: lighten($primary, 65%); + transition: background .2s ease-in; + } + .icon { + display: inline; + } + } + .title { + @include font-size(medium); + @include font-type(bold); + border-bottom: 1px solid $gray-light; + &:hover { + background: transparent; + } + div { + cursor: pointer; + } + .votes { + color: $gray; + } + } + .table-main { + @include font-size(small); + border-bottom: 1px solid darken($whitish, 4%); + } + .avatar { + align-items: center; + display: flex; + img { + width: 35px; + } + figcaption { + flex-basis: 60%; + flex-grow: 1; + margin-left: .5rem; + } + } + .title-field { + overflow: hidden; + padding-right: 1rem; + width: 100%; + a { + @include ellipsis(100%); + display: block; + } + span { + vertical-align: middle; + &:first-child { + margin-right: .5rem; + } + } + } + .editions-field, + .created-field, + .modified-field, + .creator-field , + .last-modifier-field { + flex-basis: 140px; + flex-grow: 1; + flex-shrink: 0; + padding: 0 1rem; + position: relative; + text-align: left; + } + .creator-field , + .last-modifier-field { + display: flex; + flex-basis: 180px; + flex-direction: row; + .user-avatar { + flex-grow: 0; + img { + height: 48px; + } + } + .user-full-name { + flex-grow: 1; + padding: .5rem; + } + } +} From b0f990492d22c31043d98c1b9445b570c656b14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 10 Jun 2016 13:03:40 +0200 Subject: [PATCH 076/315] Wiki Pages style --- app/locales/taiga/locale-en.json | 1 + app/partials/wiki/wiki-list.jade | 37 ++++--- app/partials/wiki/wiki-nav.jade | 56 ++++++---- app/partials/wiki/wiki-summary.jade | 22 ++-- app/partials/wiki/wiki.jade | 12 ++- app/styles/components/basic-table.scss | 3 + app/styles/layout/wiki.scss | 102 ++---------------- app/styles/modules/wiki/wiki-nav.scss | 3 - app/styles/modules/wiki/wiki-pages-table.scss | 48 +++++++++ app/styles/modules/wiki/wiki-summary.scss | 24 +++-- 10 files changed, 156 insertions(+), 152 deletions(-) create mode 100644 app/styles/modules/wiki/wiki-pages-table.scss diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 687c484a..649b5508 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1459,6 +1459,7 @@ "DELETE_LIGHTBOX_TITLE": "Delete Wiki Page", "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Links", "ACTION_ADD_LINK": "Add link", "ALL_PAGES": "All pages" diff --git a/app/partials/wiki/wiki-list.jade b/app/partials/wiki/wiki-list.jade index cc5e71c0..284b57e9 100644 --- a/app/partials/wiki/wiki-list.jade +++ b/app/partials/wiki/wiki-list.jade @@ -1,10 +1,15 @@ doctype html -div.wrapper(ng-controller="WikiPagesListController as ctrl", - ng-init="section='wiki'") +div.wrapper( + ng-controller="WikiPagesListController as ctrl" + ng-init="section='wiki'" +) tg-project-menu sidebar.menu-secondary.extrabar(ng-if="linksVisible") - section.wiki-nav(tg-wiki-nav, ng-model="wikiLinks") + section.wiki-nav( + tg-wiki-nav + ng-model="wikiLinks" + ) section.main.wiki header h1 @@ -33,12 +38,20 @@ div.wrapper(ng-controller="WikiPagesListController as ctrl", translate="WIKI.PAGES_LIST_COLUMNS.MODIFIED" ) - div.row.table-main(ng-repeat="wikipage in wikipages track by wikipage.slug") - div.title-field - a(href="", tg-nav="project-wiki-page:project=project.slug,slug=wikipage.slug") - | {{wikipage.slug}} - div.editions-field {{wikipage.editions}} - div.creator-field(tg-user-display, tg-user-id="{{wikipage.owner}}") - div.created-field(tg-bo-bind="wikipage.created_date|momentFormat:'DD MMM YYYY HH:mm'") - div.last-modifier-field(tg-user-display, tg-user-id="{{wikipage.last_modifier}}") - div.modified-field(tg-bo-bind="wikipage.modified_date|momentFormat:'DD MMM YYYY HH:mm'") + .row.table-main(ng-repeat="wikipage in wikipages track by wikipage.slug") + .title-field + a( + href="" + tg-nav="project-wiki-page:project=project.slug,slug=wikipage.slug" + ) {{wikipage.slug}} + .editions-field {{wikipage.editions}} + .creator-field( + tg-user-display + tg-user-id="{{wikipage.owner}}" + ) + .created-field(tg-bo-bind="wikipage.created_date|momentFormat:'DD MMM YYYY HH:mm'") + .last-modifier-field( + tg-user-display + tg-user-id="{{wikipage.last_modifier}}" + ) + .modified-field(tg-bo-bind="wikipage.modified_date|momentFormat:'DD MMM YYYY HH:mm'") diff --git a/app/partials/wiki/wiki-nav.jade b/app/partials/wiki/wiki-nav.jade index 7137e16f..afa7fb8f 100644 --- a/app/partials/wiki/wiki-nav.jade +++ b/app/partials/wiki/wiki-nav.jade @@ -1,29 +1,45 @@ header - h1(translate="WIKI.NAVIGATION.SECTION_NAME") - a(href="", tg-nav="project-wiki-list:project=project.slug", translate="WIKI.NAVIGATION.ALL_PAGES") + h1(translate="WIKI.NAVIGATION.SECTION_NAME") nav - ul.sortable - li.wiki-link(ng-repeat="link in wikiLinks", data-id!="{{ $index }}", tg-bind-scope) - tg-svg.dragger(svg-icon="icon-drag") - a.link-title(title!="{{ link.title }}", href!="{{ link.url }}") {{ link.title }} + ul + li.wiki-link + a.link-title( + href="" + tg-nav="project-wiki-list:project=project.slug" + translate="WIKI.NAVIGATION.ALL_PAGES" + ) - <% if (deleteWikiLinkPermission) { %> - a.js-delete-link.remove-wiki-page(title!="{{'WIKI.DELETE_LINK_TITLE' | translate}}") - tg-svg(svg-icon="icon-trash") - <% } %> + li.wiki-link + a.link-title( + href="" + tg-nav="project-wiki:project=project.slug" + translate="WIKI.NAVIGATION.HOME" + ) - input.hidden( - type="text" - placeholder="{{'COMMON.FIELDS.NAME' | translate}}" - value!="{{ link.title }}" - ) + ul.sortable + li.wiki-link(ng-repeat="link in wikiLinks", data-id!="{{ $index }}", tg-bind-scope) + <% if (addWikiLinkPermission) { %> + tg-svg.dragger(svg-icon="icon-drag") + <% } %> + a.link-title(title!="{{ link.title }}", href!="{{ link.url }}") {{ link.title }} - li.new.hidden - input( - type="text" - placeholder="{{'COMMON.FIELDS.NAME' | translate}}" - ) + <% if (deleteWikiLinkPermission) { %> + a.js-delete-link.remove-wiki-page(title!="{{'WIKI.DELETE_LINK_TITLE' | translate}}") + tg-svg(svg-icon="icon-trash") + <% } %> + + input.hidden( + type="text" + placeholder="{{'COMMON.FIELDS.NAME' | translate}}" + value!="{{ link.title }}" + ) + + li.new.hidden + input( + type="text" + placeholder="{{'COMMON.FIELDS.NAME' | translate}}" + ) <% if (addWikiLinkPermission) { %> a( diff --git a/app/partials/wiki/wiki-summary.jade b/app/partials/wiki/wiki-summary.jade index f2855cba..d10e398f 100644 --- a/app/partials/wiki/wiki-summary.jade +++ b/app/partials/wiki/wiki-summary.jade @@ -1,14 +1,14 @@ -div.wiki-times-edited - span.number <%- totalEditions %> - span.description(translate="WIKI.SUMMARY.TIMES_EDITED") - -div.wiki-last-modified - span.number <%- lastModifiedDate %> - span.description(translate="WIKI.SUMMARY.LAST_EDIT") - -div.wiki-username-edition - figure.avatar +.wiki-username-edition + .avatar img(src!="<%- user.imgUrl %>" alt!="<%- user.name %>") - div.wiki-user-modification + .wiki-user-modification span.description(translate="WIKI.SUMMARY.LAST_MODIFICATION") span.username <%- user.name %> + +.wiki-last-modified + span.number <%- lastModifiedDate %> + span.description(translate="WIKI.SUMMARY.LAST_EDIT") + +.wiki-times-edited + span.number <%- totalEditions %> + span.description(translate="WIKI.SUMMARY.TIMES_EDITED") diff --git a/app/partials/wiki/wiki.jade b/app/partials/wiki/wiki.jade index a7e59bd5..4435b495 100644 --- a/app/partials/wiki/wiki.jade +++ b/app/partials/wiki/wiki.jade @@ -4,7 +4,10 @@ div.wrapper(ng-controller="WikiDetailController as ctrl", ng-init="section='wiki'") tg-project-menu sidebar.menu-secondary.extrabar(ng-if="linksVisible") - section.wiki-nav(tg-wiki-nav, ng-model="wikiLinks") + section.wiki-nav( + tg-wiki-nav + ng-model="wikiLinks" + ) section.main.wiki header h1 @@ -12,13 +15,18 @@ div.wrapper(ng-controller="WikiDetailController as ctrl", span.green(translate="PROJECT.SECTION.WIKI") - div.summary.wiki-summary(tg-wiki-summary, ng-model="wiki", ng-if="wiki.id") h2.wiki-title(ng-bind='wikiTitle') section.wiki-content( tg-editable-wysiwyg, tg-editable-wiki-content, ng-model="wiki" ) + + div.summary.wiki-summary( + tg-wiki-summary + ng-model="wiki" + ng-if="wiki.id" + ) tg-attachments-full( ng-if="wiki.id" diff --git a/app/styles/components/basic-table.scss b/app/styles/components/basic-table.scss index 3e710ed1..09df2464 100644 --- a/app/styles/components/basic-table.scss +++ b/app/styles/components/basic-table.scss @@ -11,6 +11,9 @@ padding: .3rem 0; text-align: left; width: 100%; + @include breakpoint(tablet) { + flex-direction: column; + } @for $i from 1 through 8 { .width-#{$i} { flex-basis: 50px; diff --git a/app/styles/layout/wiki.scss b/app/styles/layout/wiki.scss index 36b3b20f..5b5c8874 100644 --- a/app/styles/layout/wiki.scss +++ b/app/styles/layout/wiki.scss @@ -1,11 +1,17 @@ .wiki { + .wysiwyg { + margin-bottom: 0; + } .wiki-title { @include font-type(light); - @include font-size(larger); + @include font-size(xxlarge); + margin-bottom: 0; + padding: 1rem; } .remove { @include font-size(small); color: $gray-light; + cursor: pointer; &:hover { color: $red-light; transition: color .1s linear; @@ -22,8 +28,8 @@ } .wiki-content { - @include cursor-progress; - margin-bottom: 2rem; + @include font-size(large); + max-width: 1024px; position: relative; &.editable { &:hover { @@ -91,93 +97,3 @@ top: .4rem; } } - -.wiki-pages-table { - display: flex; - margin-bottom: 2rem; - &.empty { - display: none; - } - .row { - &:hover { - background: lighten($primary, 65%); - transition: background .2s ease-in; - } - .icon { - display: inline; - } - } - .title { - @include font-size(medium); - @include font-type(bold); - border-bottom: 1px solid $gray-light; - &:hover { - background: transparent; - } - div { - cursor: pointer; - } - .votes { - color: $gray; - } - } - .table-main { - @include font-size(small); - border-bottom: 1px solid darken($whitish, 4%); - } - .avatar { - align-items: center; - display: flex; - img { - width: 35px; - } - figcaption { - flex-basis: 60%; - flex-grow: 1; - margin-left: .5rem; - } - } - .title-field { - overflow: hidden; - padding-right: 1rem; - width: 100%; - a { - @include ellipsis(100%); - display: block; - } - span { - vertical-align: middle; - &:first-child { - margin-right: .5rem; - } - } - } - .editions-field, - .created-field, - .modified-field, - .creator-field , - .last-modifier-field { - flex-basis: 140px; - flex-grow: 1; - flex-shrink: 0; - padding: 0 1rem; - position: relative; - text-align: left; - } - .creator-field , - .last-modifier-field { - display: flex; - flex-basis: 180px; - flex-direction: row; - .user-avatar { - flex-grow: 0; - img { - height: 48px; - } - } - .user-full-name { - flex-grow: 1; - padding: .5rem; - } - } -} diff --git a/app/styles/modules/wiki/wiki-nav.scss b/app/styles/modules/wiki/wiki-nav.scss index c19ee691..93245a85 100644 --- a/app/styles/modules/wiki/wiki-nav.scss +++ b/app/styles/modules/wiki/wiki-nav.scss @@ -47,9 +47,6 @@ } .wiki-nav { - ul { - border-top: 1px solid $gray-light; - } .add-button { color: $white; display: block; diff --git a/app/styles/modules/wiki/wiki-pages-table.scss b/app/styles/modules/wiki/wiki-pages-table.scss new file mode 100644 index 00000000..15991f3c --- /dev/null +++ b/app/styles/modules/wiki/wiki-pages-table.scss @@ -0,0 +1,48 @@ +.wiki-pages-table { + display: flex; + .row { + padding: .5rem; + } + .title { + @include font-size(medium); + @include font-type(bold); + } + .table-main { + @include font-size(small); + } + .title-field { + flex-basis: 180px; + flex-grow: 1; + flex-shrink: 0; + } + .created-field, + .created-field, + .modified-field { + flex-basis: 10vw; + flex-grow: 0; + flex-shrink: 0; + margin-right: .5rem; + } + .editions-field { + flex-basis: 80px; + flex-grow: 0; + flex-shrink: 0; + margin-right: .5rem; + } + .creator-field, + .last-modifier-field { + align-items: center; + display: flex; + flex-basis: 200px; + .user-avatar { + flex-grow: 0; + img { + height: 2rem; + } + } + .user-full-name { + flex-grow: 1; + padding: .5rem; + } + } +} diff --git a/app/styles/modules/wiki/wiki-summary.scss b/app/styles/modules/wiki/wiki-summary.scss index 21189166..1c400d20 100644 --- a/app/styles/modules/wiki/wiki-summary.scss +++ b/app/styles/modules/wiki/wiki-summary.scss @@ -1,28 +1,30 @@ .wiki-summary { align-items: center; - flex-wrap: wrap; justify-content: flex-start; + margin-top: 1rem; + &.summary { + background: $whitish; + color: $gray; + } div { display: flex; - justify-content: space-between; - margin-right: 1rem; - } - .number { - line-height: 2rem; - top: 0; + margin-right: 1.25rem; } .wiki-user-modification { display: flex; flex-direction: column; justify-content: flex-start; } - figure { - margin-right: .3rem; - width: 32px; + .avatar { + margin-right: .5rem; + width: 2.25rem; + } + img { + height: 100%; + width: 100%; } .username { @include font-size(large); - color: $primary-light; white-space: nowrap; } } From 8aef8cac8bd74f670159868779e972153e6377fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 10 Jun 2016 13:05:43 +0200 Subject: [PATCH 077/315] Center number of edition --- app/styles/modules/wiki/wiki-pages-table.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/styles/modules/wiki/wiki-pages-table.scss b/app/styles/modules/wiki/wiki-pages-table.scss index 15991f3c..130cc9a0 100644 --- a/app/styles/modules/wiki/wiki-pages-table.scss +++ b/app/styles/modules/wiki/wiki-pages-table.scss @@ -28,6 +28,7 @@ flex-grow: 0; flex-shrink: 0; margin-right: .5rem; + text-align: center; } .creator-field, .last-modifier-field { From b88d3ba30e4c52baf48b9f01d047b32ddb0614d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 10 Jun 2016 13:26:46 +0200 Subject: [PATCH 078/315] Update color to master mass white --- app/styles/modules/wiki/wiki-summary.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/styles/modules/wiki/wiki-summary.scss b/app/styles/modules/wiki/wiki-summary.scss index 1c400d20..30e7d128 100644 --- a/app/styles/modules/wiki/wiki-summary.scss +++ b/app/styles/modules/wiki/wiki-summary.scss @@ -3,7 +3,7 @@ justify-content: flex-start; margin-top: 1rem; &.summary { - background: $whitish; + background: $mass-white; color: $gray; } div { From a09a7fe5cdfafb1b80ed2d2c6b2d4592688aa4be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Tue, 14 Jun 2016 12:01:03 +0200 Subject: [PATCH 079/315] Correctly link user profile --- app/coffee/modules/common/components.coffee | 4 +--- app/partials/common/components/user-display.jade | 10 +++++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index 8514be37..eb4e2d6e 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -173,8 +173,6 @@ UserDisplayDirective = ($template, $compile, $translate, $navUrls)-> # div.creator(tg-user-display, tg-user-id="{{ user.id }}") # # Requirements: - # - model object must have the attributes 'created_date' and - # 'owner'(ng-model) # - scope.usersById object is required. link = ($scope, $el, $attrs) -> @@ -185,7 +183,7 @@ UserDisplayDirective = ($template, $compile, $translate, $navUrls)-> photo: "/" + window._version + "/images/user-noimage.png" } - $scope.url = if $scope.owner?.is_active then $navUrls.resolve("user-profile", {username: $scope.owner.username}) else "" + $scope.url = if $scope.user.is_active then $navUrls.resolve("user-profile", {username: $scope.user.username}) else "" $scope.$on "$destroy", -> $el.off() diff --git a/app/partials/common/components/user-display.jade b/app/partials/common/components/user-display.jade index e3263d3b..3e6bb71a 100644 --- a/app/partials/common/components/user-display.jade +++ b/app/partials/common/components/user-display.jade @@ -1,4 +1,4 @@ -.user-avatar +.user-avatar(ng-if="url") a( href="{{url}}" title="{{user.full_name_display}}" @@ -8,6 +8,14 @@ alt="{{user.full_name_display}}" ) a.user-full-name( + ng-if="url" href="{{url}}" title="{{user.full_name_display}}" ) {{user.full_name_display}} + +.user-avatar(ng-if="!url") + img( + src="{{user.photo}}" + alt="{{user.full_name_display}}" + ) +span.user-full-name(ng-if="!url") {{user.full_name_display}} From d01de86ec1565b4fdd6453bf4c723b5439241b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Tue, 14 Jun 2016 12:02:22 +0200 Subject: [PATCH 080/315] Add changelog entry --- CHANGELOG.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97295de2..acc48670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,17 +4,20 @@ ## 2.2.0 ???? (Unreleased) ### Features -- Show a confirmation notice when you exit edit mode by pressing ESC in the markdown inputs. - Add the tribe button to link stories from tree.taiga.io with gigs in tribe.taiga.io. +- Show a confirmation notice when you exit edit mode by pressing ESC in the markdown inputs. - Errors (not found, server error, permissions and blocked project) don't change the current url. - Neew Attachments image slider in preview mode. - New admin area to edit the tag colors used in your project. - Display the current user (me) at first in assignment lightbox (thanks to [@mikaoelitiana](https://github.com/mikaoelitiana)) -- Add a new permissions to allow add comments instead of use the existent modify permission for this purpose. -- Ability to edit comments, view edition history and redesign comments module UI. +- Divide the user dashboard in two columns in large screens, - Upvote and downvote issues from the issues list. -- Divide dashboard in two columns in large screens, -- Drag & Drop ordering for wiki links. +- Comments: + - Add a new permissions to allow add comments instead of use the existent modify permission for this purpose. + - Ability to edit comments, view edition history and redesign comments module UI. +- Wiki: + - Drag & Drop ordering for wiki links. + - Add a list of all wiki pages ### Misc - Lots of small and not so small bugfixes. From fe816216b84a9b977bc4df33750e19571bb9b6ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 22 Jun 2016 09:55:21 +0200 Subject: [PATCH 081/315] Fix empty dashboard. Fixes TG-4376 --- app/modules/home/home.scss | 11 +++++++++-- app/modules/home/working-on/empty.scss | 24 +++++++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/app/modules/home/home.scss b/app/modules/home/home.scss index 3aff4057..1aed3983 100644 --- a/app/modules/home/home.scss +++ b/app/modules/home/home.scss @@ -23,12 +23,19 @@ flex-direction: column; } } + .watching-container, .working-on-container { - margin-right: .5rem; + flex: 1; + padding-left: .5rem; padding-right: .5rem; } + .working-on-container { + margin-right: 1rem; + } .project-list { - width: 250px; + flex-basis: 250px; + flex-grow: 0; + flex-shrink: 0; } .see-more-projects-btn { display: block; diff --git a/app/modules/home/working-on/empty.scss b/app/modules/home/working-on/empty.scss index 848ffebd..28630947 100644 --- a/app/modules/home/working-on/empty.scss +++ b/app/modules/home/working-on/empty.scss @@ -3,7 +3,7 @@ margin-bottom: 4rem; p { @include font-type(light); - margin: 2rem 9rem 1rem; + margin: 2rem 2rem 1rem; text-align: center; } } @@ -34,10 +34,28 @@ background: $mass-white; height: 1rem; margin-bottom: 1rem; - width: 40vw; + width: 8vw; + @include breakpoint(laptop) { + width: 30vw; + } + @include breakpoint(tablet) { + width: 30vw; + } + @include breakpoint(mobile) { + width: 30vw; + } &:last-child { margin: 0; - width: 20vw; + width: 18vw; + @include breakpoint(laptop) { + width: 50vw; + } + @include breakpoint(tablet) { + width: 50vw; + } + @include breakpoint(mobile) { + width: 50vw; + } } } } From 41d2c905c7168c9c717b93ba8c326ba704161925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Wed, 22 Jun 2016 09:37:41 +0200 Subject: [PATCH 082/315] Bug#4345: Disable drag and drop on wikilink without permissions --- app/coffee/modules/wiki/nav.coffee | 44 +++++++++++++++------------ app/partials/wiki/wiki-nav.jade | 7 ++++- app/styles/modules/wiki/wiki-nav.scss | 4 ++- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/app/coffee/modules/wiki/nav.coffee b/app/coffee/modules/wiki/nav.coffee index ad61c141..53fc3d3d 100644 --- a/app/coffee/modules/wiki/nav.coffee +++ b/app/coffee/modules/wiki/nav.coffee @@ -45,31 +45,35 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $analytics, $loading, $t itemEl = null tdom = $el.find(".sortable") - drake = dragula([tdom[0]], { - direction: 'vertical', - copySortSource: false, - copy: false, - mirrorContainer: tdom[0], - moves: (item) -> return $(item).is('li') - }) + addWikiLinkPermission = $scope.project.my_permissions.indexOf("add_wiki_link") > -1 - drake.on 'dragend', (item) -> - itemEl = $(item) - item = itemEl.scope().link - itemIndex = itemEl.index() - $scope.$emit("wiki:links:move", item, itemIndex) + if addWikiLinkPermission + drake = dragula([tdom[0]], { + direction: 'vertical', + copySortSource: false, + copy: false, + mirrorContainer: tdom[0], + moves: (item) -> return $(item).is('li') + }) - scroll = autoScroll(window, { - margin: 20, - pixels: 30, - scrollWhenOutside: true, - autoScroll: () -> - return this.down && drake.dragging; - }) + drake.on 'dragend', (item) -> + itemEl = $(item) + item = itemEl.scope().link + itemIndex = itemEl.index() + $scope.$emit("wiki:links:move", item, itemIndex) + + scroll = autoScroll(window, { + margin: 20, + pixels: 30, + scrollWhenOutside: true, + autoScroll: () -> + return this.down && drake.dragging; + }) $scope.$on "$destroy", -> $el.off() - drake.destroy() + if addWikiLinkPermission + drake.destroy() linkWikiLinks = ($scope, $el, $attrs) -> $ctrl = $el.controller() diff --git a/app/partials/wiki/wiki-nav.jade b/app/partials/wiki/wiki-nav.jade index afa7fb8f..20b9c680 100644 --- a/app/partials/wiki/wiki-nav.jade +++ b/app/partials/wiki/wiki-nav.jade @@ -18,7 +18,12 @@ nav ) ul.sortable - li.wiki-link(ng-repeat="link in wikiLinks", data-id!="{{ $index }}", tg-bind-scope) + li.wiki-link( + ng-repeat="link in wikiLinks" + data-id!="{{ $index }}" + tg-bind-scope + tg-class-permission="{'is-sortable': 'add_wiki_link'}" + ) <% if (addWikiLinkPermission) { %> tg-svg.dragger(svg-icon="icon-drag") <% } %> diff --git a/app/styles/modules/wiki/wiki-nav.scss b/app/styles/modules/wiki/wiki-nav.scss index 93245a85..d7778923 100644 --- a/app/styles/modules/wiki/wiki-nav.scss +++ b/app/styles/modules/wiki/wiki-nav.scss @@ -2,7 +2,6 @@ @include font-type(text); align-items: center; border-bottom: 1px solid $gray-light; - cursor: move; display: flex; justify-content: space-between; padding: 1rem 0; @@ -22,6 +21,9 @@ transition-delay: .2s; } } + &.is-sortable { + cursor: move; + } .dragger { margin-right: .5rem; opacity: 0; From 155993ecc454a1c779b385f8b78bc53e264660a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 22 Jun 2016 09:20:57 +0200 Subject: [PATCH 083/315] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acc48670..c460a6f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog # -## 2.2.0 ???? (Unreleased) +## 3.0.0 ???? (Unreleased) ### Features - Add the tribe button to link stories from tree.taiga.io with gigs in tribe.taiga.io. From 68f9bad0db1e248baabada6ff1113b60a03012eb Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 23 Jun 2016 08:52:05 +0200 Subject: [PATCH 084/315] fix issue 4359 - save description & title when changing other field --- app/coffee/modules/common.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/coffee/modules/common.coffee b/app/coffee/modules/common.coffee index 3f36dd4d..5ab2fa50 100644 --- a/app/coffee/modules/common.coffee +++ b/app/coffee/modules/common.coffee @@ -300,6 +300,9 @@ class QueueModelTransformation extends taiga.Service clone = @.clone() + modified = _.omit(obj._modifiedAttrs, ['version']) + clone = _.assign(clone, modified) + transformation(clone) if comment.length From ac2c977c1b730267c7c2da2f022d931fcb1586db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 27 Jun 2016 20:10:50 +0200 Subject: [PATCH 085/315] Fix a bug with the warning message in the create-member lightbox --- app/coffee/modules/admin/lightboxes.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/coffee/modules/admin/lightboxes.coffee b/app/coffee/modules/admin/lightboxes.coffee index 2aa762d5..4ff79aec 100644 --- a/app/coffee/modules/admin/lightboxes.coffee +++ b/app/coffee/modules/admin/lightboxes.coffee @@ -132,7 +132,7 @@ module.directive("tgLbAddMembers", ["lightboxService", LightboxAddMembersDirecti LightboxAddMembersWarningMessageDirective = () -> return { - templateUrl: "admin/lightbox-add-members-no-more=memberships-warning-message.html" + templateUrl: "admin/memberships-warning-message.html" scope: { project: "=" } From 7362d6151ba6da697409555bd6edf4cd4cb0d37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 28 Jun 2016 21:23:47 +0200 Subject: [PATCH 086/315] [i18n] Update locales --- app/locales/taiga/locale-ca.json | 60 ++++++++++++++----- app/locales/taiga/locale-de.json | 78 ++++++++++++++++++------- app/locales/taiga/locale-es.json | 62 +++++++++++++++----- app/locales/taiga/locale-fi.json | 56 ++++++++++++++---- app/locales/taiga/locale-fr.json | 62 +++++++++++++++----- app/locales/taiga/locale-it.json | 60 ++++++++++++++----- app/locales/taiga/locale-nl.json | 52 ++++++++++++++--- app/locales/taiga/locale-pl.json | 60 ++++++++++++++----- app/locales/taiga/locale-pt-br.json | 84 +++++++++++++++++++-------- app/locales/taiga/locale-ru.json | 62 +++++++++++++++----- app/locales/taiga/locale-sv.json | 60 ++++++++++++++----- app/locales/taiga/locale-tr.json | 60 ++++++++++++++----- app/locales/taiga/locale-zh-hant.json | 60 ++++++++++++++----- 13 files changed, 629 insertions(+), 187 deletions(-) diff --git a/app/locales/taiga/locale-ca.json b/app/locales/taiga/locale-ca.json index de55911c..1e076009 100644 --- a/app/locales/taiga/locale-ca.json +++ b/app/locales/taiga/locale-ca.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "Vore istòries d'usuari", "ADD_USER_STORIES": "Afegir històries d'usuari", "MODIFY_USER_STORIES": "Editar història d'usuari", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Esborrar històries d'usuari" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Vore tasca", "ADD_TASKS": "Afegit tasques", "MODIFY_TASKS": "Modificar tasques", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Esborrar tasques" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Vore incidències", "ADD_ISSUES": "Afegeix incidències", "MODIFY_ISSUES": "Modifica incidències", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Elimina incidències" }, "WIKI": { @@ -558,7 +561,7 @@ "ACTION_ADD": "Afegir now {{objName}}" }, "PROJECT_VALUES_TAGS": { - "TITLE": "Tags", + "TITLE": "Etiquetes", "SUBTITLE": "View and edit the color of your user stories", "EMPTY": "Currently there are no tags", "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" @@ -689,7 +692,7 @@ "SEVERITIES": "severitats", "TYPES": "Tipus", "CUSTOM_FIELDS": "Camps personalitzats", - "TAGS": "Tags" + "TAGS": "Etiquetes" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Perfil de projecte" @@ -981,7 +984,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Història d'Usuari {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estat: {{userStoryStatus }}. Completat {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tasques tancades). Punts: {{userStoryPoints}}. Descripció: {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "Història d'usuari", "LINK_TASKBOARD": "Panell de tasques", "TITLE_LINK_TASKBOARD": "Anar a panell de tasques", "TOTAL_POINTS": "punts totals", @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comentari esborrat per {{user}} el {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Comentaris", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Comentar", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Escriu un nou comentari ací", "SHOW_DELETED": "Mostra el comentari esborrat.", "HIDE_DELETED": "Amaga el comentari esborrat", "DELETE": "Esborrar comentari", - "RESTORE": "Resturar comentari." + "RESTORE": "Resturar comentari.", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Mostrar activitat", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Mostrar activitat anterior ({{showMore}} més)", "TITLE": "Activitat", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "Borrat", "ADDED": "Afegit", - "US_POINTS": "Punts d'US ({{name}})", - "NEW_ATTACHMENT": "Nou adjunt", - "DELETED_ATTACHMENT": "Adjunts esborrats", - "UPDATED_ATTACHMENT": "Actualitzat adjunt {{filename}}", - "DELETED_CUSTOM_ATTRIBUTE": "Esborrar camps personalitzat", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Fet {size, plural, one{un canvi} other{# changes}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "si", "NO": "no", @@ -1071,6 +1093,7 @@ "TAGS": "Etiquetes", "ATTACHMENTS": "adjunts", "IS_DEPRECATED": "és obsolet", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "ordre", "BACKLOG_ORDER": "ordre de backlog", "SPRINT_ORDER": "ordre d'sprint", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Tasca {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estat: {{taskStatus }}. Descripció: {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "Tasca", "LINK_TASKBOARD": "Panell de tasques", "TITLE_LINK_TASKBOARD": "Anar a panell de tasques", "PLACEHOLDER_SUBJECT": "Afegix la descripció de la tasca", @@ -1247,7 +1270,7 @@ "PAGE_TITLE": "Incidències - {{projectName}}", "PAGE_DESCRIPTION": "El panell d'incidències de {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Incidències", - "SECTION_NAME": "Issue", + "SECTION_NAME": "incidència", "ACTION_NEW_ISSUE": "+ NOVA INCIDÈNCIA", "ACTION_PROMOTE_TO_US": "Promocionar història d'usuari", "PLACEHOLDER_FILTER_NAME": "Escriu el filtre i pressiona Intro", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Esborrar pàgina de Wiki", "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Enllaços", - "ACTION_ADD_LINK": "Afegir link" + "ACTION_ADD_LINK": "Afegir link", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "voltes
editat", "LAST_EDIT": "última
edició", "LAST_MODIFICATION": "última modificació" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/locales/taiga/locale-de.json b/app/locales/taiga/locale-de.json index 43949191..9b6b3aa7 100644 --- a/app/locales/taiga/locale-de.json +++ b/app/locales/taiga/locale-de.json @@ -43,7 +43,7 @@ "TEAM_REQUIREMENT": "Team requirement is a requirement that must exist in the project but should have no cost for the client", "OWNER": "Projekteigentümer", "CAPSLOCK_WARNING": "Achtung! Sie verwenden Großbuchstaben in einem Eingabefeld, dass Groß- und Kleinschreibung berücksichtigt.", - "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Sind Sie sicher, dass Sie den Bearbeitungsmodus beenden möchten?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Dieser Wert scheint ungültig zu sein.", @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "User-Stories ansehen", "ADD_USER_STORIES": "User-Stories hinzufügen", "MODIFY_USER_STORIES": "User-Stories modifizieren", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "User-Stories löschen" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Aufgaben ansehen", "ADD_TASKS": "Aufgaben hinzufügen", "MODIFY_TASKS": "Aufgaben ändern", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Aufgaben löschen" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Tickets ansehen", "ADD_ISSUES": "Tickets hinzufügen", "MODIFY_ISSUES": "Tickets ändern", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Tickets löschen" }, "WIKI": { @@ -290,7 +293,7 @@ "HEADER": "Ich bin bereits bei Taiga angemeldet", "PLACEHOLDER_AUTH_NAME": "Benutzername oder E-Mail-Adresse", "LINK_FORGOT_PASSWORD": "Haben Sie es vergessen?", - "TITLE_LINK_FORGOT_PASSWORD": "Did you forget your password?", + "TITLE_LINK_FORGOT_PASSWORD": "Haben Sie Ihr Passwort vergessen?", "ACTION_ENTER": "Eingabe", "ACTION_SIGN_IN": "Login", "PLACEHOLDER_AUTH_PASSWORD": "Passwort" @@ -558,10 +561,10 @@ "ACTION_ADD": "Neu hinzufügen {{objName}}" }, "PROJECT_VALUES_TAGS": { - "TITLE": "Tags", + "TITLE": "Schlagwörter", "SUBTITLE": "View and edit the color of your user stories", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "Es sieht so aus, als konnte zu Ihren Suchkriterien nichts passendes gefunden werden." }, "ROLES": { "PAGE_TITLE": "Rollen - {{projectName}}", @@ -689,7 +692,7 @@ "SEVERITIES": "Schweregrade", "TYPES": "Typen", "CUSTOM_FIELDS": "Benutzerdefinierte Felder", - "TAGS": "Tags" + "TAGS": "Schlagwörter" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Projektprofil" @@ -981,7 +984,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - User-Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Abgeschlossen {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} von {{userStoryTotalTasks}} Aufgaben geschlossen). Punkte: {{userStoryPoints}}. Beschreibung: {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "User-Story", "LINK_TASKBOARD": "Taskboard", "TITLE_LINK_TASKBOARD": "Zu Taskboard wechseln", "TOTAL_POINTS": "Gesamtpunkte", @@ -1001,13 +1004,13 @@ "NOT_ESTIMATED": "Nicht eingeschätzt", "TOTAL_US_POINTS": "User-Story-Punkte insgesamt", "TRIBE": { - "PUBLISH": "Publish as Gig in Taiga Tribe", - "PUBLISH_INFO": "More info", + "PUBLISH": "Als Gig in Taiga Tribe veröffentlichen", + "PUBLISH_INFO": "Weitere Infos", "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", - "EDIT_LINK": "Edit link", - "CLOSE": "Close", - "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "EDIT_LINK": "Link bearbeiten", + "CLOSE": "Schließen", + "SYNCHRONIZE_LINK": "mit Taiga Tribe synchronisieren", "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" }, @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Kommentar gelöscht von {{user}} am {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Kommentare", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Kommentieren", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Geben Sie hier einen neuen Kommentar ein", "SHOW_DELETED": "Gelöschten Kommentar anzeigen", "HIDE_DELETED": "Gelöschten Kommentar ausblenden", "DELETE": "Kommentar löschen", - "RESTORE": "Kommentar wiederherstellen" + "RESTORE": "Kommentar wiederherstellen", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Aktivitäten zeigen", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Vorherige Einträge zeigen ({{showMore}} vorhanden)", "TITLE": "Aktivität", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "entfernt", "ADDED": "hinzugefügt", - "US_POINTS": "User-Story Punkte ({{name}})", - "NEW_ATTACHMENT": "Neuer Anhang", - "DELETED_ATTACHMENT": "Gelöschter Anhang", - "UPDATED_ATTACHMENT": "aktualisierter Anhang {{filename}}", - "DELETED_CUSTOM_ATTRIBUTE": "gelöschtes Kundenattribut", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Machte {size, plural, one{eine Änderung} other{# Änderungen}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "ja", "NO": "nein", @@ -1071,6 +1093,7 @@ "TAGS": "Schlagwörter", "ATTACHMENTS": "Anhänge", "IS_DEPRECATED": "ist veraltet", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "Befehl", "BACKLOG_ORDER": "Backlog Befehl", "SPRINT_ORDER": "Sprint Befehl", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Aufgabe {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{taskStatus }}. Beschreibung: {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "Aufgabe", "LINK_TASKBOARD": "Taskboard", "TITLE_LINK_TASKBOARD": "Zu Taskboard wechseln", "PLACEHOLDER_SUBJECT": "Betreff...", @@ -1247,7 +1270,7 @@ "PAGE_TITLE": "Tickets - {{projectName}}", "PAGE_DESCRIPTION": "Das Ticket-Listen Panel des Projekts {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Tickets", - "SECTION_NAME": "Issue", + "SECTION_NAME": "Ticket", "ACTION_NEW_ISSUE": "+ NEUES TICKET", "ACTION_PROMOTE_TO_US": "Zur User-Story aufwerten", "PLACEHOLDER_FILTER_NAME": "Benennen Sie den Filter und drücken Sie die Eingabetaste", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Wiki Seite löschen", "DELETE_LINK_TITLE": "Entferne Wiki Link", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Links", - "ACTION_ADD_LINK": "Link hinzufügen" + "ACTION_ADD_LINK": "Link hinzufügen", + "ALL_PAGES": "All pages" }, "SUMMARY": { - "TIMES_EDITED": "Zeiten
bearbeitet", + "TIMES_EDITED": "mal
bearbeitet", "LAST_EDIT": "letzte
Bearbeitung", "LAST_MODIFICATION": "letzte Änderung" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/locales/taiga/locale-es.json b/app/locales/taiga/locale-es.json index 080ef897..8e608581 100644 --- a/app/locales/taiga/locale-es.json +++ b/app/locales/taiga/locale-es.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "Ver historias de usuario", "ADD_USER_STORIES": "Crear historias de usuario", "MODIFY_USER_STORIES": "Editar historias de usuario", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Borrar historias de usuario" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Ver tareas", "ADD_TASKS": "Crear tareas", "MODIFY_TASKS": "Editar tareas", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Borrar tareas" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Ver peticiones", "ADD_ISSUES": "Crear peticiones", "MODIFY_ISSUES": "Editar peticiones", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Borrar peticiones" }, "WIKI": { @@ -558,7 +561,7 @@ "ACTION_ADD": "Añadir nuevo {{objName}}" }, "PROJECT_VALUES_TAGS": { - "TITLE": "Tags", + "TITLE": "Etiquetas", "SUBTITLE": "View and edit the color of your user stories", "EMPTY": "Currently there are no tags", "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" @@ -689,7 +692,7 @@ "SEVERITIES": "Gravedades", "TYPES": "Tipos", "CUSTOM_FIELDS": "Atributos personalizados", - "TAGS": "Tags" + "TAGS": "Etiquetas" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Perfil de proyecto" @@ -981,7 +984,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Historia de Usuario {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{userStoryStatus }}. Completado el {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tareas cerradas). Puntos: {{userStoryPoints}}. Descripción: {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "Historia de usuario", "LINK_TASKBOARD": "Panel de tareas", "TITLE_LINK_TASKBOARD": "Ir al panel de tareas", "TOTAL_POINTS": "puntos totales", @@ -1002,7 +1005,7 @@ "TOTAL_US_POINTS": "Total puntos de historia", "TRIBE": { "PUBLISH": "Publish as Gig in Taiga Tribe", - "PUBLISH_INFO": "More info", + "PUBLISH_INFO": "Mas información", "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", "EDIT_LINK": "Edit link", @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comentario borrado por {{user}} el {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Comentarios", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Comentar", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Escribe un nuevo comentario aquí", "SHOW_DELETED": "Mostrar comentarios eliminados", "HIDE_DELETED": "Ocultar comentarios eliminados", "DELETE": "Borrar comentario", - "RESTORE": "Restaurar comentario" + "RESTORE": "Restaurar comentario", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Mostrar actividad", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Ver entradas anteriores ({{showMore}} más)", "TITLE": "Actividad", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "borrado", "ADDED": "agregado", - "US_POINTS": "Puntos de historia ({{name}})", - "NEW_ATTACHMENT": "nuevo adjunto", - "DELETED_ATTACHMENT": "adjunto eliminado", - "UPDATED_ATTACHMENT": "Adjunto {{filename}} actualizado", - "DELETED_CUSTOM_ATTRIBUTE": "eliminar atributos personalizados", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "{size, plural, one{Un cambio realizado} other{# cambios realizados}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "sí", "NO": "no", @@ -1071,6 +1093,7 @@ "TAGS": "etiquetas", "ATTACHMENTS": "adjuntos", "IS_DEPRECATED": "está desactualizado", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "orden", "BACKLOG_ORDER": "orden en backlog", "SPRINT_ORDER": "orden en sprint", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Tarea {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{taskStatus }}. Descripción: {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "Tarea", "LINK_TASKBOARD": "Panel de tareas", "TITLE_LINK_TASKBOARD": "Ir al panel de tareas", "PLACEHOLDER_SUBJECT": "Escribe el asunto de la nueva tarea", @@ -1247,7 +1270,7 @@ "PAGE_TITLE": "Peticiones - {{projectName}}", "PAGE_DESCRIPTION": "El panel de peticiones del proyecto {{projectName}}: {{projectDescription}}\n", "LIST_SECTION_NAME": "Peticiones", - "SECTION_NAME": "Issue", + "SECTION_NAME": "Petición", "ACTION_NEW_ISSUE": "+ NUEVA PETICIÓN", "ACTION_PROMOTE_TO_US": "Promover a Historia de Usuario", "PLACEHOLDER_FILTER_NAME": "Escribe un nombre para el filtro y pulsa enter", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Eliminar Página del Wiki", "DELETE_LINK_TITLE": "Eliminar enlace de la Wiki", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Enlaces", - "ACTION_ADD_LINK": "Añadir enlace" + "ACTION_ADD_LINK": "Añadir enlace", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "veces
editada", "LAST_EDIT": "última
edición", "LAST_MODIFICATION": "ultima modificación" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/locales/taiga/locale-fi.json b/app/locales/taiga/locale-fi.json index 960b9254..eab5b6a4 100644 --- a/app/locales/taiga/locale-fi.json +++ b/app/locales/taiga/locale-fi.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "Katso käyttäjätarinoita", "ADD_USER_STORIES": "Lisää käyttäjätarinoita", "MODIFY_USER_STORIES": "Muokkaa käyttäjätarinoita", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Poista käyttäjätarinoita" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Katsot tehtäviä", "ADD_TASKS": "Lisää tehtäviä", "MODIFY_TASKS": "Muokkaa tehtäviä", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Poista tehtäviä" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Katso pyyntöjä", "ADD_ISSUES": "Lisää pyyntöjä", "MODIFY_ISSUES": "Muokkaa pyyntöjä", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Poista pyyntöjä" }, "WIKI": { @@ -558,7 +561,7 @@ "ACTION_ADD": "Lisää uusi {{objName}}" }, "PROJECT_VALUES_TAGS": { - "TITLE": "Tags", + "TITLE": "Avainsanat", "SUBTITLE": "View and edit the color of your user stories", "EMPTY": "Currently there are no tags", "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" @@ -689,7 +692,7 @@ "SEVERITIES": "Vakavuudet", "TYPES": "Tyypit", "CUSTOM_FIELDS": "Omat kentät", - "TAGS": "Tags" + "TAGS": "Avainsanat" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Projektin profiili" @@ -981,7 +984,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "Käyttäjätarina", "LINK_TASKBOARD": "Tehtävätaulu", "TITLE_LINK_TASKBOARD": "Siirry tehtävätauluun", "TOTAL_POINTS": "total points", @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "{{user}} poisti kommentin {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Kommentit", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Kommentti", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Lisää uusi kommentti tässä", "SHOW_DELETED": "Näytä poistettu kommentti", "HIDE_DELETED": "Piilota poistettu kommentti", "DELETE": "Delete comment", - "RESTORE": "Palauta kommentti" + "RESTORE": "Palauta kommentti", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Näytä tapahtumat", "DATETIME": "DD.MM.YY - HH:mm", "SHOW_MORE": "+ Näytä edelliset rivit ({{showMore}} lisää)", "TITLE": "Aktiivisuus", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "poistettu", "ADDED": "lisätty", - "US_POINTS": "Kt pisteet ({{name}})", - "NEW_ATTACHMENT": "uusi liite", - "DELETED_ATTACHMENT": "poistettu liite", - "UPDATED_ATTACHMENT": "päivitetty liite {{filename}}", - "DELETED_CUSTOM_ATTRIBUTE": "poista oma attribuutti", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Tehty {size, plural, one{muutos} other{# muutosta}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "Kyllä", "NO": "ei", @@ -1071,6 +1093,7 @@ "TAGS": "avainsanat", "ATTACHMENTS": "liitteet", "IS_DEPRECATED": "on vanhentunut", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "järjestys", "BACKLOG_ORDER": "odottavien järjestys", "SPRINT_ORDER": "kierroksen järjestys", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Poista wiki-sivu", "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Linkit", - "ACTION_ADD_LINK": "Lisää linkki" + "ACTION_ADD_LINK": "Lisää linkki", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "kertaa
muokattu", "LAST_EDIT": "viimeinen
muokkaus", "LAST_MODIFICATION": "viimeinen muokkaus" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/locales/taiga/locale-fr.json b/app/locales/taiga/locale-fr.json index f7d7cc71..106a5df0 100644 --- a/app/locales/taiga/locale-fr.json +++ b/app/locales/taiga/locale-fr.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "Afficher les récits utilisateur", "ADD_USER_STORIES": "Ajouter des récits utilisateur", "MODIFY_USER_STORIES": "Modifier les récits utilisateur", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Supprimer des récits utilisateur" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Voir les tâches", "ADD_TASKS": "Ajouter des tâches", "MODIFY_TASKS": "Modifier des tâches", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Supprimer des tâches" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Voir les tickets", "ADD_ISSUES": "Ajouter des tickets", "MODIFY_ISSUES": "Modifier des tickets", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Supprimer des tickets" }, "WIKI": { @@ -558,7 +561,7 @@ "ACTION_ADD": "Ajouter un nouveau {{objName}}" }, "PROJECT_VALUES_TAGS": { - "TITLE": "Tags", + "TITLE": "Mots-clés", "SUBTITLE": "View and edit the color of your user stories", "EMPTY": "Currently there are no tags", "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" @@ -689,7 +692,7 @@ "SEVERITIES": "Gravité", "TYPES": "Types", "CUSTOM_FIELDS": "Champs personnalisés", - "TAGS": "Tags" + "TAGS": "Mots-clés" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Profil projet" @@ -981,7 +984,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Récit utilisateur {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "État : {{userStoryStatus }}. Achevé {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} sur {{userStoryTotalTasks}} tâches fermées). Points : {{userStoryPoints}}. Description : {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "Récit utilisateur", "LINK_TASKBOARD": "Tableau des tâches", "TITLE_LINK_TASKBOARD": "Aller au tableau des tâches", "TOTAL_POINTS": "total des points", @@ -1002,7 +1005,7 @@ "TOTAL_US_POINTS": "Total des points RU", "TRIBE": { "PUBLISH": "Publish as Gig in Taiga Tribe", - "PUBLISH_INFO": "More info", + "PUBLISH_INFO": "Plus d'informations", "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", "EDIT_LINK": "Edit link", @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Commentaire supprimé par {{user}} le {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Commentaires", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Commentaire", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Entrez un nouveau commentaire ici", "SHOW_DELETED": "Afficher le commentaire supprimé", "HIDE_DELETED": "Cacher le commentaire supprimé", "DELETE": "Supprimer le commentaire", - "RESTORE": "Restaurer le commentaire" + "RESTORE": "Restaurer le commentaire", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Afficher l'activité", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Montrer les entrées précédentes ({{showMore}} plus)", "TITLE": "Activité", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "supprimé", "ADDED": "ajouté", - "US_POINTS": "Points du récit utilisateur ({{name}})", - "NEW_ATTACHMENT": "nouvelle pièce jointe", - "DELETED_ATTACHMENT": "Pièce jointe supprimée", - "UPDATED_ATTACHMENT": "Pièce jointe {{filename}} modifiée", - "DELETED_CUSTOM_ATTRIBUTE": "Attribut personnalisé supprimé", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "A fait {size, plural, one{une modification} other{# modifications}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "oui", "NO": "no", @@ -1071,6 +1093,7 @@ "TAGS": "mots-clés", "ATTACHMENTS": "pièces jointes", "IS_DEPRECATED": "est obsolète", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "classement", "BACKLOG_ORDER": "classement du backlog", "SPRINT_ORDER": "classement du sprint", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Tâche {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "État : {{taskStatus }}. Description : {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "Tâche", "LINK_TASKBOARD": "Tableau des tâches", "TITLE_LINK_TASKBOARD": "Aller au tableau des tâches", "PLACEHOLDER_SUBJECT": "Entrez l'objet de la nouvelle tâche", @@ -1247,7 +1270,7 @@ "PAGE_TITLE": "Tickets - {{projectName}}", "PAGE_DESCRIPTION": "Le panneau de la liste des tickets du projet {{projectName}} : {{projectDescription}}", "LIST_SECTION_NAME": "Tickets", - "SECTION_NAME": "Issue", + "SECTION_NAME": "Ticket", "ACTION_NEW_ISSUE": "+ NOUVEAU TICKET", "ACTION_PROMOTE_TO_US": "Promouvoir en récit utilisateur", "PLACEHOLDER_FILTER_NAME": "Écrivez le nom du filtre et appuyez sur \"Entrée\"", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Supprimer la page Wiki", "DELETE_LINK_TITLE": "Supprimer un lien Wiki", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Liens", - "ACTION_ADD_LINK": "Ajouter un lien" + "ACTION_ADD_LINK": "Ajouter un lien", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "modifications", "LAST_EDIT": "dernière
modification", "LAST_MODIFICATION": "dernière modification" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/locales/taiga/locale-it.json b/app/locales/taiga/locale-it.json index 497bd899..a4293d59 100644 --- a/app/locales/taiga/locale-it.json +++ b/app/locales/taiga/locale-it.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "Vai alle storie utente", "ADD_USER_STORIES": "Aggiungi le storie utente", "MODIFY_USER_STORIES": "Modifica le storie utente", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Elimina le storie utente" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Guarda i compiti", "ADD_TASKS": "Aggiungi i compiti", "MODIFY_TASKS": "Modifica i compiti", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Elimina i compiti" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Guarda i problemi", "ADD_ISSUES": "Aggiungi un problema", "MODIFY_ISSUES": "Modifica i problemi", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Elimina i problemi" }, "WIKI": { @@ -558,7 +561,7 @@ "ACTION_ADD": "Aggiungi {{objName}}" }, "PROJECT_VALUES_TAGS": { - "TITLE": "Tags", + "TITLE": "Tag", "SUBTITLE": "View and edit the color of your user stories", "EMPTY": "Currently there are no tags", "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" @@ -689,7 +692,7 @@ "SEVERITIES": "Severitá", "TYPES": "Tipi", "CUSTOM_FIELDS": "Campi personalizzati", - "TAGS": "Tags" + "TAGS": "Tag" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Profilo progetto" @@ -981,7 +984,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completata per il {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} di {{userStoryTotalTasks}} tasks closed). Punti: {{userStoryPoints}}. Descrizione: {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "Storia utente", "LINK_TASKBOARD": "Pannello dei compiti", "TITLE_LINK_TASKBOARD": "Vai al pannello dei compiti", "TOTAL_POINTS": "totale punti", @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Commento cancellato da {{user}} il {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Commenti", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Commento", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Scrivi un nuovo commento qui", "SHOW_DELETED": "Visualizza commento cancellato", "HIDE_DELETED": "Nascondi commento cancellato", "DELETE": "Cancella commento", - "RESTORE": "Ripristina commento" + "RESTORE": "Ripristina commento", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Mostra attività", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "Mostra gli inserimenti precedenti ({{showMore}} more)", "TITLE": "Attività", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "rimosso", "ADDED": "aggiunto", - "US_POINTS": "punti storia utente ({{name}})", - "NEW_ATTACHMENT": "nuovo allegato", - "DELETED_ATTACHMENT": "allegato eliminato", - "UPDATED_ATTACHMENT": "Aggiornato l'allegato {{filename}}", - "DELETED_CUSTOM_ATTRIBUTE": "elimina un attributo personalizzato", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Fatto {size, plural, one{un cambiamento} other{# cambiamenti}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "si", "NO": "no", @@ -1071,6 +1093,7 @@ "TAGS": "tag", "ATTACHMENTS": "allegati", "IS_DEPRECATED": "è deprecato", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "ordine", "BACKLOG_ORDER": "Ordine di backlog", "SPRINT_ORDER": "Ordine dello sprint", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Compiti {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Stato: {{taskStatus }}. Descrizione: {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "Compito", "LINK_TASKBOARD": "Pannello dei compiti", "TITLE_LINK_TASKBOARD": "Vai al pannello dei compiti", "PLACEHOLDER_SUBJECT": "Inserisci il soggetto del nuovo compito", @@ -1247,7 +1270,7 @@ "PAGE_TITLE": "Criticitá - {{projectName}}", "PAGE_DESCRIPTION": "Il pannello con la lista dei problemi del progetto {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "problemi", - "SECTION_NAME": "Issue", + "SECTION_NAME": "Problema", "ACTION_NEW_ISSUE": "+ NUOVA CRITICITÁ", "ACTION_PROMOTE_TO_US": "Promuovi la storia utente", "PLACEHOLDER_FILTER_NAME": "Scrivi il nome del filtro e premi invio", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Elimina Pagina Wiki", "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Link", - "ACTION_ADD_LINK": "Aggiungi link" + "ACTION_ADD_LINK": "Aggiungi link", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "tempo
modificato", "LAST_EDIT": "
ultima modifica", "LAST_MODIFICATION": "ultima modifica" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/locales/taiga/locale-nl.json b/app/locales/taiga/locale-nl.json index 4fdfb575..a4c97aba 100644 --- a/app/locales/taiga/locale-nl.json +++ b/app/locales/taiga/locale-nl.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "Bekijk user stories", "ADD_USER_STORIES": "User stories toevoegen", "MODIFY_USER_STORIES": "user stories bewerken", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Verwijderd user stories" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Bekijk taken", "ADD_TASKS": "Taak toevoegen", "MODIFY_TASKS": "Bewerk taken", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Verwijder taken" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Bekijk issues", "ADD_ISSUES": "Issues toevoegen", "MODIFY_ISSUES": "Bewerk issues", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Issues verwijderen" }, "WIKI": { @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Opmerking verwijderd door {{user}} op {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Reacties", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Reageer", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Type hier nieuw commentaar", "SHOW_DELETED": "Toon verwijderd commentaar", "HIDE_DELETED": "Verberg verwijderde opmerkingen", "DELETE": "Delete comment", - "RESTORE": "Opmerking herstellen" + "RESTORE": "Opmerking herstellen", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Toon activiteit", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Toon vorige items ({{showMore}} meer)", "TITLE": "Activiteit", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "verwijderd", "ADDED": "toegevoegd", - "US_POINTS": "US punten ({{name}})", - "NEW_ATTACHMENT": "Nieuwe bijlage", - "DELETED_ATTACHMENT": "verwijderd bijlage", - "UPDATED_ATTACHMENT": "bijlage {{filename}} bijgewerkt", - "DELETED_CUSTOM_ATTRIBUTE": "eigen attribuut verwijderen", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "{size, plural, one{één verandering} other{# veranderingen}} gemaakt", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "ja", "NO": "nee", @@ -1071,6 +1093,7 @@ "TAGS": "tags", "ATTACHMENTS": "bijlagen", "IS_DEPRECATED": "is verourderd", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "volgorde", "BACKLOG_ORDER": "backlog volgorde", "SPRINT_ORDER": "sprint volgorde", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Taak {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{taskStatus }}. Omschrijving: {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "Taak", "LINK_TASKBOARD": "Taakbord", "TITLE_LINK_TASKBOARD": "Ga naar het taakbord", "PLACEHOLDER_SUBJECT": "Type het nieuwe onderwerp voor de taak", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Verwijderd wiki pagina", "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Links", - "ACTION_ADD_LINK": "Link toevoegen" + "ACTION_ADD_LINK": "Link toevoegen", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "keer
bewerkt", "LAST_EDIT": "laatst
bewerkt", "LAST_MODIFICATION": "laatste wijziging" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/locales/taiga/locale-pl.json b/app/locales/taiga/locale-pl.json index 8010643a..7353bc6f 100644 --- a/app/locales/taiga/locale-pl.json +++ b/app/locales/taiga/locale-pl.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "Przeglądaj historyjki użytkownika", "ADD_USER_STORIES": "Dodawaj historyjki użytkownika", "MODIFY_USER_STORIES": "Modyfikuj historyjki użytkownika", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Usuwaj historyjki użytkownika" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Przeglądaj zadania", "ADD_TASKS": "Dodawaj zadania", "MODIFY_TASKS": "Modyfikuj zadania", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Usuwaj zadania" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Przeglądaj zgłoszenia", "ADD_ISSUES": "Dodawaj zgłoszenia", "MODIFY_ISSUES": "Modyfikuj zgłoszenia", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Usuwaj zgłoszenia" }, "WIKI": { @@ -558,7 +561,7 @@ "ACTION_ADD": "Dodaj nowy {{objName}}" }, "PROJECT_VALUES_TAGS": { - "TITLE": "Tags", + "TITLE": "Tagi", "SUBTITLE": "View and edit the color of your user stories", "EMPTY": "Currently there are no tags", "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" @@ -689,7 +692,7 @@ "SEVERITIES": "Ważność", "TYPES": "Typy", "CUSTOM_FIELDS": "Niestandardowe pola", - "TAGS": "Tags" + "TAGS": "Tagi" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Profil projektu" @@ -981,7 +984,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Historyjka użytkownika {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Zakończono {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} z {{userStoryTotalTasks}} zadań). Punktów: {{userStoryPoints}}. Opis: {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "Historyjka użytkownika", "LINK_TASKBOARD": "Tablica zadań", "TITLE_LINK_TASKBOARD": "Idź do listy zadań", "TOTAL_POINTS": "total points", @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Komentarz usunięty przez {{user}} w dniu {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Komentarze", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Komentarz", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Tutaj wpisz nowy komentarz", "SHOW_DELETED": "Pokaż usunięty komentarz", "HIDE_DELETED": "Ukryj skasowane komentarze", "DELETE": "Delete comment", - "RESTORE": "Przywróć komentarz" + "RESTORE": "Przywróć komentarz", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Pokaż aktywność", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Pokaż poprzednie wpisy ({{showMore}} więcej)", "TITLE": "Aktywność", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "usunięty", "ADDED": "dodany", - "US_POINTS": "Punkty HU ({{name}})", - "NEW_ATTACHMENT": "nowy załącznik", - "DELETED_ATTACHMENT": "Usunięty załącznik", - "UPDATED_ATTACHMENT": "Zaktualizowany załącznik {{filename}}", - "DELETED_CUSTOM_ATTRIBUTE": "Usunięty niestandardowy atrybut", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Dokonano {size, plural, one{one change} other{# changes}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "tak", "NO": "nie", @@ -1071,6 +1093,7 @@ "TAGS": "tagi", "ATTACHMENTS": "załączniki", "IS_DEPRECATED": "jest przedawniony", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "kolejność", "BACKLOG_ORDER": "kolejność backlogu", "SPRINT_ORDER": "kolejność sprintów", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Zadanie {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{taskStatus }}. Opis: {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "Zadania", "LINK_TASKBOARD": "Tablica zadań", "TITLE_LINK_TASKBOARD": "Idź do listy zadań", "PLACEHOLDER_SUBJECT": "Wpisz temat zadania", @@ -1247,7 +1270,7 @@ "PAGE_TITLE": "Zgłoszenia - {{projectName}}", "PAGE_DESCRIPTION": "Lista zgłoszeń w projekcie {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Zgłoszenia", - "SECTION_NAME": "Issue", + "SECTION_NAME": "Zgłoszenie", "ACTION_NEW_ISSUE": "+ NOWE ZGŁOSZENIE", "ACTION_PROMOTE_TO_US": "Awansuj na historyjkę użytkownika", "PLACEHOLDER_FILTER_NAME": "Wpisz nazwę filtru i kliknij enter", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Usuń tą stronę Wiki", "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Linki", - "ACTION_ADD_LINK": "Dodaj link" + "ACTION_ADD_LINK": "Dodaj link", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "razy
edytowano", "LAST_EDIT": "ostatnia
edycja", "LAST_MODIFICATION": "ostatnia modyfikacja" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index 024a91bf..29003627 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "Ver histórias de usuários", "ADD_USER_STORIES": "Adicionar histórias de usuários", "MODIFY_USER_STORIES": "Modificar histórias de usuários", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Apagar histórias de usuários" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Ver tarefas", "ADD_TASKS": "Adicionar uma nova Tarefa", "MODIFY_TASKS": "Modificar tarefa", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Apagar tarefas" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Ver problemas", "ADD_ISSUES": "Adicionar problemas", "MODIFY_ISSUES": "Modificar problemas", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Apagar problemas" }, "WIKI": { @@ -484,8 +487,8 @@ "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Gostaria de se tornar o novo dono do projeto?", "REQUEST_OWNERSHIP_DESC": "Solicitar ao atual dono de projeto {{name}} a transferência da propriedade deste projeto para você.", "REQUEST_OWNERSHIP_BUTTON": "Solicitação", - "REQUEST_OWNERSHIP_SUCCESS": "We'll notify the project owner", - "CHANGE_OWNER": "Change owner", + "REQUEST_OWNERSHIP_SUCCESS": "Vamos notificar o dono do projeto", + "CHANGE_OWNER": "Mudar dono", "CHANGE_OWNER_SUCCESS_TITLE": "Ok, your request has been sent!", "CHANGE_OWNER_SUCCESS_DESC": "We will notify you by email if the project ownership request is accepted or declined" }, @@ -560,7 +563,7 @@ "PROJECT_VALUES_TAGS": { "TITLE": "Tags", "SUBTITLE": "View and edit the color of your user stories", - "EMPTY": "Currently there are no tags", + "EMPTY": "Atualmente não há tags", "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" }, "ROLES": { @@ -780,7 +783,7 @@ "MEMBERS_COUNTER_TITLE": "{total, plural, one{one member} other{# members}}", "BLOCKED_PROJECT": { "BLOCKED": "Projeto bloqueado", - "THIS_PROJECT_IS_BLOCKED": "This project is temporarily blocked", + "THIS_PROJECT_IS_BLOCKED": "Este projeto está temporariamente bloqueado", "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "In order to unblock your projects, contact the administrator." }, "STATS": { @@ -962,14 +965,14 @@ "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { - "TITLE": "Unfortunately, this project can't be left without an owner", + "TITLE": "Infelizmente, este projeto não pode ficar sem um dono", "CURRENT_USER_OWNER": { "DESC": "You are the current owner of this project. Before leaving, please transfer ownership to someone else.", - "BUTTON": "Change the project owner" + "BUTTON": "Mude o dono do projeto" }, "OTHER_USER_OWNER": { "DESC": "Unfortunately, you can't delete a member who is also the current project owner. First, please assign a new project owner.", - "BUTTON": "Request project owner change" + "BUTTON": "Solicite a mudança do dono do projeto" } }, "CHANGE_OWNER": { @@ -981,13 +984,13 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - História de Usuário {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{userStoryStatus }}. Completos {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tarefas encerradas). Pontos: {{userStoryPoints}}. Descrição: {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "História de usuário", "LINK_TASKBOARD": "Quadro de Tarefas", "TITLE_LINK_TASKBOARD": "Ir para o quadro de tarefas", "TOTAL_POINTS": "total de pontos", "ADD": "+ Adicionar uma nova História de Usuário", "ADD_BULK": "Adicionar Histórias de Usuários em lote", - "PROMOTED": "Esta história de usuário foi promovida do problema:", + "PROMOTED": "Esta História de Usuário foi criada a partir do Problema:", "TITLE_LINK_GO_TO_ISSUE": "Ir para problema", "EXTERNAL_REFERENCE": "Esta História de Usuário foi criada de", "GO_TO_EXTERNAL_REFERENCE": "Ir para a origem", @@ -1002,7 +1005,7 @@ "TOTAL_US_POINTS": "Total de pontos de histórias", "TRIBE": { "PUBLISH": "Publish as Gig in Taiga Tribe", - "PUBLISH_INFO": "More info", + "PUBLISH_INFO": "Mais informações", "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", "EDIT_LINK": "Edit link", @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comentário apagado por {{user}} em {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Comentários", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Comentário", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Escreva um novo comentário aqui", "SHOW_DELETED": "Mostrar comentários apagados", "HIDE_DELETED": "Esconder comentário apagado", "DELETE": "Apagar comentário", - "RESTORE": "Restaurar comentário" + "RESTORE": "Restaurar comentário", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Exibir atividade", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Mostrar entradas anteriores (mais {{showMore}})", "TITLE": "Atividade", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "removido", "ADDED": "adicionado", - "US_POINTS": "pontos de história ({{name}})", - "NEW_ATTACHMENT": "novo anexo", - "DELETED_ATTACHMENT": "apagar anexo", - "UPDATED_ATTACHMENT": "anexo atualizado {{filename}}", - "DELETED_CUSTOM_ATTRIBUTE": "atributo personalizado apagado", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Feito {size, plural, one{one change} other{# changes}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "sim", "NO": "não", @@ -1071,6 +1093,7 @@ "TAGS": "tags", "ATTACHMENTS": "anexos", "IS_DEPRECATED": "está obsoleto", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "ordem", "BACKLOG_ORDER": "requisição do backlog", "SPRINT_ORDER": "ordem de sprint ", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Tarefa {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{taskStatus }}. Descrição: {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "Tarefa", "LINK_TASKBOARD": "Quadro de Tarefas", "TITLE_LINK_TASKBOARD": "Ir para o quadro de tarefas", "PLACEHOLDER_SUBJECT": "Digite um novo titulo para tarefa", @@ -1247,7 +1270,7 @@ "PAGE_TITLE": "Problemas - {{projectName}}", "PAGE_DESCRIPTION": "O painel de problemas do projeto {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Problemas", - "SECTION_NAME": "Issue", + "SECTION_NAME": "Problema", "ACTION_NEW_ISSUE": "+ NOVO PROBLEMA", "ACTION_PROMOTE_TO_US": "Promover para História de Usuário", "PLACEHOLDER_FILTER_NAME": "Digite o nome do filtro e pressione Enter", @@ -1424,7 +1447,7 @@ "PRIVATE_PROJECT": "Projeto Privado", "CREATE_PROJECT": "Criar projeto", "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects", - "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects", + "MAX_PUBLIC_PROJECTS": "Infelizmente, você atingiu o número máximo de projetos públicos", "CHANGE_PLANS": "change plans" }, "WIKI": { @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Apagar página Wiki", "DELETE_LINK_TITLE": "Remover link de Wiki", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Links", - "ACTION_ADD_LINK": "Adicionar link" + "ACTION_ADD_LINK": "Adicionar link", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "vezes
editadas", "LAST_EDIT": "última
edição", "LAST_MODIFICATION": "ultima modificação" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { @@ -1469,12 +1503,12 @@ "NEW_PROJECT": "{{username}} criou o projeto {{project_name}}", "MILESTONE_UPDATED": "{{username}} atualizou a sprint {{obj_name}}", "US_UPDATED": "{{username}} atualizou o atributo \"{{field_name}}\" da História de Usuário {{obj_name}}", - "US_UPDATED_WITH_NEW_VALUE": "{{username}} atualizou o trabalho \"{{field_name}}\" da US {{obj_name}} para {{new_value}}", - "US_UPDATED_POINTS": "{{username}} atualizou pontos de '{{role_name}}' da História de Usuário {{obj_name}} para {{new_value}}", + "US_UPDATED_WITH_NEW_VALUE": "{{username}} atualizou o atributo \"{{field_name}}\" da História de Usuário {{obj_name}} para {{new_value}}", + "US_UPDATED_POINTS": "{{username}} atualizou os pontos de '{{role_name}}' da História de Usuário {{obj_name}} para {{new_value}}", "ISSUE_UPDATED": "{{username}} atualizou o atributo \"{{field_name}}\" do problema {{obj_name}}", "ISSUE_UPDATED_WITH_NEW_VALUE": "{{username}} atualizou o atributo \"{{field_name}}\" do problema {{obj_name}} para {{new_value}}", "TASK_UPDATED": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} para {{new_value}}", - "TASK_UPDATED_WITH_NEW_VALUE": "{{username}} Atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} para {{new_value}}", + "TASK_UPDATED_WITH_NEW_VALUE": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} para {{new_value}}", "TASK_UPDATED_WITH_US": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} que pertence à História de Usuário {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} que pertence à História de Usuário {{us_name}} para {{new_value}}", "WIKI_UPDATED": "{{username}} atualizou a página wiki {{obj_name}}", @@ -1518,7 +1552,7 @@ }, "STEP3": { "TITLE": "Observando", - "TEXT1": "And right here you will find the ones in your projects that you want to know about.", + "TEXT1": "E aqui você vai encontrar os do seu projeto que você escolheu seguir.", "TEXT2": "Você já está trabalhando com Taiga ;)" }, "STEP4": { diff --git a/app/locales/taiga/locale-ru.json b/app/locales/taiga/locale-ru.json index dacb6949..7a0c666e 100644 --- a/app/locales/taiga/locale-ru.json +++ b/app/locales/taiga/locale-ru.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "Просматривать пользовательские истории", "ADD_USER_STORIES": "Добавлять пользовательские истории", "MODIFY_USER_STORIES": "Изменять пользовательские истории", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Удалять пользовательские истории" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Просмотреть задачи", "ADD_TASKS": "Добавить задачи", "MODIFY_TASKS": "Редактировать задачи", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Удалить задачи" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Посмотреть запросы", "ADD_ISSUES": "Добавить запросы", "MODIFY_ISSUES": "Изменить запросы", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Удалить запросы" }, "WIKI": { @@ -558,7 +561,7 @@ "ACTION_ADD": "Добавить новый" }, "PROJECT_VALUES_TAGS": { - "TITLE": "Tags", + "TITLE": "Тэги", "SUBTITLE": "View and edit the color of your user stories", "EMPTY": "Currently there are no tags", "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" @@ -689,7 +692,7 @@ "SEVERITIES": "Степени важности", "TYPES": "Типы", "CUSTOM_FIELDS": "Собственные поля", - "TAGS": "Tags" + "TAGS": "Тэги" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Профиль проекта" @@ -981,7 +984,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Пользовательская История {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Статус: {{userStoryStatus }}. Выполнено {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} из {{userStoryTotalTasks}} задач). Очки: {{userStoryPoints}}. Описание: {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "Пользовательская история", "LINK_TASKBOARD": "Панель задач", "TITLE_LINK_TASKBOARD": "Перейти к панели задач", "TOTAL_POINTS": "общее число очков", @@ -1002,7 +1005,7 @@ "TOTAL_US_POINTS": "Всего очков за ПИ", "TRIBE": { "PUBLISH": "Publish as Gig in Taiga Tribe", - "PUBLISH_INFO": "More info", + "PUBLISH_INFO": "Больше инфо", "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", "EDIT_LINK": "Edit link", @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Комментарий удален {{user}} {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Комментарии", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Комментарий", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Добавить комментарий", "SHOW_DELETED": "Показать удаленный комментарий", "HIDE_DELETED": "Скрыть удаленный комментарий", "DELETE": "Удалить комментарий", - "RESTORE": "Показать удаленный комментарий" + "RESTORE": "Показать удаленный комментарий", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Показать действия", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Показать предыдущие записи (ещё {{showMore}})", "TITLE": "Действия", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "удален", "ADDED": "добавлено", - "US_POINTS": "ПИ очки ({{name}})", - "NEW_ATTACHMENT": "новое вложение", - "DELETED_ATTACHMENT": "удаленное вложение", - "UPDATED_ATTACHMENT": "обновлено приложение {{filename}}", - "DELETED_CUSTOM_ATTRIBUTE": "удалить атрибут", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Сделано {size, plural, one{изменение} other{# изменений}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "да", "NO": "нет", @@ -1071,6 +1093,7 @@ "TAGS": "тэги", "ATTACHMENTS": "Вложения", "IS_DEPRECATED": "рекомендовано", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "порядок", "BACKLOG_ORDER": "порядок списка задач", "SPRINT_ORDER": "порядок спринтов", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Задача {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Статус: {{taskStatus }}. Описание: {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "Задача", "LINK_TASKBOARD": "Панель задач", "TITLE_LINK_TASKBOARD": "Перейти к панели задач", "PLACEHOLDER_SUBJECT": "Укажите новое название задачи", @@ -1247,7 +1270,7 @@ "PAGE_TITLE": "Запросы - {{projectName}}", "PAGE_DESCRIPTION": "Панель запросов проекта {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Запросы", - "SECTION_NAME": "Issue", + "SECTION_NAME": "Запрос", "ACTION_NEW_ISSUE": "+НОВЫЙ ЗАПРОС", "ACTION_PROMOTE_TO_US": "Повысить до пользовательской истории", "PLACEHOLDER_FILTER_NAME": "Введите название фильтра и нажмите \"ввод\"", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Удалить вики страницу", "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Ссылки", - "ACTION_ADD_LINK": "Добавить ссылку" + "ACTION_ADD_LINK": "Добавить ссылку", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "раз
отредактировано", "LAST_EDIT": "последняя
правка", "LAST_MODIFICATION": "последнее изменение" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/locales/taiga/locale-sv.json b/app/locales/taiga/locale-sv.json index f860f7a4..54261c4b 100644 --- a/app/locales/taiga/locale-sv.json +++ b/app/locales/taiga/locale-sv.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "Visa användarhistorier", "ADD_USER_STORIES": "Lägg till användarhistorier", "MODIFY_USER_STORIES": "Modifiera användarhistorier", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Ta bort användarhistorier" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Visa uppgifter", "ADD_TASKS": "Lägg till uppgifter", "MODIFY_TASKS": "Modifiera uppgifter", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Ta bort uppgift" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Visa ärenden", "ADD_ISSUES": "Lägg till ärenden", "MODIFY_ISSUES": "Modifiera ärenden", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Ta bort uppgifter" }, "WIKI": { @@ -558,7 +561,7 @@ "ACTION_ADD": "Lägg till ny {{objName}}" }, "PROJECT_VALUES_TAGS": { - "TITLE": "Tags", + "TITLE": "Etiketter", "SUBTITLE": "View and edit the color of your user stories", "EMPTY": "Currently there are no tags", "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" @@ -689,7 +692,7 @@ "SEVERITIES": "Allvarsgrad", "TYPES": "Typ", "CUSTOM_FIELDS": "Anpassade fält", - "TAGS": "Tags" + "TAGS": "Etiketter" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Projektprofil" @@ -981,7 +984,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Användarhistorier {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. avslutad{{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} av {{userStoryTotalTasks}} tasks closed). Poäng: {{userStoryPoints}}. Beskrivning: {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "Användarhistorie", "LINK_TASKBOARD": "Uppgiftstavla", "TITLE_LINK_TASKBOARD": "Gå till uppgiftstavlan", "TOTAL_POINTS": "totalpoäng", @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Kommentar raderad av {{user}} den {{date}}", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Kommentarer", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Kommentarer", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Skriv en ny kommentar här", "SHOW_DELETED": "Visa raderade kommentarer", "HIDE_DELETED": "Dölj raderade kommentarer", "DELETE": "Ta bort kommentar", - "RESTORE": "Hämta tillbaka tidigare kommentarer" + "RESTORE": "Hämta tillbaka tidigare kommentarer", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Visa aktiviteter", "DATETIME": "YYYY-MM-DD HH:mm", "SHOW_MORE": "+ Visa tidigare poster ({{showMore}} more)", "TITLE": "Aktiviteter", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "borttaget", "ADDED": "lagt till", - "US_POINTS": "US-poäng ({{name}})", - "NEW_ATTACHMENT": "ny bilaga", - "DELETED_ATTACHMENT": "ta bort bifogad fil", - "UPDATED_ATTACHMENT": "uppdaterad bilaga {{filename}}", - "DELETED_CUSTOM_ATTRIBUTE": "raderad anpassad atribut", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Gjorde {size, plural, one{one change} other{# changes}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "ja", "NO": "nej", @@ -1071,6 +1093,7 @@ "TAGS": "etiketter", "ATTACHMENTS": "bilagor", "IS_DEPRECATED": "undviks", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "sortera", "BACKLOG_ORDER": "sortera inkorgen", "SPRINT_ORDER": "sortera sprintar", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Uppgift {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{taskStatus }}. Beskrivning: {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "Uppgift", "LINK_TASKBOARD": "Uppgiftstavla", "TITLE_LINK_TASKBOARD": "Gå till uppgiftstavlan", "PLACEHOLDER_SUBJECT": "Skriv in den nya uppgiftens titel", @@ -1247,7 +1270,7 @@ "PAGE_TITLE": "Ärenden - {{projectName}}", "PAGE_DESCRIPTION": "Ärenden som listas för projektet {{projectName}}: {{projectDescription}}", "LIST_SECTION_NAME": "Ärenden", - "SECTION_NAME": "Issue", + "SECTION_NAME": "ärende", "ACTION_NEW_ISSUE": "+ NYTT ÄRENDE", "ACTION_PROMOTE_TO_US": "Flytta till användarhistorie", "PLACEHOLDER_FILTER_NAME": "Skriv filternamnet och tryck på ", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Ta bort Wiki-sida", "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Länkar", - "ACTION_ADD_LINK": "Lägg till länk" + "ACTION_ADD_LINK": "Lägg till länk", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "gånger
ändrad", "LAST_EDIT": "senaste
ändring", "LAST_MODIFICATION": "senast modifierad" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/locales/taiga/locale-tr.json b/app/locales/taiga/locale-tr.json index 7733a4db..db8c7d20 100644 --- a/app/locales/taiga/locale-tr.json +++ b/app/locales/taiga/locale-tr.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "Kullanıcı hikayelerini gör", "ADD_USER_STORIES": "Kullanıcı hikayeleri ekle", "MODIFY_USER_STORIES": "Kullanıcı hikayelerini değiştir", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "Kullanıcı hikayelerini sil" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "Görevler gör", "ADD_TASKS": "Görevleri ekle", "MODIFY_TASKS": "Görevleri değiştir", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "Görevleri sil" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "Sorunları gör", "ADD_ISSUES": "Sorunları ekle", "MODIFY_ISSUES": "Sorunları değiştir", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "Sorunları sil" }, "WIKI": { @@ -558,7 +561,7 @@ "ACTION_ADD": "Ekle yeni {{objName}}" }, "PROJECT_VALUES_TAGS": { - "TITLE": "Tags", + "TITLE": "Etiketler ", "SUBTITLE": "View and edit the color of your user stories", "EMPTY": "Currently there are no tags", "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" @@ -689,7 +692,7 @@ "SEVERITIES": "Önem Dereceleri", "TYPES": "Tipler", "CUSTOM_FIELDS": "Özel alanlar", - "TAGS": "Tags" + "TAGS": "Etiketler " }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "Proje Profili" @@ -981,7 +984,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - Kullanıcı Hikayesi {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Durum: {{userStoryStatus }}. Tamamlanan {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Puanlar: {{userStoryPoints}}. Tanım: {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "Kullanıcı hikayesi", "LINK_TASKBOARD": "Görev Panosu", "TITLE_LINK_TASKBOARD": "Görev panosuna git", "TOTAL_POINTS": "toplam puanlar", @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "Yorum {{date}} tarihinde {{user}} tarafından silindi", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "Yorumlar", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "Yorum Yap", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Buraya yeni bir yorum yazın", "SHOW_DELETED": "Silinmiş yorumları göster", "HIDE_DELETED": "Silinmiş yorumu gizle", "DELETE": "Yorumu sil", - "RESTORE": "Yorumu geri yükle" + "RESTORE": "Yorumu geri yükle", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "Eylemleri göster", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Önceki girdileri göster ({{showMore}} daha fazla)", "TITLE": "Aktivite", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "silindi", "ADDED": "eklendi", - "US_POINTS": "KH puanları ({{name}})", - "NEW_ATTACHMENT": "yeni ek", - "DELETED_ATTACHMENT": "ek sil", - "UPDATED_ATTACHMENT": "güncellenmiş ek {{filename}}", - "DELETED_CUSTOM_ATTRIBUTE": "silinmiş özel öznitelik", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Yapılan {size, plural, one{tek değişiklik} other{# değişiklikler}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "evet", "NO": "hayır", @@ -1071,6 +1093,7 @@ "TAGS": "etiketler", "ATTACHMENTS": "ekler", "IS_DEPRECATED": "kaldırıldı", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "sıra", "BACKLOG_ORDER": "havuz sıralaması", "SPRINT_ORDER": "koşu sırası", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - Görev {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Durum: {{taskStatus }}. Tanım: {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "Görev", "LINK_TASKBOARD": "Görev Panosu", "TITLE_LINK_TASKBOARD": "Görev panosuna git", "PLACEHOLDER_SUBJECT": "Yeni görev konusunu gir", @@ -1247,7 +1270,7 @@ "PAGE_TITLE": "Sorunlar - {{projectName}}", "PAGE_DESCRIPTION": "{{projectName}} projesinin sorun listesi paneli: {{projectDescription}}", "LIST_SECTION_NAME": "Sorunlar", - "SECTION_NAME": "Issue", + "SECTION_NAME": "Sorun", "ACTION_NEW_ISSUE": "+ YENİ SORUN", "ACTION_PROMOTE_TO_US": "Kullanıcı Hikayesine Terfi Ettir", "PLACEHOLDER_FILTER_NAME": "Filtre adı yazın ve enter a basın", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Wiki Sayfası Sil", "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "Bağlantılar", - "ACTION_ADD_LINK": "Bağlantı ekle" + "ACTION_ADD_LINK": "Bağlantı ekle", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "kere
düzenlendi", "LAST_EDIT": "son
düzenleme", "LAST_MODIFICATION": "son düzenleme" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { diff --git a/app/locales/taiga/locale-zh-hant.json b/app/locales/taiga/locale-zh-hant.json index d1ca1d19..ae5b83d1 100644 --- a/app/locales/taiga/locale-zh-hant.json +++ b/app/locales/taiga/locale-zh-hant.json @@ -244,6 +244,7 @@ "VIEW_USER_STORIES": "檢視使用者故事", "ADD_USER_STORIES": "新增使用者故事", "MODIFY_USER_STORIES": "修正使用者故事", + "COMMENT_USER_STORIES": "Comment user stories", "DELETE_USER_STORIES": "刪除使用者故事" }, "TASKS": { @@ -251,6 +252,7 @@ "VIEW_TASKS": "檢視任務 ", "ADD_TASKS": "新增任務 ", "MODIFY_TASKS": "修正任務 ", + "COMMENT_TASKS": "Comment tasks", "DELETE_TASKS": "刪除任務" }, "ISSUES": { @@ -258,6 +260,7 @@ "VIEW_ISSUES": "檢視問題 ", "ADD_ISSUES": "新增問題 ", "MODIFY_ISSUES": "修正議題 ", + "COMMENT_ISSUES": "Comment issues", "DELETE_ISSUES": "刪除問題 " }, "WIKI": { @@ -558,7 +561,7 @@ "ACTION_ADD": "新增{{objName}}" }, "PROJECT_VALUES_TAGS": { - "TITLE": "Tags", + "TITLE": "標籤", "SUBTITLE": "View and edit the color of your user stories", "EMPTY": "Currently there are no tags", "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" @@ -689,7 +692,7 @@ "SEVERITIES": "急迫性", "TYPES": "類型", "CUSTOM_FIELDS": "客製化欄位", - "TAGS": "Tags" + "TAGS": "標籤" }, "SUBMENU_PROJECT_PROFILE": { "TITLE": "專案檔案" @@ -981,7 +984,7 @@ "US": { "PAGE_TITLE": "{{userStorySubject}} - 使用者故事 {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "狀態: {{userStoryStatus }}.已完成 {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). 點數: {{userStoryPoints}}. 描述: {{userStoryDescription}}", - "SECTION_NAME": "User story", + "SECTION_NAME": "使用者故事", "LINK_TASKBOARD": "任務板", "TITLE_LINK_TASKBOARD": "到任務板去", "TOTAL_POINTS": "所有點數", @@ -1018,28 +1021,47 @@ } }, "COMMENTS": { - "DELETED_INFO": "評論被 {{user}} 於 {{date}}刪除 ", + "DELETED_INFO": "Comment deleted by {{user}}", "TITLE": "評論", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", "COMMENT": "評論", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "在此輸入一個新的評論", "SHOW_DELETED": "顯示遭刪除的評論 ", "HIDE_DELETED": "隱藏已刪除之評論 ", "DELETE": "刪除評論 ", - "RESTORE": "恢復原評論 " + "RESTORE": "恢復原評論 ", + "HISTORY": { + "TITLE": "Activity" + } }, "ACTIVITY": { "SHOW_ACTIVITY": "顯示動態", "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ 顯示過去條目 ({{showMore}} more)", "TITLE": "動態", + "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "已移除", "ADDED": "已加入", - "US_POINTS": "使用者故事點數({{name}})", - "NEW_ATTACHMENT": "新附件", - "DELETED_ATTACHMENT": "已刪除附件", - "UPDATED_ATTACHMENT": "更新附件 {{filename}}", - "DELETED_CUSTOM_ATTRIBUTE": "刪除客製屬性", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "使 {size, plural, one{更改} other{變化}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team Requirement", + "CLIENT_REQUIREMENT": "Client Requirement", + "BLOCKED": "Blocked", "VALUES": { "YES": "yes", "NO": "no", @@ -1071,6 +1093,7 @@ "TAGS": "標籤", "ATTACHMENTS": "附件", "IS_DEPRECATED": "被棄用", + "IS_NOT_DEPRECATED": "is not deprecated", "ORDER": "次序", "BACKLOG_ORDER": "待辦任務先後次序", "SPRINT_ORDER": "衝刺任務次序", @@ -1198,7 +1221,7 @@ "TASK": { "PAGE_TITLE": "{{taskSubject}} - 任務 {{taskRef}} - {{projectName}}", "PAGE_DESCRIPTION": "狀態: {{taskStatus }}.描述: {{taskDescription}}", - "SECTION_NAME": "Task", + "SECTION_NAME": "任務", "LINK_TASKBOARD": "任務板", "TITLE_LINK_TASKBOARD": "到任務板去", "PLACEHOLDER_SUBJECT": "鍵入新任務主旨", @@ -1247,7 +1270,7 @@ "PAGE_TITLE": "問題 - {{projectName}}", "PAGE_DESCRIPTION": " {{projectName}}的問題清單看版: {{projectDescription}}", "LIST_SECTION_NAME": "問題 ", - "SECTION_NAME": "Issue", + "SECTION_NAME": "問題", "ACTION_NEW_ISSUE": "+ 新問題 ", "ACTION_PROMOTE_TO_US": "提昇到使用者故事", "PLACEHOLDER_FILTER_NAME": "寫入過濾器名稱後按下enter ", @@ -1436,13 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "刪除維基頁", "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { + "HOME": "Main Page", "SECTION_NAME": "連結", - "ACTION_ADD_LINK": "新增連結" + "ACTION_ADD_LINK": "新增連結", + "ALL_PAGES": "All pages" }, "SUMMARY": { "TIMES_EDITED": "次數
編輯 ", "LAST_EDIT": "上次
編輯 ", "LAST_MODIFICATION": "上回修改" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Created", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" } }, "HINTS": { From 250254ed0f893301e8e564cd0e700c4d0f97dbe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 24 Jun 2016 12:32:33 +0200 Subject: [PATCH 087/315] Add project picture to dashboard --- app/modules/home/duties/duty.directive.coffee | 4 +++- app/modules/home/duties/duty.jade | 16 +++++++++++----- app/modules/home/home.service.coffee | 3 +-- app/modules/home/working-on/working-on.jade | 2 ++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/modules/home/duties/duty.directive.coffee b/app/modules/home/duties/duty.directive.coffee index 5ffbf653..798db53c 100644 --- a/app/modules/home/duties/duty.directive.coffee +++ b/app/modules/home/duties/duty.directive.coffee @@ -21,6 +21,7 @@ DutyDirective = (navurls, $translate) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} scope.vm.duty = scope.duty + scope.vm.type = scope.type scope.vm.getDutyType = () -> if scope.vm.duty @@ -34,7 +35,8 @@ DutyDirective = (navurls, $translate) -> return { templateUrl: "home/duties/duty.html" scope: { - "duty": "=tgDuty" + "duty": "=tgDuty", + "type": "@" } link: link } diff --git a/app/modules/home/duties/duty.jade b/app/modules/home/duties/duty.jade index fa3b5411..eb7e2601 100644 --- a/app/modules/home/duties/duty.jade +++ b/app/modules/home/duties/duty.jade @@ -1,27 +1,33 @@ a.list-itemtype-ticket( href="{{ ::vm.duty.get('url') }}" title="{{ ::duty.get('subject') }}" - ng-class="{'blocked': vm.duty.get('is_blocked'), 'blocked-project': vm.duty.get('blockedProject')}" + ng-class="{'blocked': vm.duty.get('is_blocked'), 'blocked-project': vm.duty.getIn(['project', 'blocked_code'])}" ) - div.list-itemtype-avatar(ng-if="::vm.duty.get('assigned_to_extra_info')") + div.list-itemtype-avatar(ng-if="vm.type == 'working-on'") img( + tg-project-logo-small-src="::vm.duty.get('project')" + title="{{ ::vm.duty.getIn(['project', 'name']) }}" + ) + div.list-itemtype-avatar(ng-if="vm.type == 'watching'") + img( + ng-if="vm.duty.get('assigned_to_extra_info')" ng-src="{{ ::vm.duty.get('assigned_to_extra_info').get('photo') }}" title="{{ ::vm.duty.get('assigned_to_extra_info').get('full_name_display') }}" ) - div.list-itemtype-avatar(ng-if="::!vm.duty.get('assigned_to_extra_info')") img( + ng-if="!vm.duty.get('assigned_to_extra_info')" src="/#{v}/images/unnamed.png" title="{{'ACTIVITY.VALUES.UNASSIGNED' | translate}}" ) div.list-itemtype-ticket-data p - span.ticket-project {{ ::vm.duty.get('projectName')}} + span.ticket-project {{ ::vm.duty.getIn(['project', 'name']) }} span.ticket-type {{ ::vm.getDutyType() }} span.ticket-status(ng-style="{'color': vm.duty.get('status_extra_info').get('color')}") {{ ::vm.duty.get('status_extra_info').get('name') }} tg-svg( - ng-if="vm.duty.get('blockedProject')", + ng-if="vm.duty.getIn(['project', 'blocked_code'])" svg-icon="icon-blocked-project", svg-title-translate="PROJECT.BLOCKED_PROJECT.BLOCKED" ) diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee index fe068447..74bb9e5f 100644 --- a/app/modules/home/home.service.coffee +++ b/app/modules/home/home.service.coffee @@ -40,8 +40,7 @@ class HomeService extends taiga.Service url = @navurls.resolve("project-#{objType}-detail", ctx) duty = duty.set('url', url) - duty = duty.set('projectName', project.get('name')) - duty = duty.set('blockedProject', project.get('blocked_code')) + duty = duty.set('project', project) duty = duty.set("_name", objType) return duty diff --git a/app/modules/home/working-on/working-on.jade b/app/modules/home/working-on/working-on.jade index 22b032de..dc001bd1 100644 --- a/app/modules/home/working-on/working-on.jade +++ b/app/modules/home/working-on/working-on.jade @@ -5,6 +5,7 @@ section.working-on-container .working-on(ng-if="vm.assignedTo.size") .duty-single( tg-duty="duty" + type="working-on" tg-repeat="duty in vm.assignedTo" ) .working-on-empty(ng-if="vm.assignedTo != undefined && vm.assignedTo.size === 0") @@ -18,6 +19,7 @@ section.watching-container .watching(ng-if="vm.watching.size") .duty-single( tg-duty="duty" + type="watching" tg-repeat="duty in vm.watching" ng-class="{'blocked': duty.is_blocked}" ) From 1a07a887b536cce3516fd0c7a80053cb7f742188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Wed, 29 Jun 2016 18:30:39 +0200 Subject: [PATCH 088/315] Show server errors on memberships add --- app/coffee/modules/admin/lightboxes.coffee | 9 ++++++++- app/partials/admin/lightbox-add-members.jade | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/admin/lightboxes.coffee b/app/coffee/modules/admin/lightboxes.coffee index 4ff79aec..9802d72a 100644 --- a/app/coffee/modules/admin/lightboxes.coffee +++ b/app/coffee/modules/admin/lightboxes.coffee @@ -99,7 +99,14 @@ class LightboxAddMembersController _onErrorInvite: (response) -> @.submitInvites = false - @.form.setErrors(response.data) + errors = {} + _.each response.data.bulk_memberships, (value, index) => + if value.email + errors["email-#{index}"] = value.email[0] + if value.role + errors["role-#{index}"] = value.role[0] + + @.form.setErrors(errors) if response.data._error_message @confirm.notify("error", response.data._error_message) diff --git a/app/partials/admin/lightbox-add-members.jade b/app/partials/admin/lightbox-add-members.jade index dbdc66b4..9b8caa73 100644 --- a/app/partials/admin/lightbox-add-members.jade +++ b/app/partials/admin/lightbox-add-members.jade @@ -10,6 +10,7 @@ tg-lightbox-close required placeholder="{{'LIGHTBOX.CREATE_MEMBER.PLACEHOLDER_TYPE_EMAIL' | translate}}" data-required="true" + name="email-{{$index}}" data-type="email" ng-model="member.email" ) @@ -17,12 +18,14 @@ tg-lightbox-close ng-if="!$first" type="email" placeholder="{{'LIGHTBOX.CREATE_MEMBER.PLACEHOLDER_TYPE_EMAIL' | translate}}" + name="email-{{$index}}" data-type="email" ng-model="member.email" ) fieldset select( ng-if="vm.project" + name="role-{{$index}}" ng-model="member.role_id" ng-options="role.id as role.name for role in vm.project.roles" ) From 3294f6833b3ba580347979555120a99aa4e1145e Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 4 Jul 2016 13:38:34 +0200 Subject: [PATCH 089/315] fix #2369 - confirm cancel in wiki --- app/coffee/modules/wiki/main.coffee | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/coffee/modules/wiki/main.coffee b/app/coffee/modules/wiki/main.coffee index e2bf7749..6b89edaf 100644 --- a/app/coffee/modules/wiki/main.coffee +++ b/app/coffee/modules/wiki/main.coffee @@ -239,9 +239,12 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $ cancelEdition = -> return if not $model.$modelValue.id - $model.$modelValue.revert() - - switchToReadMode() + title = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_TITLE") + message = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_MESSAGE") + $confirm.ask(title, null, message).then (askResponse) -> + $model.$modelValue.revert() + switchToReadMode() + askResponse.finish() getSelectedText = -> if $window.getSelection From dc078a534c9cfd2964b0db1438e36791b73f11cd Mon Sep 17 00:00:00 2001 From: Juanfran Date: Mon, 4 Jul 2016 13:53:10 +0200 Subject: [PATCH 090/315] fix home.service spec --- app/modules/home/home.service.spec.coffee | 31 ++++++++--------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee index 41b162ff..f1ef832f 100644 --- a/app/modules/home/home.service.spec.coffee +++ b/app/modules/home/home.service.spec.coffee @@ -73,11 +73,14 @@ describe "tgHome", -> it "get work in progress by user", (done) -> userId = 3 + project1 = {id: 1, name: "fake1", slug: "project-1"} + project2 = {id: 2, name: "fake2", slug: "project-2"} + mocks.projectsService.getProjectsByUserId .withArgs(userId) .resolve(Immutable.fromJS([ - {id: 1, name: "fake1", slug: "project-1"}, - {id: 2, name: "fake2", slug: "project-2"} + project1, + project2 ])) mocks.resources.userstories.listInAllProjects @@ -125,28 +128,22 @@ describe "tgHome", -> userStories: [{ id: 1, ref: 1, - project: '1', url: '/testing-project/us/1', - projectName: 'fake1', - blockedProject: undefined, + project: project1, _name: 'userstories' }] tasks: [{ id: 2, ref: 2, - project: '1', + project: project1, url: '/testing-project/tasks/1', - projectName: 'fake1', - blockedProject: undefined, _name: 'tasks' }] issues: [{ id: 3, ref: 3, - project: '1', url: '/testing-project/issues/1', - projectName: 'fake1', - blockedProject: undefined, + project: project1, _name: 'issues' }] } @@ -154,28 +151,22 @@ describe "tgHome", -> userStories: [{ id: 1, ref: 1, - project: '1', url: '/testing-project/us/1', - projectName: 'fake1', - blockedProject: undefined, + project: project1, _name: 'userstories' }] tasks: [{ id: 2, ref: 2, - project: '1', url: '/testing-project/tasks/1', - projectName: 'fake1', - blockedProject: undefined, + project: project1, _name: 'tasks' }] issues: [{ id: 3, ref: 3, - project: '1', url: '/testing-project/issues/1', - projectName: 'fake1', - blockedProject: undefined, + project: project1, _name: 'issues' }] } From 8a24afa99764775e42ab4d9762d8b0fad409521f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 23 Jun 2016 12:49:27 +0200 Subject: [PATCH 091/315] Wiki nav redesign --- app/coffee/modules/wiki/nav.coffee | 69 ++++++++-------- app/locales/taiga/locale-en.json | 6 +- app/partials/wiki/wiki-list.jade | 10 +-- app/partials/wiki/wiki-nav.jade | 87 ++++++++++---------- app/partials/wiki/wiki.jade | 16 ++-- app/styles/modules/wiki/wiki-nav.scss | 112 +++++++++++++++++--------- 6 files changed, 165 insertions(+), 135 deletions(-) diff --git a/app/coffee/modules/wiki/nav.coffee b/app/coffee/modules/wiki/nav.coffee index 53fc3d3d..40c7bbd7 100644 --- a/app/coffee/modules/wiki/nav.coffee +++ b/app/coffee/modules/wiki/nav.coffee @@ -39,48 +39,15 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $analytics, $loading, $t $compile, $translate) -> template = $template.get("wiki/wiki-nav.html", true) - linkDragAndDrop = ($scope, $el, $attrs) -> - oldParentScope = null - newParentScope = null - itemEl = null - tdom = $el.find(".sortable") - - addWikiLinkPermission = $scope.project.my_permissions.indexOf("add_wiki_link") > -1 - - if addWikiLinkPermission - drake = dragula([tdom[0]], { - direction: 'vertical', - copySortSource: false, - copy: false, - mirrorContainer: tdom[0], - moves: (item) -> return $(item).is('li') - }) - - drake.on 'dragend', (item) -> - itemEl = $(item) - item = itemEl.scope().link - itemIndex = itemEl.index() - $scope.$emit("wiki:links:move", item, itemIndex) - - scroll = autoScroll(window, { - margin: 20, - pixels: 30, - scrollWhenOutside: true, - autoScroll: () -> - return this.down && drake.dragging; - }) - - $scope.$on "$destroy", -> - $el.off() - if addWikiLinkPermission - drake.destroy() - linkWikiLinks = ($scope, $el, $attrs) -> $ctrl = $el.controller() if not $attrs.ngModel? return $log.error "WikiNavDirective: no ng-model attr is defined" + addWikiLinkPermission = $scope.project.my_permissions.indexOf("add_wiki_link") > -1 + drake = null + render = (wikiLinks) -> addWikiLinkPermission = $scope.project.my_permissions.indexOf("add_wiki_link") > -1 deleteWikiLinkPermission = $scope.project.my_permissions.indexOf("delete_wiki_link") > -1 @@ -95,8 +62,37 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $analytics, $loading, $t html = $compile(html)($scope) $el.off() + if addWikiLinkPermission and drake + drake.destroy() + $el.html(html) + if addWikiLinkPermission + itemEl = null + tdom = $el.find(".sortable") + + drake = dragula([tdom[0]], { + direction: 'vertical', + copySortSource: false, + copy: false, + mirrorContainer: tdom[0], + moves: (item) -> return $(item).is('li') + }) + + drake.on 'dragend', (item) -> + itemEl = $(item) + item = itemEl.scope().link + itemIndex = itemEl.index() + $scope.$emit("wiki:links:move", item, itemIndex) + + scroll = autoScroll(window, { + margin: 20, + pixels: 30, + scrollWhenOutside: true, + autoScroll: () -> + return this.down && drake.dragging; + }) + $el.on "click", ".add-button", (event) -> event.preventDefault() $el.find(".new").removeClass("hidden") @@ -171,7 +167,6 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $analytics, $loading, $t link = ($scope, $el, $attrs) -> linkWikiLinks($scope, $el, $attrs) - linkDragAndDrop($scope, $el, $attrs) $scope.$on "$destroy", -> $el.off() diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 649b5508..83b897de 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "Links", - "ACTION_ADD_LINK": "Add link", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "times
edited", diff --git a/app/partials/wiki/wiki-list.jade b/app/partials/wiki/wiki-list.jade index 284b57e9..413e694d 100644 --- a/app/partials/wiki/wiki-list.jade +++ b/app/partials/wiki/wiki-list.jade @@ -5,11 +5,11 @@ div.wrapper( ng-init="section='wiki'" ) tg-project-menu - sidebar.menu-secondary.extrabar(ng-if="linksVisible") - section.wiki-nav( - tg-wiki-nav - ng-model="wikiLinks" - ) + sidebar.menu-secondary.extrabar.wiki-nav( + ng-if="linksVisible" + tg-wiki-nav + ng-model="wikiLinks" + ) section.main.wiki header h1 diff --git a/app/partials/wiki/wiki-nav.jade b/app/partials/wiki/wiki-nav.jade index 20b9c680..93aa3e0d 100644 --- a/app/partials/wiki/wiki-nav.jade +++ b/app/partials/wiki/wiki-nav.jade @@ -1,56 +1,57 @@ header - h1(translate="WIKI.NAVIGATION.SECTION_NAME") + h1.title(translate="WIKI.NAVIGATION.SECTION_NAME") -nav - ul - li.wiki-link - a.link-title( - href="" - tg-nav="project-wiki-list:project=project.slug" - translate="WIKI.NAVIGATION.ALL_PAGES" - ) - - li.wiki-link - a.link-title( - href="" - tg-nav="project-wiki:project=project.slug" - translate="WIKI.NAVIGATION.HOME" - ) - - ul.sortable - li.wiki-link( - ng-repeat="link in wikiLinks" - data-id!="{{ $index }}" - tg-bind-scope - tg-class-permission="{'is-sortable': 'add_wiki_link'}" +ul.wiki-link-container + li.wiki-link.fixed-link + a.link-title( + href="" + tg-nav="project-wiki:project=project.slug" + translate="WIKI.NAVIGATION.HOME" ) - <% if (addWikiLinkPermission) { %> - tg-svg.dragger(svg-icon="icon-drag") - <% } %> - a.link-title(title!="{{ link.title }}", href!="{{ link.url }}") {{ link.title }} - <% if (deleteWikiLinkPermission) { %> - a.js-delete-link.remove-wiki-page(title!="{{'WIKI.DELETE_LINK_TITLE' | translate}}") - tg-svg(svg-icon="icon-trash") - <% } %> +ul.sortable.wiki-link-container + li.wiki-link( + ng-repeat="link in wikiLinks" + data-id!="{{ $index }}" + tg-bind-scope + tg-class-permission="{'is-sortable': 'add_wiki_link'}" + ) + <% if (addWikiLinkPermission) { %> + tg-svg.dragger(svg-icon="icon-drag") + <% } %> + a.link-title(title!="{{ link.title }}", href!="{{ link.url }}") {{ link.title }} - input.hidden( - type="text" - placeholder="{{'COMMON.FIELDS.NAME' | translate}}" - value!="{{ link.title }}" - ) + <% if (deleteWikiLinkPermission) { %> + a.js-delete-link.remove-wiki-page(title!="{{'WIKI.DELETE_LINK_TITLE' | translate}}") + tg-svg(svg-icon="icon-trash") + <% } %> - li.new.hidden - input( - type="text" - placeholder="{{'COMMON.FIELDS.NAME' | translate}}" - ) + input.hidden( + type="text" + placeholder="{{'COMMON.FIELDS.NAME' | translate}}" + value!="{{ link.title }}" + ) + +ul.sortable.wiki-link-container + li.new.hidden + input( + type="text" + placeholder="{{'COMMON.FIELDS.NAME' | translate}}" + ) <% if (addWikiLinkPermission) { %> -a( +a.add-button( href="" title="{{'WIKI.NAVIGATION.ACTION_ADD_LINK' | translate}}" - class="add-button button-gray" ) + tg-svg(svg-icon="icon-add") span(translate="WIKI.NAVIGATION.ACTION_ADD_LINK") <% } %> + +ul.wiki-link-container.wiki-all-links + li.wiki-link.fixed-link + a.link-title( + href="" + tg-nav="project-wiki-list:project=project.slug" + translate="WIKI.NAVIGATION.ALL_PAGES" + ) diff --git a/app/partials/wiki/wiki.jade b/app/partials/wiki/wiki.jade index 4435b495..ab482e0d 100644 --- a/app/partials/wiki/wiki.jade +++ b/app/partials/wiki/wiki.jade @@ -1,13 +1,15 @@ doctype html -div.wrapper(ng-controller="WikiDetailController as ctrl", - ng-init="section='wiki'") +div.wrapper( + ng-controller="WikiDetailController as ctrl" + ng-init="section='wiki'" +) tg-project-menu - sidebar.menu-secondary.extrabar(ng-if="linksVisible") - section.wiki-nav( - tg-wiki-nav - ng-model="wikiLinks" - ) + sidebar.menu-secondary.extrabar.wiki-nav( + ng-if="linksVisible" + tg-wiki-nav + ng-model="wikiLinks" + ) section.main.wiki header h1 diff --git a/app/styles/modules/wiki/wiki-nav.scss b/app/styles/modules/wiki/wiki-nav.scss index d7778923..ca0a521d 100644 --- a/app/styles/modules/wiki/wiki-nav.scss +++ b/app/styles/modules/wiki/wiki-nav.scss @@ -1,34 +1,97 @@ +.wiki-nav { + padding: 0; + width: 240px; + .title { + @include font-size(larger); + padding: 2rem 1rem 0 2rem; + } + .add-button { + align-items: center; + display: flex; + padding: 1rem 1rem 1rem 2rem; + text-transform: uppercase; + vertical-align: middle; + &:hover { + svg { + background: $primary-light; + } + } + svg { + @include svg-size(1.25rem); + background: $gray-light; + border-radius: 2px; + fill: $white; + margin-right: .5rem; + padding: .25rem; + transition: background .2s linear; + } + } + .wiki-link-container { + margin: 0; + &.wiki-all-links { + border-top: 1px solid $gray-light; + } + } + input[type="text"] { + background: $whitish; + color: $grayer; + margin: 1rem 1rem 1rem 2rem; + width: 80%; + @include placeholder { + color: $gray-light; + } + } + .loading { + padding: 1rem; + text-align: center; + } +} .wiki-link { - @include font-type(text); align-items: center; - border-bottom: 1px solid $gray-light; + border-bottom: 1px solid $whitish; display: flex; justify-content: space-between; - padding: 1rem 0; - text-transform: uppercase; + margin-left: 2rem; + padding-right: 1rem; + position: relative; &:hover { .remove-wiki-page { cursor: pointer; opacity: 1; transition: opacity .2s linear; - transition-delay: .2s; + transition-delay: .1s; } .dragger { cursor: move; - fill: $gray-light; opacity: 1; transition: opacity .2s linear; - transition-delay: .2s; + transition-delay: .1s; } } + &.gu-mirror { + border-bottom: 0; + } + &.fixed-link { + @include font-size(large); + text-transform: uppercase; + } &.is-sortable { cursor: move; } + .link-title { + cursor: pointer; + display: block; + flex-grow: 1; + padding: 1rem 0; + } .dragger { - margin-right: .5rem; + fill: $gray-light; + left: -1rem; opacity: 0; + position: absolute; + top: 1rem; svg { - @include svg-size(.9rem); + @include svg-size(.7rem); } } .remove-wiki-page { @@ -39,35 +102,4 @@ } } } - .link-title { - cursor: pointer; - flex-grow: 1; - } - .icon-trash { - fill: $gray-light; - } -} - -.wiki-nav { - .add-button { - color: $white; - display: block; - margin-bottom: .5rem; - text-align: center; - } - input[type="text"] { - @include font-type(text); - @include font-size(medium); - background: $grayer; - color: $whitish; - @include placeholder { - color: $gray-light; - } - } - .loading { - margin: 0; - padding: 8px; - text-align: center; - width: 100%; - } } From e488fac8d1b6c1e77b47b2585f65ab4db10c0186 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 6 Jul 2016 12:12:20 +0200 Subject: [PATCH 092/315] hide singup form invitation page if publicRegisterEnabled is false --- app/coffee/modules/auth.coffee | 5 +++-- app/partials/auth/invitation.jade | 2 +- app/partials/includes/modules/invitation-register-form.jade | 4 ++-- app/styles/layout/invitation.scss | 6 ++++++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/coffee/modules/auth.coffee b/app/coffee/modules/auth.coffee index b2c6a4f5..a2e1df0c 100644 --- a/app/coffee/modules/auth.coffee +++ b/app/coffee/modules/auth.coffee @@ -472,13 +472,14 @@ module.directive("tgChangePasswordFromRecovery", ["$tgAuth", "$tgConfirm", "$tgL ## Invitation ############################################################################# -InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics, $translate) -> +InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics, $translate, config) -> link = ($scope, $el, $attrs) -> token = $params.token promise = $auth.getInvitation(token) promise.then (invitation) -> $scope.invitation = invitation + $scope.publicRegisterEnabled = config.get("publicRegisterEnabled") promise.then null, (response) -> $location.path($navUrls.resolve("login")) @@ -549,7 +550,7 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics return {link:link} module.directive("tgInvitation", ["$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams", - "$tgNavUrls", "$tgAnalytics", "$translate", InvitationDirective]) + "$tgNavUrls", "$tgAnalytics", "$translate", "$tgConfig", InvitationDirective]) ############################################################################# diff --git a/app/partials/auth/invitation.jade b/app/partials/auth/invitation.jade index caf9041c..6288b606 100644 --- a/app/partials/auth/invitation.jade +++ b/app/partials/auth/invitation.jade @@ -10,6 +10,6 @@ div.wrapper p(translate="AUTH.INVITED_YOU") p.project-name(tg-bo-bind="invitation.project_name") - div.invitation-form + div.invitation-form(ng-class="{'public-register-disabled': !publicRegisterEnabled}") include ../includes/modules/invitation-login-form include ../includes/modules/invitation-register-form diff --git a/app/partials/includes/modules/invitation-register-form.jade b/app/partials/includes/modules/invitation-register-form.jade index b5921ffa..c943db40 100644 --- a/app/partials/includes/modules/invitation-register-form.jade +++ b/app/partials/includes/modules/invitation-register-form.jade @@ -1,9 +1,9 @@ -form.register-form +form.register-form(ng-if="publicRegisterEnabled") p.form-header(translate="REGISTER_FORM.TITLE") fieldset input( type="text" - autocorrect="off" + autocorrect="off" autocapitalize="none" name="username" ng-model="dataRegister.username" diff --git a/app/styles/layout/invitation.scss b/app/styles/layout/invitation.scss index f8d1ea37..cbdf801c 100644 --- a/app/styles/layout/invitation.scss +++ b/app/styles/layout/invitation.scss @@ -101,4 +101,10 @@ .login-form { border-right: 1px solid rgba($white, .3); } + .public-register-disabled { + width: 400px; + .login-form { + border-right: 0; + } + } } From 8de4db9e377c8176de1f9f08f1fdaea65f07c1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 7 Jun 2016 10:17:47 +0200 Subject: [PATCH 093/315] Show history in all wiki pages --- CHANGELOG.md | 1 + app/coffee/app.coffee | 1 + app/coffee/modules/resources.coffee | 2 +- app/coffee/modules/wiki/main.coffee | 7 +- .../history/history-tabs/history-tabs.jade | 2 +- .../history/history-tabs/history-tabs.scss | 21 ----- .../history-templates/history-templates.scss | 5 +- app/modules/history/history/history.scss | 38 +------- app/modules/resources/resources.coffee | 3 +- .../resources/wiki-resource.service.coffee | 42 +++++++++ .../history-attachments.jade | 42 +++++++++ .../wiki-history-diff.directive.coffee | 31 +++++++ .../wiki/history/wiki-history-diff.jade | 16 ++++ .../wiki-history-entry.directive.coffee | 34 +++++++ .../wiki/history/wiki-history-entry.jade | 16 ++++ .../history/wiki-history.controller.coffee | 38 ++++++++ .../wiki-history.controller.spec.coffee | 62 +++++++++++++ .../history/wiki-history.directive.coffee | 41 +++++++++ app/modules/wiki/history/wiki-history.jade | 12 +++ .../wiki/history/wiki-history.module.coffee | 20 ++++ app/modules/wiki/history/wiki-history.scss | 3 + .../wiki/history/wiki-history.service.coffee | 51 ++++++++++ .../history/wiki-history.service.spec.coffee | 92 +++++++++++++++++++ app/partials/wiki/wiki-summary.jade | 9 ++ app/partials/wiki/wiki.jade | 14 +-- app/styles/components/history.scss | 32 +++++++ app/styles/layout/wiki.scss | 19 +--- app/styles/modules/wiki/wiki-summary.scss | 12 +++ 28 files changed, 578 insertions(+), 88 deletions(-) create mode 100644 app/modules/resources/wiki-resource.service.coffee create mode 100644 app/modules/wiki/history/history-templates/history-attachments.jade create mode 100644 app/modules/wiki/history/wiki-history-diff.directive.coffee create mode 100644 app/modules/wiki/history/wiki-history-diff.jade create mode 100644 app/modules/wiki/history/wiki-history-entry.directive.coffee create mode 100644 app/modules/wiki/history/wiki-history-entry.jade create mode 100644 app/modules/wiki/history/wiki-history.controller.coffee create mode 100644 app/modules/wiki/history/wiki-history.controller.spec.coffee create mode 100644 app/modules/wiki/history/wiki-history.directive.coffee create mode 100644 app/modules/wiki/history/wiki-history.jade create mode 100644 app/modules/wiki/history/wiki-history.module.coffee create mode 100644 app/modules/wiki/history/wiki-history.scss create mode 100644 app/modules/wiki/history/wiki-history.service.coffee create mode 100644 app/modules/wiki/history/wiki-history.service.spec.coffee create mode 100644 app/styles/components/history.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index c460a6f1..0c840d86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Wiki: - Drag & Drop ordering for wiki links. - Add a list of all wiki pages + - Add Wiki history ### Misc - Lots of small and not so small bugfixes. diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index c212486b..a1840ebf 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -779,6 +779,7 @@ modules = [ "taigaExternalApps", "taigaDiscover", "taigaHistory", + "taigaWikiHistory", # template cache "templates", diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index c32a9f56..8d30fb0f 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -131,7 +131,7 @@ urls = { "history/us": "/history/userstory" "history/issue": "/history/issue" "history/task": "/history/task" - "history/wiki": "/history/wiki" + "history/wiki": "/history/wiki/%s" # Attachments "attachments/us": "/userstories/attachments" diff --git a/app/coffee/modules/wiki/main.coffee b/app/coffee/modules/wiki/main.coffee index 6b89edaf..f10479b9 100644 --- a/app/coffee/modules/wiki/main.coffee +++ b/app/coffee/modules/wiki/main.coffee @@ -218,7 +218,8 @@ module.directive("tgWikiSummary", ["$log", "$tgTemplate", "$compile", "$translat ## Editable Wiki Content Directive ############################################################################# -EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $analytics, $qqueue, $translate) -> +EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $analytics, $qqueue, $translate, + $wikiHistoryService) -> link = ($scope, $el, $attrs, $model) -> isEditable = -> return $scope.project.my_permissions.indexOf("modify_wiki_page") != -1 @@ -260,6 +261,7 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $ $model.$setViewValue wikiPage.clone() + $wikiHistoryService.loadHistoryEntries() $confirm.notify("success") switchToReadMode() @@ -336,4 +338,5 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $ } module.directive("tgEditableWikiContent", ["$window", "$document", "$tgRepo", "$tgConfirm", "$tgLoading", - "$tgAnalytics", "$tgQqueue", "$translate", EditableWikiContentDirective]) + "$tgAnalytics", "$tgQqueue", "$translate", "tgWikiHistoryService", + EditableWikiContentDirective]) diff --git a/app/modules/history/history-tabs/history-tabs.jade b/app/modules/history/history-tabs/history-tabs.jade index 8b0c175a..deb458de 100644 --- a/app/modules/history/history-tabs/history-tabs.jade +++ b/app/modules/history/history-tabs/history-tabs.jade @@ -1,7 +1,7 @@ nav.history-tabs a.history-tab.e2e-comments-tab( href="" - title="Comments" + title="{{COMMENTS.COMMENT}}" ng-click="onActiveComments()" ng-class="{active: activeTab}" translate="COMMENTS.COMMENTS_COUNT" diff --git a/app/modules/history/history-tabs/history-tabs.scss b/app/modules/history/history-tabs/history-tabs.scss index 48cb8471..9c7bef56 100644 --- a/app/modules/history/history-tabs/history-tabs.scss +++ b/app/modules/history/history-tabs/history-tabs.scss @@ -1,25 +1,4 @@ .history-tabs { - background: $whitish; - display: flex; - flex-direction: row; - a { - color: $grayer; - display: inline-block; - padding: .75rem 1rem; - &:hover { - color: $primary; - } - } - .history-tab { - @include font-type(bold); - border-bottom: 3px solid transparent; - color: $gray-light; - transition: all .1s linear; - &.active { - border-bottom: 3px solid $grayer; - color: $grayer; - } - } .order-comments { @include font-type(light); margin-left: auto; diff --git a/app/modules/history/history/history-templates/history-templates.scss b/app/modules/history/history/history-templates/history-templates.scss index 69773c26..77d5d7ed 100644 --- a/app/modules/history/history/history-templates/history-templates.scss +++ b/app/modules/history/history/history-templates/history-templates.scss @@ -17,9 +17,12 @@ p { display: inline-block; } - del { + ins { background: lighten(rgba($primary-light, .3), 20%); text-decoration: underline; } + del { + background: rgba($red-light, .3); + } } } diff --git a/app/modules/history/history/history.scss b/app/modules/history/history/history.scss index 840e79f2..48bd6da8 100644 --- a/app/modules/history/history/history.scss +++ b/app/modules/history/history/history.scss @@ -1,10 +1,8 @@ -.activities { - .activity { - align-items: flex-start; - border-bottom: 1px solid $whitish; - display: flex; - padding: 2rem 0; - } +.activity { + align-items: flex-start; + border-bottom: 1px solid $whitish; + display: flex; + padding: 2rem 0; .activity-avatar { flex-basis: 50px; flex-shrink: 0; @@ -20,30 +18,4 @@ .activity-date { color: $gray-light; } - .comment-options { - align-items: center; - align-self: stretch; - display: flex; - flex-basis: 50px; - flex-shrink: 0; - margin-left: 1.5rem; - .comment-option { - cursor: pointer; - opacity: 0; - transition: opacity .2s; - } - .icon-edit { - fill: $gray-light; - margin-right: .5rem; - &:hover { - fill: $gray; - } - } - .icon-trash { - fill: $red-light; - &:hover { - fill: $red; - } - } - } } diff --git a/app/modules/resources/resources.coffee b/app/modules/resources/resources.coffee index e8a4c33a..5fafa0bf 100644 --- a/app/modules/resources/resources.coffee +++ b/app/modules/resources/resources.coffee @@ -26,7 +26,8 @@ services = [ "tgIssuesResource", "tgExternalAppsResource", "tgAttachmentsResource", - "tgStatsResource" + "tgStatsResource", + "tgWikiHistory" ] Resources = ($injector) -> diff --git a/app/modules/resources/wiki-resource.service.coffee b/app/modules/resources/wiki-resource.service.coffee new file mode 100644 index 00000000..6c3a67fe --- /dev/null +++ b/app/modules/resources/wiki-resource.service.coffee @@ -0,0 +1,42 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: wiki-resource.service.coffee +### + +Resource = (urlsService, http) -> + service = {} + + service.getWikiHistory = (wikiId) -> + url = urlsService.resolve("history/wiki", wikiId) + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + return http.get(url, null, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) + + return () -> + return {"wikiHistory": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgWikiHistory", Resource) diff --git a/app/modules/wiki/history/history-templates/history-attachments.jade b/app/modules/wiki/history/history-templates/history-attachments.jade new file mode 100644 index 00000000..b9a9d074 --- /dev/null +++ b/app/modules/wiki/history/history-templates/history-attachments.jade @@ -0,0 +1,42 @@ + +//- New attachment added +.diff-attachments-new( + ng-if="diff.new.length" + ng-repeat="newAttachment in diff.new" +) + span.key(translate="ACTIVITY.NEW_ATTACHMENT") + span.diff {{newAttachment.filename}} + +//- Attachment updated +.diff-attachments-update( + ng-if="diff.changed.length" + ng-repeat="editAttachment in diff.changed" +) + span.key( + translate="ACTIVITY.UPDATED_ATTACHMENT" + translate-values="{filename: editAttachment.filename}" + ) + span.diff(ng-if="editAttachment.changes.is_deprecated") + span( + ng-if="editAttachment.changes.is_deprecated[1] == false" + translate="ACTIVITY.BECAME_UNDEPRECATED" + ) + span( + ng-if="editAttachment.changes.is_deprecated[1] == true" + translate="ACTIVITY.BECAME_DEPRECATED" + ) + span.diff(ng-if="editAttachment.changes.description") + span(ng-if='editAttachment.changes.description[0].length') {{editAttachment.changes.description[0]}} + span(ng-if='!editAttachment.changes.description[0].length') ... + tg-svg( + svg-icon="icon-arrow-right" + ) + span {{editAttachment.changes.description[1]}} + +//- Attachment deleted +.diff-attachments-deleted( + ng-if="diff.deleted.length" + ng-repeat="deletedAttachment in diff.deleted" +) + span.key(translate="ACTIVITY.DELETED_ATTACHMENT") + span.diff {{deletedAttachment.filename}} diff --git a/app/modules/wiki/history/wiki-history-diff.directive.coffee b/app/modules/wiki/history/wiki-history-diff.directive.coffee new file mode 100644 index 00000000..3388d969 --- /dev/null +++ b/app/modules/wiki/history/wiki-history-diff.directive.coffee @@ -0,0 +1,31 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: wiki-history.directive.coffee +### + +module = angular.module('taigaWikiHistory') + +WikiHistoryDiffDirective = () -> + return { + templateUrl:"wiki/history/wiki-history-diff.html", + scope: { + key: "<", + diff: "<" + } + } + +module.directive("tgWikiHistoryDiff", WikiHistoryDiffDirective) diff --git a/app/modules/wiki/history/wiki-history-diff.jade b/app/modules/wiki/history/wiki-history-diff.jade new file mode 100644 index 00000000..4ae07bc0 --- /dev/null +++ b/app/modules/wiki/history/wiki-history-diff.jade @@ -0,0 +1,16 @@ +.diff-status-wrapper( + ng-if="key === 'attachments'" +) + include history-templates/history-attachments + +.diff-status-wrapper( + ng-if="key === 'content_diff'" +) + p.diff( + ng-if="diff[0]" + ng-bind-html="diff[0]" + ) + p.diff( + ng-if="diff[1]" + ng-bind-html="diff[1]" + ) diff --git a/app/modules/wiki/history/wiki-history-entry.directive.coffee b/app/modules/wiki/history/wiki-history-entry.directive.coffee new file mode 100644 index 00000000..7f0905ab --- /dev/null +++ b/app/modules/wiki/history/wiki-history-entry.directive.coffee @@ -0,0 +1,34 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: wiki-history.directive.coffee +### + +module = angular.module('taigaWikiHistory') + +WikiHistoryEntryDirective = () -> + link = (scope, el, attr) -> + scope.singleHistoryEntry = scope.historyEntry.toJS() + + return { + link: link, + templateUrl:"wiki/history/wiki-history-entry.html", + scope: { + historyEntry: "<" + } + } + +module.directive("tgWikiHistoryEntry", WikiHistoryEntryDirective) diff --git a/app/modules/wiki/history/wiki-history-entry.jade b/app/modules/wiki/history/wiki-history-entry.jade new file mode 100644 index 00000000..d8d2633c --- /dev/null +++ b/app/modules/wiki/history/wiki-history-entry.jade @@ -0,0 +1,16 @@ +.activity + img.activity-avatar( + ng-src="{{singleHistoryEntry.user.photo}}" + ng-alt="{{singleHistoryEntry.user.name}}" + ) + .activity-main + .activity-data + span.activity-creator {{singleHistoryEntry.user.name}} + span.activity-date {{singleHistoryEntry.created_at | momentFormat:'DD MMM YYYY HH:mm'}} + + .activity-diff( + ng-repeat="(key, diff) in singleHistoryEntry.values_diff" + tg-wiki-history-diff + key='key' + diff='diff' + ) diff --git a/app/modules/wiki/history/wiki-history.controller.coffee b/app/modules/wiki/history/wiki-history.controller.coffee new file mode 100644 index 00000000..45f7b983 --- /dev/null +++ b/app/modules/wiki/history/wiki-history.controller.coffee @@ -0,0 +1,38 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: wiki-history.controller.coffee +### + +taiga = @.taiga + +module = angular.module("taigaWikiHistory") + +class WikiHistoryController + @.$inject = [ + "tgWikiHistoryService" + ] + + constructor: (@wikiHistoryService) -> + taiga.defineImmutableProperty @, 'historyEntries', () => return @wikiHistoryService.historyEntries + + initializeHistoryEntries: (wikiId) -> + if wikiId + @wikiHistoryService.setWikiId(wikiId) + + @wikiHistoryService.loadHistoryEntries() + +module.controller("WikiHistoryCtrl", WikiHistoryController) diff --git a/app/modules/wiki/history/wiki-history.controller.spec.coffee b/app/modules/wiki/history/wiki-history.controller.spec.coffee new file mode 100644 index 00000000..d0a0fbc8 --- /dev/null +++ b/app/modules/wiki/history/wiki-history.controller.spec.coffee @@ -0,0 +1,62 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: wiki-history.controller.spec.coffee +### + +describe "WikiHistorySection", -> + provide = null + controller = null + mocks = {} + + _mockTgWikiHistoryService = () -> + mocks.tgWikiHistoryService = { + setWikiId: sinon.stub(), + loadHistoryEntries: sinon.stub() + } + + provide.value "tgWikiHistoryService", mocks.tgWikiHistoryService + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgWikiHistoryService() + return null + + beforeEach -> + module "taigaWikiHistory" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "initialize histori entries with id", -> + wikiId = 42 + + historyCtrl = controller "WikiHistoryCtrl" + historyCtrl.initializeHistoryEntries(wikiId) + + expect(mocks.tgWikiHistoryService.setWikiId).to.be.calledOnce + expect(mocks.tgWikiHistoryService.setWikiId).to.be.calledWith(wikiId) + expect(mocks.tgWikiHistoryService.loadHistoryEntries).to.be.calledOnce + + it "initialize history entries without id", -> + historyCtrl = controller "WikiHistoryCtrl" + historyCtrl.initializeHistoryEntries() + + expect(mocks.tgWikiHistoryService.setWikiId).to.not.be.calledOnce + expect(mocks.tgWikiHistoryService.loadHistoryEntries).to.be.calledOnce diff --git a/app/modules/wiki/history/wiki-history.directive.coffee b/app/modules/wiki/history/wiki-history.directive.coffee new file mode 100644 index 00000000..34d96c31 --- /dev/null +++ b/app/modules/wiki/history/wiki-history.directive.coffee @@ -0,0 +1,41 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: wiki-history.directive.coffee +### + +bindOnce = @.taiga.bindOnce + +module = angular.module('taigaWikiHistory') + + +WikiHistoryDirective = () -> + link = (scope, el, attrs, ctrl) -> + bindOnce scope, 'vm.wikiId', (value) -> + ctrl.initializeHistoryEntries(value) + + return { + scope: {}, + bindToController: { + wikiId: "<" + } + controller: "WikiHistoryCtrl", + controllerAs: "vm", + templateUrl:"wiki/history/wiki-history.html", + link: link + } + +module.directive("tgWikiHistory", WikiHistoryDirective) diff --git a/app/modules/wiki/history/wiki-history.jade b/app/modules/wiki/history/wiki-history.jade new file mode 100644 index 00000000..57715241 --- /dev/null +++ b/app/modules/wiki/history/wiki-history.jade @@ -0,0 +1,12 @@ +nav.history-tabs(ng-if="vm.historyEntries") + a.history-tab.active( + href="" + title="{{ACTIVITY.TITLE}}" + translate="ACTIVITY.TITLE" + ) + +section.wiki-history(ng-if="vm.historyEntries") + tg-wiki-history-entry.wiki-history-entry( + tg-repeat="historyEntry in vm.historyEntries" + history-entry="historyEntry" + ) diff --git a/app/modules/wiki/history/wiki-history.module.coffee b/app/modules/wiki/history/wiki-history.module.coffee new file mode 100644 index 00000000..4ed5d8d0 --- /dev/null +++ b/app/modules/wiki/history/wiki-history.module.coffee @@ -0,0 +1,20 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: wiki-history.module.coffee +### + +angular.module("taigaWikiHistory", []) diff --git a/app/modules/wiki/history/wiki-history.scss b/app/modules/wiki/history/wiki-history.scss new file mode 100644 index 00000000..3e0f5a02 --- /dev/null +++ b/app/modules/wiki/history/wiki-history.scss @@ -0,0 +1,3 @@ +.wiki-history { + margin-bottom: 2rem; +} diff --git a/app/modules/wiki/history/wiki-history.service.coffee b/app/modules/wiki/history/wiki-history.service.coffee new file mode 100644 index 00000000..6b3107ad --- /dev/null +++ b/app/modules/wiki/history/wiki-history.service.coffee @@ -0,0 +1,51 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: wiki-history.service.coffee +### + +taiga = @.taiga + +module = angular.module('taigaWikiHistory') + +class WikiHistoryService extends taiga.Service + @.$inject = [ + "tgResources" + "tgXhrErrorService" + ] + + constructor: (@rs, @xhrError) -> + @._wikiId = null + @._historyEntries = Immutable.List() + + taiga.defineImmutableProperty @, "wikiId", () => return @._wikiId + taiga.defineImmutableProperty @, "historyEntries", () => return @._historyEntries + + setWikiId: (wikiId) -> + @._wikiId = wikiId + @._historyEntries = Immutable.List() + + loadHistoryEntries: () -> + return if not @._wikiId + + return @rs.wikiHistory.getWikiHistory(@._wikiId) + .then (historyEntries) => + if historyEntries.size + @._historyEntries = historyEntries.reverse() + .catch (xhr) => + @xhrError.response(xhr) + _ +module.service("tgWikiHistoryService", WikiHistoryService) diff --git a/app/modules/wiki/history/wiki-history.service.spec.coffee b/app/modules/wiki/history/wiki-history.service.spec.coffee new file mode 100644 index 00000000..26410361 --- /dev/null +++ b/app/modules/wiki/history/wiki-history.service.spec.coffee @@ -0,0 +1,92 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: wiki-history.service.spec.coffee +### + + +describe "tgWikiHistoryService", -> + $provide = null + wikiHistoryService = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + wikiHistory: { + getWikiHistory: sinon.stub() + } + } + $provide.value("tgResources", mocks.tgResources) + + _mockXhrErrorService = () -> + mocks.xhrErrorService = { + response: sinon.stub() + } + + $provide.value "tgXhrErrorService", mocks.xhrErrorService + + _mocks = -> + module (_$provide_) -> + $provide = _$provide_ + + _mockTgResources() + _mockXhrErrorService() + + return null + + _inject = -> + inject (_tgWikiHistoryService_) -> + wikiHistoryService = _tgWikiHistoryService_ + + _setup = -> + _mocks() + _inject() + + beforeEach -> + module "taigaWikiHistory" + + _setup() + + it "populate history entries", (done) -> + wikiId = 42 + historyEntries = Immutable.List([ + {id: 1, name: 'history entrie 1'}, + {id: 2, name: 'history entrie 2'}, + {id: 3, name: 'history entrie 3'}, + ]) + + mocks.tgResources.wikiHistory.getWikiHistory.withArgs(wikiId).promise().resolve(historyEntries) + + wikiHistoryService.setWikiId(wikiId) + expect(wikiHistoryService.wikiId).to.be.equal(wikiId) + + expect(wikiHistoryService.historyEntries.size).to.be.equal(0) + wikiHistoryService.loadHistoryEntries().then () -> + expect(wikiHistoryService.historyEntries.size).to.be.equal(3) + done() + + it "reset history entries if wikiId change", () -> + wikiId = 42 + + wikiHistoryService._historyEntries = Immutable.List([ + {id: 1, name: 'history entrie 1'}, + {id: 2, name: 'history entrie 2'}, + {id: 3, name: 'history entrie 3'}, + ]) + + expect(wikiHistoryService.historyEntries.size).to.be.equal(3) + wikiHistoryService.setWikiId(wikiId) + expect(wikiHistoryService.historyEntries.size).to.be.equal(0) diff --git a/app/partials/wiki/wiki-summary.jade b/app/partials/wiki/wiki-summary.jade index d10e398f..e1708e7b 100644 --- a/app/partials/wiki/wiki-summary.jade +++ b/app/partials/wiki/wiki-summary.jade @@ -12,3 +12,12 @@ .wiki-times-edited span.number <%- totalEditions %> span.description(translate="WIKI.SUMMARY.TIMES_EDITED") + +tg-svg.remove( + tg-check-permission="delete_wiki_page" + title="{{'WIKI.REMOVE' | translate}}" + ng-click="ctrl.delete()" + svg-icon="icon-trash" + ng-if="wiki.id" + +) diff --git a/app/partials/wiki/wiki.jade b/app/partials/wiki/wiki.jade index ab482e0d..cde221a6 100644 --- a/app/partials/wiki/wiki.jade +++ b/app/partials/wiki/wiki.jade @@ -16,15 +16,14 @@ div.wrapper( span(tg-bo-bind="project.name") span.green(translate="PROJECT.SECTION.WIKI") - h2.wiki-title(ng-bind='wikiTitle') section.wiki-content( tg-editable-wysiwyg, tg-editable-wiki-content, ng-model="wiki" ) - - div.summary.wiki-summary( + + .summary.wiki-summary( tg-wiki-summary ng-model="wiki" ng-if="wiki.id" @@ -38,12 +37,7 @@ div.wrapper( edit-permission = "modify_wiki_page" ) - a.remove( - href="" - ng-click="ctrl.delete()" + tg-wiki-history( ng-if="wiki.id" - title="{{'WIKI.REMOVE' | translate}}" - tg-check-permission="delete_wiki_page" + wiki-id="wiki.id" ) - tg-svg(svg-icon="icon-trash") - span(translate="WIKI.REMOVE") diff --git a/app/styles/components/history.scss b/app/styles/components/history.scss new file mode 100644 index 00000000..d949df35 --- /dev/null +++ b/app/styles/components/history.scss @@ -0,0 +1,32 @@ +.history-tabs { + background: $whitish; + display: flex; + flex-direction: row; + a { + display: inline-block; + padding: .75rem 1rem; + &:hover { + color: $primary; + } + } + .history-tab { + @include font-type(bold); + border-bottom: 3px solid transparent; + color: $gray-light; + transition: all .1s linear; + &.active { + border-bottom: 3px solid $grayer; + color: $grayer; + } + } + .order-comments { + @include font-type(light); + color: $grayer; + margin-left: auto; + transition: none; + } + .icon-arrow-up, + .icon-arrow-down { + @include svg-size(.75rem); + } +} diff --git a/app/styles/layout/wiki.scss b/app/styles/layout/wiki.scss index 5b5c8874..3930ca41 100644 --- a/app/styles/layout/wiki.scss +++ b/app/styles/layout/wiki.scss @@ -1,4 +1,5 @@ .wiki { + max-width: 1024px; .wysiwyg { margin-bottom: 0; } @@ -8,28 +9,10 @@ margin-bottom: 0; padding: 1rem; } - .remove { - @include font-size(small); - color: $gray-light; - cursor: pointer; - &:hover { - color: $red-light; - transition: color .1s linear; - .icon { - fill: $red-light; - transition: fill .1s linear; - } - } - .icon { - color: $gray-light; - margin-right: .2rem; - } - } } .wiki-content { @include font-size(large); - max-width: 1024px; position: relative; &.editable { &:hover { diff --git a/app/styles/modules/wiki/wiki-summary.scss b/app/styles/modules/wiki/wiki-summary.scss index 30e7d128..8abcf6bf 100644 --- a/app/styles/modules/wiki/wiki-summary.scss +++ b/app/styles/modules/wiki/wiki-summary.scss @@ -27,4 +27,16 @@ @include font-size(large); white-space: nowrap; } + .remove { + fill: $gray-light; + margin-left: auto; + transition: fill .1s linear; + &:hover { + cursor: pointer; + fill: $red-light; + } + svg { + @include svg-size(1.5rem); + } + } } From 716124edb8dc8a4b7847e911eba27a2ee5b6f6e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 6 Jul 2016 17:13:39 +0200 Subject: [PATCH 094/315] Remove css lint from default task --- gulpfile.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 0f1fab49..7c5b788f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -354,6 +354,14 @@ gulp.task("compile-themes", function(cb) { }); gulp.task("styles", function(cb) { + return runSequence("scss-lint", + "sass-compile", + ["app-css", "vendor-css"], + "main-css", + cb); +}); + +gulp.task("styles-lint", function(cb) { return runSequence("scss-lint", "sass-compile", "css-lint-app", @@ -589,7 +597,7 @@ gulp.task("watch", function() { livereload.listen(); gulp.watch(paths.jade, ["jade-watch"]); - gulp.watch(paths.sass_watch, ["styles"]); + gulp.watch(paths.sass_watch, ["styles-lint"]); gulp.watch(paths.styles_dependencies, ["styles-dependencies"]); gulp.watch(paths.svg, ["copy-svg"]); gulp.watch(paths.coffee, ["app-watch"]); From dae8d420565610ea0b71dcdd3884d8116d049560 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 8 Jul 2016 07:59:19 +0200 Subject: [PATCH 095/315] upgrade dom-autoscroller --- app/js/dom-autoscroller.js | 214 ++++++++++++++++++++++++++++++++----- package.json | 2 +- 2 files changed, 191 insertions(+), 25 deletions(-) diff --git a/app/js/dom-autoscroller.js b/app/js/dom-autoscroller.js index 08831141..535eb2a6 100644 --- a/app/js/dom-autoscroller.js +++ b/app/js/dom-autoscroller.js @@ -464,13 +464,70 @@ }; + var createPointCB = function createPointCB(object){ + // A persistent object (as opposed to returned object) is used to save memory + // This is good to prevent layout thrashing, or for games, and such + + // NOTE + // This uses IE fixes which should be OK to remove some day. :) + // Some speed will be gained by removal of these. + + // pointCB should be saved in a variable on return + // This allows the usage of element.removeEventListener + + return function pointCB(event){ + + event = event || window.event; // IE-ism + object.target = event.target || event.srcElement || event.originalTarget; + object.element = this; + object.type = event.type; + + // Support touch + // http://www.creativebloq.com/javascript/make-your-site-work-touch-devices-51411644 + + if(event.targetTouches){ + object.x = event.targetTouches[0].clientX; + object.y = event.targetTouches[0].clientY; + object.pageX = event.pageX; + object.pageY = event.pageY; + }else{ + + // If pageX/Y aren't available and clientX/Y are, + // calculate pageX/Y - logic taken from jQuery. + // (This is to support old IE) + // NOTE Hopefully this can be removed soon. + + if (event.pageX === null && event.clientX !== null) { + var eventDoc = (event.target && event.target.ownerDocument) || document; + var doc = eventDoc.documentElement; + var body = eventDoc.body; + + object.pageX = event.clientX + + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - + (doc && doc.clientLeft || body && body.clientLeft || 0); + object.pageY = event.clientY + + (doc && doc.scrollTop || body && body.scrollTop || 0) - + (doc && doc.clientTop || body && body.clientTop || 0 ); + }else{ + object.pageX = event.pageX; + object.pageY = event.pageY; + } + + // pageX, and pageY change with page scroll + // so we're not going to use those for x, and y. + // NOTE Most browsers also alias clientX/Y with x/y + // so that's something to consider down the road. + + object.x = event.clientX; + object.y = event.clientY; + } + + }; + + //NOTE Remember accessibility, Aria roles, and labels. + }; // Autscroller - - function AutoScrollerFactory(element, options){ - return new AutoScroller(element, options); - } - function AutoScroller(elements, options){ var self = this, pixels = 2; options = options || {}; @@ -479,7 +536,10 @@ this.scrolling = false; this.scrollWhenOutside = options.scrollWhenOutside || false; - this.point = pointer(elements); + var point = {}, pointCB = createPointCB(point), down = false; + + window.addEventListener('mousemove', pointCB, false); + window.addEventListener('touchmove', pointCB, false); if(!isNaN(options.pixels)){ pixels = options.pixels; @@ -494,12 +554,30 @@ } this.destroy = function() { - this.point.destroy(); + window.removeEventListener('mousemove', pointCB, false); + window.removeEventListener('touchmove', pointCB, false); + window.removeEventListener('mousedown', onDown, false); + window.removeEventListener('touchstart', onDown, false); + window.removeEventListener('mouseup', onUp, false); + window.removeEventListener('touchend', onUp, false); }; + var hasWindow = null, temp = []; + for(var i=0; i rect.bottom - self.margin){ + }else if(point.y > rect.bottom - self.margin){ autoScrollV(el, 1, rect); } - if(self.point.x < rect.left + self.margin){ + if(point.x < rect.left + self.margin){ autoScrollH(el, -1, rect); - }else if(self.point.x > rect.right - self.margin){ + }else if(point.x > rect.right - self.margin){ autoScrollH(el, 1, rect); } - }); + } + + function autoScrollV(el, amount, rect){ - //if(!self.down) return; + if(!self.autoScroll()) return; - if(!self.scrollWhenOutside && self.point.outside(el)) return; + if(!self.scrollWhenOutside && !inside(point, el, rect)) return; + if(el === window){ window.scrollTo(el.pageXOffset, el.pageYOffset + amount); }else{ + el.scrollTop = el.scrollTop + amount; } setTimeout(function(){ - if(self.point.y < rect.top + self.margin){ + if(point.y < rect.top + self.margin){ autoScrollV(el, amount, rect); - }else if(self.point.y > rect.bottom - self.margin){ + }else if(point.y > rect.bottom - self.margin){ autoScrollV(el, amount, rect); } }, self.interval); } function autoScrollH(el, amount, rect){ - //if(!self.down) return; + if(!self.autoScroll()) return; - if(!self.scrollWhenOutside && self.point.outside(el)) return; + if(!self.scrollWhenOutside && !inside(point, el, rect)) return; + if(el === window){ window.scrollTo(el.pageXOffset + amount, el.pageYOffset); }else{ @@ -559,9 +694,9 @@ } setTimeout(function(){ - if(self.point.x < rect.left + self.margin){ + if(point.x < rect.left + self.margin){ autoScrollH(el, amount, rect); - }else if(self.point.x > rect.right - self.margin){ + }else if(point.x > rect.right - self.margin){ autoScrollH(el, amount, rect); } }, self.interval); @@ -569,5 +704,36 @@ } + function getRect(el){ + if(el === window){ + return { + top: 0, + left: 0, + right: window.innerWidth, + bottom: window.innerHeight, + width: window.innerWidth, + height: window.innerHeight + }; + + }else{ + try{ + return el.getBoundingClientRect(); + }catch(e){ + throw new TypeError("Can't call getBoundingClientRect on "+el); + } + + } + } + + function inside(point, el, rect){ + rect = rect || getRect(el); + return (point.y > rect.top && point.y < rect.bottom && + point.x > rect.left && point.x < rect.right); + } + + function AutoScrollerFactory(element, options){ + return new AutoScroller(element, options); + } + window.autoScroll = AutoScrollerFactory; }()); diff --git a/package.json b/package.json index 4b3e17c0..2849caf4 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,6 @@ ], "dependencies": { "awesomplete": "^1.0.0", - "dom-autoscroller": "^1.2.3" + "dom-autoscroller": "^1.3.1" } } From 9289807b5ad64ab862e9a1b3b4e6d146a0bc1958 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 28 Jun 2016 09:09:45 +0200 Subject: [PATCH 096/315] Improving API performance --- app/coffee/modules/resources/projects.coffee | 2 +- app/modules/resources/projects-resource.service.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/coffee/modules/resources/projects.coffee b/app/coffee/modules/resources/projects.coffee index b4822b05..b93912f2 100644 --- a/app/coffee/modules/resources/projects.coffee +++ b/app/coffee/modules/resources/projects.coffee @@ -40,7 +40,7 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) -> return $repo.queryMany("projects") service.listByMember = (memberId) -> - params = {"member": memberId, "order_by": "memberships__user_order"} + params = {"member": memberId, "order_by": "user_order"} return $repo.queryMany("projects", params) service.templates = -> diff --git a/app/modules/resources/projects-resource.service.coffee b/app/modules/resources/projects-resource.service.coffee index 226534ff..9df02c3a 100644 --- a/app/modules/resources/projects-resource.service.coffee +++ b/app/modules/resources/projects-resource.service.coffee @@ -54,7 +54,7 @@ Resource = (urlsService, http, paginateResponseService) -> "x-disable-pagination": "1" } - params = {"member": userId, "order_by": "memberships__user_order"} + params = {"member": userId, "order_by": "user_order"} return http.get(url, params, httpOptions) .then (result) -> From c7ea967997624794073594d20e30047b83f9ba33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 21 Jul 2016 23:53:28 +0200 Subject: [PATCH 097/315] [i18n] Update locales --- app/locales/taiga/locale-ca.json | 16 +-- app/locales/taiga/locale-de.json | 16 +-- app/locales/taiga/locale-es.json | 158 +++++++++++++------------- app/locales/taiga/locale-fi.json | 16 +-- app/locales/taiga/locale-fr.json | 16 +-- app/locales/taiga/locale-it.json | 16 +-- app/locales/taiga/locale-nl.json | 16 +-- app/locales/taiga/locale-pl.json | 74 ++++++------ app/locales/taiga/locale-pt-br.json | 132 ++++++++++----------- app/locales/taiga/locale-ru.json | 16 +-- app/locales/taiga/locale-sv.json | 16 +-- app/locales/taiga/locale-tr.json | 16 +-- app/locales/taiga/locale-zh-hant.json | 16 +-- 13 files changed, 262 insertions(+), 262 deletions(-) diff --git a/app/locales/taiga/locale-ca.json b/app/locales/taiga/locale-ca.json index 1e076009..dfa78e74 100644 --- a/app/locales/taiga/locale-ca.json +++ b/app/locales/taiga/locale-ca.json @@ -1037,7 +1037,7 @@ "DELETE": "Esborrar comentari", "RESTORE": "Resturar comentari.", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Activitat" } }, "ACTIVITY": { @@ -1059,9 +1059,9 @@ "SIZE_CHANGE": "Fet {size, plural, one{un canvi} other{# changes}}", "BECAME_DEPRECATED": "became deprecated", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "Requeriment d'equip", + "CLIENT_REQUIREMENT": "Requeriment de client", + "BLOCKED": "Bloquejat", "VALUES": { "YES": "si", "NO": "no", @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "Enllaços", - "ACTION_ADD_LINK": "Afegir link", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "voltes
editat", @@ -1473,7 +1473,7 @@ "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", - "CREATED": "Created", + "CREATED": "Creat", "MODIFIED": "Modified", "CREATOR": "Creator", "LAST_MODIFIER": "Last modifier" diff --git a/app/locales/taiga/locale-de.json b/app/locales/taiga/locale-de.json index 9b6b3aa7..5ec14239 100644 --- a/app/locales/taiga/locale-de.json +++ b/app/locales/taiga/locale-de.json @@ -1037,7 +1037,7 @@ "DELETE": "Kommentar löschen", "RESTORE": "Kommentar wiederherstellen", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Aktivität" } }, "ACTIVITY": { @@ -1059,9 +1059,9 @@ "SIZE_CHANGE": "Machte {size, plural, one{eine Änderung} other{# Änderungen}}", "BECAME_DEPRECATED": "became deprecated", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "Team Anforderung", + "CLIENT_REQUIREMENT": "Kundenanforderung", + "BLOCKED": "Blockiert", "VALUES": { "YES": "ja", "NO": "nein", @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Entferne Wiki Link", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "Links", - "ACTION_ADD_LINK": "Link hinzufügen", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "mal
bearbeitet", @@ -1473,7 +1473,7 @@ "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", - "CREATED": "Created", + "CREATED": "Erstellt", "MODIFIED": "Modified", "CREATOR": "Creator", "LAST_MODIFIER": "Last modifier" diff --git a/app/locales/taiga/locale-es.json b/app/locales/taiga/locale-es.json index 8e608581..41fdf455 100644 --- a/app/locales/taiga/locale-es.json +++ b/app/locales/taiga/locale-es.json @@ -42,9 +42,9 @@ "CLIENT_REQUIREMENT": "Requerimiento de cliente es un nuevo requisito que no se esperaba y es necesario que forme parte del proyecto.", "TEAM_REQUIREMENT": "Requerimiento del equipo es un nuevo requisito que debe existir en el proyecto pero que no conllevará ningún coste para el cliente.", "OWNER": "Dueño del proyecto", - "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", + "CAPSLOCK_WARNING": "¡Cuidado!. Esta usando mayusculas en un campo sensible a mayusculas", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "¿Seguro que desea cerrar el modo de edición?", - "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Recuerde que si cierra el modo de edicion sin guardar todos los cambios se perderán", "FORM_ERRORS": { "DEFAULT_MESSAGE": "Este valor parece inválido.", "TYPE_EMAIL": "El valor debe ser un email.", @@ -227,8 +227,8 @@ "CODE_BLOCK_SAMPLE_TEXT": "Tu texto aquí...", "PREVIEW_BUTTON": "Previsualizar", "EDIT_BUTTON": "Editar", - "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", - "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP": "Adjunte archivos arrastrando y soltando dentro del area de texto", + "ATTACH_FILE_HELP_SAVE_FIRST": "Si desea guardar adjuntos guarde primero, luego arrastre y suelte los archivos en el area de texto mas arriba", "MARKDOWN_HELP": "Ayuda de sintaxis Markdown" }, "PERMISIONS_CATEGORIES": { @@ -244,7 +244,7 @@ "VIEW_USER_STORIES": "Ver historias de usuario", "ADD_USER_STORIES": "Crear historias de usuario", "MODIFY_USER_STORIES": "Editar historias de usuario", - "COMMENT_USER_STORIES": "Comment user stories", + "COMMENT_USER_STORIES": "Comentar historias de usuario", "DELETE_USER_STORIES": "Borrar historias de usuario" }, "TASKS": { @@ -252,7 +252,7 @@ "VIEW_TASKS": "Ver tareas", "ADD_TASKS": "Crear tareas", "MODIFY_TASKS": "Editar tareas", - "COMMENT_TASKS": "Comment tasks", + "COMMENT_TASKS": "Comentar tareas", "DELETE_TASKS": "Borrar tareas" }, "ISSUES": { @@ -260,7 +260,7 @@ "VIEW_ISSUES": "Ver peticiones", "ADD_ISSUES": "Crear peticiones", "MODIFY_ISSUES": "Editar peticiones", - "COMMENT_ISSUES": "Comment issues", + "COMMENT_ISSUES": "Comentar problemas", "DELETE_ISSUES": "Borrar peticiones" }, "WIKI": { @@ -441,10 +441,10 @@ "DISABLE": "Desactivado", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Gestiona tus historias de usuario para mantener una vista organizada y priorizada de los próximos trabajos que deberás afrontar. ", - "NUMBER_SPRINTS": "Expected number of sprints", - "NUMBER_SPRINTS_HELP": "0 for an undetermined number", - "NUMBER_US_POINTS": "Expected total of story points", - "NUMBER_US_POINTS_HELP": "0 for an undetermined number", + "NUMBER_SPRINTS": "Numero esperado de sprints", + "NUMBER_SPRINTS_HELP": "0 para un numero indeterminado", + "NUMBER_US_POINTS": "Total esperado de puntos historicos", + "NUMBER_US_POINTS_HELP": "0 para un numero indeterminado", "KANBAN": "Kanban", "KANBAN_DESCRIPTION": "Organiza tus proyectos de una manera flexible con este panel.", "ISSUES": "Peticiones", @@ -479,18 +479,18 @@ "CHANGE_LOGO": "Cambia el logo", "ACTION_USE_DEFAULT_LOGO": "Usar imagen por defecto", "MAX_PRIVATE_PROJECTS": "Has alcanzado el número máximo de proyectos privados permitidos por su actual plan", - "MAX_PRIVATE_PROJECTS_MEMBERS": "The maximum number of members for private projects has been exceeded", + "MAX_PRIVATE_PROJECTS_MEMBERS": "El numero máximo de miembros para proyectos privados se ha excedido", "MAX_PUBLIC_PROJECTS": "Desafortunadamente, usted ha alcanzado el número máximo de proyectos públicos permitidos por su plan actual", - "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds your maximum number of members for public projects", + "MAX_PUBLIC_PROJECTS_MEMBERS": "El proyecto excede el numero maximo de usuarios para proyectos publicos", "PROJECT_OWNER": "Dueño del proyecto", "REQUEST_OWNERSHIP": "Solicitar de dueño", - "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Do you want to become the new project owner?", + "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "¿Desea convertirse en el nuevo dueño del proyecto?", "REQUEST_OWNERSHIP_DESC": "Solicitar que el actual project owner {{name}} te transfiera la propiedad de este proyecto a ti.", "REQUEST_OWNERSHIP_BUTTON": "Solicitud", - "REQUEST_OWNERSHIP_SUCCESS": "We'll notify the project owner", + "REQUEST_OWNERSHIP_SUCCESS": "Notificaremos al dueño del proyecto", "CHANGE_OWNER": "Cambiar dueño", "CHANGE_OWNER_SUCCESS_TITLE": "Ok, su solicitud ha sido enviado!", - "CHANGE_OWNER_SUCCESS_DESC": "We will notify you by email if the project ownership request is accepted or declined" + "CHANGE_OWNER_SUCCESS_DESC": "Le notificaremos por correo si la solicitud para ser dueño del proyecto fue aceptada o rechazada" }, "REPORTS": { "TITLE": "Informes", @@ -562,9 +562,9 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Etiquetas", - "SUBTITLE": "View and edit the color of your user stories", - "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "SUBTITLE": "Ver y editar el color de sus historias", + "EMPTY": "Actualmente no hay etiquetas", + "EMPTY_SEARCH": "Parece que no se encontro nada con este criterio de busqueda" }, "ROLES": { "PAGE_TITLE": "Roles - {{projectName}}", @@ -712,14 +712,14 @@ "REJECTED_PROJECT_OWNERNSHIP": "Ok. Nos pondremos en contacto con el propietario actual del proyecto", "ACCEPT": "Aceptar", "REJECT": "Rechazar", - "PROPOSE_OWNERSHIP": "{{owner}}, the current owner of the project {{project}} has asked that you become the new project owner.", + "PROPOSE_OWNERSHIP": "{{owner}}, el dueño del proyecto {{project}} pregunta si es el nuevo dueño del proyecto.", "ADD_COMMENT": "¿Te gustaría añadir un comentario para el project owner?", "UNLIMITED_PROJECTS": "Sin límite", "OWNER_MESSAGE": { - "PRIVATE": "Please remember that you can own up to {{maxProjects}} private projects. You currently own {{currentProjects}} private projects", - "PUBLIC": "Please remember that you can own up to {{maxProjects}} public projects. You currently own {{currentProjects}} public projects" + "PRIVATE": "Por favor recuerde que puede ser dueño de maximo {{maxProjects}} proyectos privados. Usted tiene actualmente {{currentProjects}} proyectos privados bajo su poder", + "PUBLIC": "Pro favor recuerde que solo puede ser dueño de maximo {{maxProjects}} proyectos publicos. Actualmente tiene {{currentProjects}} proyectos publicos bajo su poder" }, - "CANT_BE_OWNED": "At the moment you cannot become an owner of a project of this type. If you would like to become the owner of this project, please contact the administrator so they change your account settings to enable project ownership.", + "CANT_BE_OWNED": "en el momento no puede ser el dueño de un proyecto de este tipo. Si desea ser el dueño de este proyecto, por favor contacte al administrador para cambiar la configuracion que le permita ser dueño del proyecto", "CHANGE_MY_PLAN": "Cambiar mi plan" } }, @@ -784,7 +784,7 @@ "BLOCKED_PROJECT": { "BLOCKED": "Proyecto bloqueado", "THIS_PROJECT_IS_BLOCKED": "Este proyecto esta temporalmente bloqueado", - "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "In order to unblock your projects, contact the administrator." + "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "Para desbloquear sus proyectos, contacte al administrador." }, "STATS": { "PROJECT": "puntos
proyecto", @@ -849,27 +849,27 @@ "ERROR_MAX_SIZE_EXCEEDED": "El fichero '{{fileName}}' ({{fileSize}}) es demasiado pesado para nuestros Oompa Loompas, prueba con uno de menos de ({{maxFileSize}}).", "SYNC_SUCCESS": "Tu proyecto se ha importado con éxito.", "PROJECT_RESTRICTIONS": { - "PROJECT_MEMBERS_DESC": "The project you are trying to import has {{members}} members, unfortunately, your current plan allows for a maximum of {{max_memberships}} members per project. If you would like to increase that limit please contact the administrator.", + "PROJECT_MEMBERS_DESC": "El proyecto que esta tratando de importar tiene {{members}} miembros, desafortunadamente, su plna actual solo le permite un maximo de {{max_memberships}} miembros por proyecto. si desea aumentar este limite por favor contacte al administrador.", "PRIVATE_PROJECTS_SPACE": { "TITLE": "Desafortunadamente, su plan actual no permite a los proyectos privados adicionales", - "DESC": "The project you are trying to import is private. Unfortunately, your current plan does not allow for additional private projects." + "DESC": "El proyecto que trata de importar es privado. Desafortunadamente, su plan actual no le permite adicionar mas proyectos privados" }, "PUBLIC_PROJECTS_SPACE": { - "TITLE": "Unfortunately, your current plan does not allow for additional public projects", + "TITLE": "Desafortunadamente, su plan actual no permite adicionar mas proyectos publicos", "DESC": "El proyecto que estás intento importar es público. Desafortunadamente, tu plan actual no permite proyectos públicos adicionales." }, "PRIVATE_PROJECTS_MEMBERS": { - "TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per private project" + "TITLE": "Su plan actual solo permite un numero maximo de {{max_memberships}} miembros por proyecto privado" }, "PUBLIC_PROJECTS_MEMBERS": { - "TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per public project." + "TITLE": "Su plan actual solo permite un maximo de {{max_memberships}} miembros por proyecto publico." }, "PRIVATE_PROJECTS_SPACE_MEMBERS": { "TITLE": "Desafortunadamente tu plan actual no permite proyectos privados adicionales o un incremento de más de {{max_memberships}} miembros por proyecto privado", "DESC": "El proyecto que estás intentando importar es privado y tiene {{members}} miembros." }, "PUBLIC_PROJECTS_SPACE_MEMBERS": { - "TITLE": "Unfortunately your current plan doesn't allow additional public projects or an increase of more than {{max_memberships}} members per public project", + "TITLE": "Desafortunadamente su plan actual no le permite adicionar proyectos publicos o un aumento de {{max_memberships}} miembros por proyecto publico", "DESC": "El proyecto que estás intentando importar es público y tiene más de {{members}} miembros." } } @@ -903,7 +903,7 @@ "NEWSLETTER_LABEL_TEXT": "No quiero recibir la newsletter nunca más.", "CANCEL": "Volver a los ajustes", "ACCEPT": "Eliminar cuenta", - "BLOCK_PROJECT": "Note that all the projects you own projects will be blocked after you delete your account. If you do want a project blocked, transfer ownership to another member of each project prior to deleting your account.", + "BLOCK_PROJECT": "Recuerde que todos los proyectos de los cuales usted es dueño seran bloqueados despues de eliminar su cuenta. si desea mantener un proyecto bloqueado, transfiera el dominio a otro usuario en cada proyecto antes de eliminar la cuenta.", "SUBTITLE": "Sentimos mucho verte ir. Estaremos aquí por si alguna vez nos consideras de nuevo! :(" }, "DELETE_PROJECT": { @@ -961,24 +961,24 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opcional) Añade un texto personalizado a la invitación. Dile algo encantador a tus nuevos miembros ;-)", "PLACEHOLDER_TYPE_EMAIL": "Escribe un email", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Desafortunadamente, este proyecto no puede tener mas de {{maxMembers}} miembros.
Si desea aumentar este limite, por favor contacte al administrador.", + "LIMIT_USERS_WARNING_MESSAGE": "Desafortunadamente, este proyecto no puede tener mas de {{maxMembers}} miembros." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Por desgracia, este proyecto no puede ser dejado sin dueño", "CURRENT_USER_OWNER": { - "DESC": "You are the current owner of this project. Before leaving, please transfer ownership to someone else.", + "DESC": "Usted es el dueño actual de este proyecto. Antes de salir, tranfiera el dominio de su proyecto a alguien mas.", "BUTTON": "Cambiar el dueño del proyecto" }, "OTHER_USER_OWNER": { - "DESC": "Unfortunately, you can't delete a member who is also the current project owner. First, please assign a new project owner.", + "DESC": "Desafortunadamente, usted no puede eliminar un miembro que es a su vez el dueño actual del proyecto. Primero, por favor asigne un nuevo dueño del proyecto.", "BUTTON": "Solicitud del cambio del dueño del proyecto" } }, "CHANGE_OWNER": { "TITLE": "¿A quién quiere ser el nuevo dueño del proyecto?", "ADD_COMMENT": "Añadir comentario", - "BUTTON": "Ask this project member to become the new project owner" + "BUTTON": "Pregunte a este usuario para convertirlo en el nuero dueño del proyecto" } }, "US": { @@ -1004,15 +1004,15 @@ "NOT_ESTIMATED": "No estimada", "TOTAL_US_POINTS": "Total puntos de historia", "TRIBE": { - "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH": "Publicar como Gig en la Tribu Taiga", "PUBLISH_INFO": "Mas información", - "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", - "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", - "EDIT_LINK": "Edit link", - "CLOSE": "Close", - "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", - "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", - "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + "PUBLISH_TITLE": "Mas informacion para publicar en la Tribu Taiga", + "PUBLISHED_AS_GIG": "Historia publicada como Gig en la Tribu Taiga", + "EDIT_LINK": "Editar link", + "CLOSE": "Cerrar", + "SYNCHRONIZE_LINK": "sincronizar con la Tribu Taiga", + "PUBLISH_MORE_INFO_TITLE": "Necesita a alguien para esta tarea?", + "PUBLISH_MORE_INFO_TEXT": "

SI necesita ayuda con una parte especifica del trabajo puede crear facilmente gigs en la Tribu Taiga y recibir ayuda de todo el mundo. Tendra la habilidad de manejar y controlar su gig disfrutando de una comunidad dispuesta a colaborar.

la Tribu Taiga nacio como una rama de Taiga. Ambas plataformas viven separadas Pero creemos que hay mucho poder al usarlas combinadamente y asi aseguramos que la integracion funciona muy bien.

" }, "FIELDS": { "TEAM_REQUIREMENT": "Requerido por el Equipo", @@ -1021,23 +1021,23 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comment deleted by {{user}}", + "DELETED_INFO": "Comentario borrado por {{user}}", "TITLE": "Comentarios", - "COMMENTS_COUNT": "{{comments}} Comments", - "ORDER": "Order", - "OLDER_FIRST": "Older first", - "RECENT_FIRST": "Recent first", + "COMMENTS_COUNT": "{{comments}} Comentarios", + "ORDER": "Orden", + "OLDER_FIRST": "Mas antiguo primero", + "RECENT_FIRST": "Mas reciente primero", "COMMENT": "Comentar", - "EDIT_COMMENT": "Edit comment", - "EDITED_COMMENT": "Edited:", - "SHOW_HISTORY": "View historic", + "EDIT_COMMENT": "Editar comentario", + "EDITED_COMMENT": "Editado:", + "SHOW_HISTORY": "Ver histórico", "TYPE_NEW_COMMENT": "Escribe un nuevo comentario aquí", "SHOW_DELETED": "Mostrar comentarios eliminados", "HIDE_DELETED": "Ocultar comentarios eliminados", "DELETE": "Borrar comentario", "RESTORE": "Restaurar comentario", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Actividad" } }, "ACTIVITY": { @@ -1045,23 +1045,23 @@ "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Ver entradas anteriores ({{showMore}} más)", "TITLE": "Actividad", - "ACTIVITIES_COUNT": "{{activities}} Activities", + "ACTIVITIES_COUNT": "{{activities}} Actividades", "REMOVED": "borrado", "ADDED": "agregado", - "TAGS_ADDED": "tags added:", - "TAGS_REMOVED": "tags removed:", - "US_POINTS": "{{role}} points", - "NEW_ATTACHMENT": "new attachment:", - "DELETED_ATTACHMENT": "deleted attachment:", - "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", - "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", - "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", + "TAGS_ADDED": "etiquetas añadidas", + "TAGS_REMOVED": "Etiquetas borradas:", + "US_POINTS": "{{role}} puntos", + "NEW_ATTACHMENT": "nuevo adjunto", + "DELETED_ATTACHMENT": "adjunto borrado", + "UPDATED_ATTACHMENT": "actualizar adjunto ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "atributo personalizado creado", + "UPDATED_CUSTOM_ATTRIBUTE": "atributo personalizado actualizado", "SIZE_CHANGE": "{size, plural, one{Un cambio realizado} other{# cambios realizados}}", - "BECAME_DEPRECATED": "became deprecated", - "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "BECAME_DEPRECATED": "esta obsoleto", + "BECAME_UNDEPRECATED": "no esta obsoleto", + "TEAM_REQUIREMENT": "Requerido por el Equipo", + "CLIENT_REQUIREMENT": "Requerido por el Cliente", + "BLOCKED": "Bloqueada", "VALUES": { "YES": "sí", "NO": "no", @@ -1093,7 +1093,7 @@ "TAGS": "etiquetas", "ATTACHMENTS": "adjuntos", "IS_DEPRECATED": "está desactualizado", - "IS_NOT_DEPRECATED": "is not deprecated", + "IS_NOT_DEPRECATED": "No es obsoleto", "ORDER": "orden", "BACKLOG_ORDER": "orden en backlog", "SPRINT_ORDER": "orden en sprint", @@ -1459,24 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Eliminar Página del Wiki", "DELETE_LINK_TITLE": "Eliminar enlace de la Wiki", "NAVIGATION": { - "HOME": "Main Page", - "SECTION_NAME": "Enlaces", - "ACTION_ADD_LINK": "Añadir enlace", - "ALL_PAGES": "All pages" + "HOME": "Principal", + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "veces
editada", "LAST_EDIT": "última
edición", "LAST_MODIFICATION": "ultima modificación" }, - "SECTION_PAGES_LIST": "All pages", + "SECTION_PAGES_LIST": "Todas", "PAGES_LIST_COLUMNS": { - "TITLE": "Title", - "EDITIONS": "Editions", - "CREATED": "Created", - "MODIFIED": "Modified", - "CREATOR": "Creator", - "LAST_MODIFIER": "Last modifier" + "TITLE": "Título", + "EDITIONS": "Ediciones", + "CREATED": "Creado", + "MODIFIED": "Modificado", + "CREATOR": "Creador", + "LAST_MODIFIER": "Ultimo modificador" } }, "HINTS": { diff --git a/app/locales/taiga/locale-fi.json b/app/locales/taiga/locale-fi.json index eab5b6a4..4a7c2af8 100644 --- a/app/locales/taiga/locale-fi.json +++ b/app/locales/taiga/locale-fi.json @@ -1037,7 +1037,7 @@ "DELETE": "Delete comment", "RESTORE": "Palauta kommentti", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Aktiivisuus" } }, "ACTIVITY": { @@ -1059,9 +1059,9 @@ "SIZE_CHANGE": "Tehty {size, plural, one{muutos} other{# muutosta}}", "BECAME_DEPRECATED": "became deprecated", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "Tiimin vaatimus", + "CLIENT_REQUIREMENT": "Asiakkaan vaatimus", + "BLOCKED": "Suljettu", "VALUES": { "YES": "Kyllä", "NO": "ei", @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "Linkit", - "ACTION_ADD_LINK": "Lisää linkki", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "kertaa
muokattu", @@ -1473,7 +1473,7 @@ "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", - "CREATED": "Created", + "CREATED": "Luotu", "MODIFIED": "Modified", "CREATOR": "Creator", "LAST_MODIFIER": "Last modifier" diff --git a/app/locales/taiga/locale-fr.json b/app/locales/taiga/locale-fr.json index 106a5df0..1e5348e7 100644 --- a/app/locales/taiga/locale-fr.json +++ b/app/locales/taiga/locale-fr.json @@ -1037,7 +1037,7 @@ "DELETE": "Supprimer le commentaire", "RESTORE": "Restaurer le commentaire", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Activité" } }, "ACTIVITY": { @@ -1059,9 +1059,9 @@ "SIZE_CHANGE": "A fait {size, plural, one{une modification} other{# modifications}}", "BECAME_DEPRECATED": "became deprecated", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "Besoin projet", + "CLIENT_REQUIREMENT": "Besoin client", + "BLOCKED": "Bloqué", "VALUES": { "YES": "oui", "NO": "no", @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Supprimer un lien Wiki", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "Liens", - "ACTION_ADD_LINK": "Ajouter un lien", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "modifications", @@ -1473,7 +1473,7 @@ "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", - "CREATED": "Created", + "CREATED": "Créé le", "MODIFIED": "Modified", "CREATOR": "Creator", "LAST_MODIFIER": "Last modifier" diff --git a/app/locales/taiga/locale-it.json b/app/locales/taiga/locale-it.json index a4293d59..86db70c8 100644 --- a/app/locales/taiga/locale-it.json +++ b/app/locales/taiga/locale-it.json @@ -1037,7 +1037,7 @@ "DELETE": "Cancella commento", "RESTORE": "Ripristina commento", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Attività" } }, "ACTIVITY": { @@ -1059,9 +1059,9 @@ "SIZE_CHANGE": "Fatto {size, plural, one{un cambiamento} other{# cambiamenti}}", "BECAME_DEPRECATED": "became deprecated", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "Requisito del team", + "CLIENT_REQUIREMENT": "Requisito del client", + "BLOCKED": "Bloccato", "VALUES": { "YES": "si", "NO": "no", @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "Link", - "ACTION_ADD_LINK": "Aggiungi link", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "tempo
modificato", @@ -1473,7 +1473,7 @@ "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", - "CREATED": "Created", + "CREATED": "Creato", "MODIFIED": "Modified", "CREATOR": "Creator", "LAST_MODIFIER": "Last modifier" diff --git a/app/locales/taiga/locale-nl.json b/app/locales/taiga/locale-nl.json index a4c97aba..0caba2ba 100644 --- a/app/locales/taiga/locale-nl.json +++ b/app/locales/taiga/locale-nl.json @@ -1037,7 +1037,7 @@ "DELETE": "Delete comment", "RESTORE": "Opmerking herstellen", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Activiteit" } }, "ACTIVITY": { @@ -1059,9 +1059,9 @@ "SIZE_CHANGE": "{size, plural, one{één verandering} other{# veranderingen}} gemaakt", "BECAME_DEPRECATED": "became deprecated", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "Eisen team", + "CLIENT_REQUIREMENT": "Requirement van de klant", + "BLOCKED": "Geblokkeerd", "VALUES": { "YES": "ja", "NO": "nee", @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "Links", - "ACTION_ADD_LINK": "Link toevoegen", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "keer
bewerkt", @@ -1473,7 +1473,7 @@ "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", - "CREATED": "Created", + "CREATED": "Aangemaakt", "MODIFIED": "Modified", "CREATOR": "Creator", "LAST_MODIFIER": "Last modifier" diff --git a/app/locales/taiga/locale-pl.json b/app/locales/taiga/locale-pl.json index 7353bc6f..8818b746 100644 --- a/app/locales/taiga/locale-pl.json +++ b/app/locales/taiga/locale-pl.json @@ -19,11 +19,11 @@ "TAG_LINE": "Twoje zwinne, wolne, otwartoźródłowe narzędzie do zarządzania projektem", "TAG_LINE_2": "Pokochaj swój projekt!", "BLOCK": "Blokuj", - "BLOCK_TITLE": "Block this item for example if it has a dependency that can not be satisfied", + "BLOCK_TITLE": "Zablokuj to np. jeżeli posiada zależności, które nie mogą być zrealizowane", "BLOCKED": "Zablokowane", "UNBLOCK": "Odblokuj", - "UNBLOCK_TITLE": "Unblock this item", - "BLOCKED_NOTE": "Why is this blocked?", + "UNBLOCK_TITLE": "Odblokuj", + "BLOCKED_NOTE": "Dlaczego jest zabokowane?", "BLOCKED_REASON": "Wyjaśnij powód", "CREATED_BY": "Utworzone przez {{fullDisplayName}}", "FROM": "od", @@ -363,7 +363,7 @@ "HOME": { "PAGE_TITLE": "Strona główna - Taiga", "PAGE_DESCRIPTION": "Główna strona Taiga, z Twoimi głównymi projektami i wszystkimi przypisanymi Tobie i obserwowanymi historyjkami użytkownika, zadaniami i zgłoszeniami.", - "EMPTY_WORKING_ON": "It feels empty, doesn't it? Start working with Taiga and you'll see here the stories, tasks and issues you are working on.", + "EMPTY_WORKING_ON": "Trochę pusto, nieprawdaż? Zacznij pracować z Taiga a tutaj pojawią się historie, zadania i zgłoszenia nad którymi pracujesz.", "EMPTY_WATCHING": "Follow User Stories, Tasks, Issues in your projects and be notified about its changes :)", "EMPTY_PROJECT_LIST": "Nie masz jeszcze żadnych projektów", "WORKING_ON_SECTION": "Pracujesz nad", @@ -384,7 +384,7 @@ "DEPRECATED": "(przestarzały)", "DEPRECATED_FILE": "Przestarzałe?", "ADD": "Dodaj nowy załącznik. {{maxFileSizeMsg}}", - "DROP": "Drop attachments here!", + "DROP": "Upuść załączniki tutaj", "SHOW_DEPRECATED": "+ pokaż przestarzałe załączniki", "HIDE_DEPRECATED": "- ukryj przestarzałe załączniki", "COUNT_DEPRECATED": "({{ counter }} przestarzałych", @@ -418,7 +418,7 @@ "PAGE_TITLE": "Członkostwa - {{projectName}}", "ADD_BUTTON": "+ Nowy członek", "ADD_BUTTON_TITLE": "Dodaj nowego członka", - "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Unfortunately, this project has reached its limit of ({{members}}) allowed members.", + "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Niestety ten projekt osiągnął maksymalną liczbę {{{members}}} dozwolonych użytkowników.", "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "This project has reached its limit of ({{members}}) allowed members. If you would like to increase that limit please contact the administrator." }, "PROJECT_EXPORT": { @@ -442,9 +442,9 @@ "BACKLOG": "Dziennik", "BACKLOG_DESCRIPTION": "Zarządzaj swoimi historyjkami użytkownika aby utrzymać zorganizowany widok i priorytety zadań", "NUMBER_SPRINTS": "Expected number of sprints", - "NUMBER_SPRINTS_HELP": "0 for an undetermined number", + "NUMBER_SPRINTS_HELP": "0 dla nieokreślonej liczby", "NUMBER_US_POINTS": "Expected total of story points", - "NUMBER_US_POINTS_HELP": "0 for an undetermined number", + "NUMBER_US_POINTS_HELP": "0 dla nieokreślonej liczby", "KANBAN": "Kanban", "KANBAN_DESCRIPTION": "Organizuj swój projekt przy użyciu metody lean.", "ISSUES": "Zgłoszenia", @@ -468,8 +468,8 @@ "PROJECT_SLUG": "Szczegóły projektu", "TAGS": "Tagi", "DESCRIPTION": "Opis", - "RECRUITING": "Is this project looking for people?", - "RECRUITING_MESSAGE": "Who are you looking for?", + "RECRUITING": "Czy ten pojekt szuka uczestników?", + "RECRUITING_MESSAGE": "Kogo szukasz?", "RECRUITING_PLACEHOLDER": "Define the profiles you are looking for", "PUBLIC_PROJECT": "Projekt publiczny", "PRIVATE_PROJECT": "Projekt prywatny", @@ -738,9 +738,9 @@ "REPORT": "Zgłoś naruszenie", "TABS": { "ACTIVITY_TAB": "Oś czasu", - "ACTIVITY_TAB_TITLE": "Show all the activity of this user", + "ACTIVITY_TAB_TITLE": "Wyświetl całą aktywność użytkownika", "PROJECTS_TAB": "Projekty", - "PROJECTS_TAB_TITLE": "List of all projects in which the user is a member", + "PROJECTS_TAB_TITLE": "Lista wszystkich projektów, do których należy użytkownik", "LIKES_TAB": "Likes", "LIKES_TAB_TITLE": "List all likes made by this user", "VOTES_TAB": "Głosy", @@ -754,7 +754,7 @@ "PROFILE_SIDEBAR": { "TITLE": "Twój profil", "DESCRIPTION": "People can see everything you do and what you are working on. Add a nice bio to give an enhanced version of your information.", - "ADD_INFO": "Edit bio" + "ADD_INFO": "Edytuj biografię" }, "PROFILE_FAVS": { "FILTER_INPUT_PLACEHOLDER": "Type something...", @@ -768,7 +768,7 @@ "FILTER_TYPE_TASK_TITLES": "Show only tasks", "FILTER_TYPE_ISSUES": "Zgłoszenia", "FILTER_TYPE_ISSUES_TITLE": "Show only issues", - "EMPTY_TITLE": "It looks like there's nothing to show here." + "EMPTY_TITLE": "Wygląda na to, że nie ma niczego do wyświetlenia tutaj." } }, "PROJECT": { @@ -777,14 +777,14 @@ "SECTION_PROJECTS": "Projekty", "HELP": "Ustal kolejność Twoich projektów tak, aby na górze znalazły się te najważniejsze.
Pierwsze 10 projektów pojawi się w liście projektów na górnym pasku nawigacji.", "PRIVATE": "Projekt prywatny", - "LOOKING_FOR_PEOPLE": "This project is looking for people", + "LOOKING_FOR_PEOPLE": "Ten projekt szuka uczestników", "FANS_COUNTER_TITLE": "{total, plural, one{one fan} other{# fans}}", "WATCHERS_COUNTER_TITLE": "{total, plural, one{one watcher} other{# watchers}}", "MEMBERS_COUNTER_TITLE": "{total, plural, one{one member} other{# members}}", "BLOCKED_PROJECT": { - "BLOCKED": "Blocked project", - "THIS_PROJECT_IS_BLOCKED": "This project is temporarily blocked", - "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "In order to unblock your projects, contact the administrator." + "BLOCKED": "Projekt zablokowany", + "THIS_PROJECT_IS_BLOCKED": "Ten projekt jest tymczasowo zablokowany", + "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "Aby odblokować swój projekt, skontaktuj się z administacją" }, "STATS": { "PROJECT": "projekt
punkty", @@ -901,8 +901,8 @@ "SECTION_NAME": "Usuń konto z Taiga", "CONFIRM": "Czy na pewno chcesz usunąć swoje konto z Taiga?", "NEWSLETTER_LABEL_TEXT": "Nie chcę więcej otrzymywać waszego newslettera", - "CANCEL": "Back to settings", - "ACCEPT": "Delete account", + "CANCEL": "Powrót do ustawień", + "ACCEPT": "Usuń konto", "BLOCK_PROJECT": "Note that all the projects you own projects will be blocked after you delete your account. If you do want a project blocked, transfer ownership to another member of each project prior to deleting your account.", "SUBTITLE": "Sorry to see you go. We'll be here if you should ever consider us again! :(" }, @@ -1021,15 +1021,15 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comment deleted by {{user}}", + "DELETED_INFO": "Komentarz usunięty przez {{user}}", "TITLE": "Komentarze", "COMMENTS_COUNT": "{{comments}} Comments", "ORDER": "Order", "OLDER_FIRST": "Older first", - "RECENT_FIRST": "Recent first", + "RECENT_FIRST": "Ostatnie najpierw", "COMMENT": "Komentarz", - "EDIT_COMMENT": "Edit comment", - "EDITED_COMMENT": "Edited:", + "EDIT_COMMENT": "Edytuj komentarz", + "EDITED_COMMENT": "Edytowano", "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Tutaj wpisz nowy komentarz", "SHOW_DELETED": "Pokaż usunięty komentarz", @@ -1037,7 +1037,7 @@ "DELETE": "Delete comment", "RESTORE": "Przywróć komentarz", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Aktywność" } }, "ACTIVITY": { @@ -1048,10 +1048,10 @@ "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "usunięty", "ADDED": "dodany", - "TAGS_ADDED": "tags added:", - "TAGS_REMOVED": "tags removed:", + "TAGS_ADDED": "dodano klucz", + "TAGS_REMOVED": "usunięto tag:", "US_POINTS": "{{role}} points", - "NEW_ATTACHMENT": "new attachment:", + "NEW_ATTACHMENT": "nowy załącznik", "DELETED_ATTACHMENT": "deleted attachment:", "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", @@ -1059,9 +1059,9 @@ "SIZE_CHANGE": "Dokonano {size, plural, one{one change} other{# changes}}", "BECAME_DEPRECATED": "became deprecated", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "Wymaganie zespołu", + "CLIENT_REQUIREMENT": "Wymaganie klienta", + "BLOCKED": "Zablokowane", "VALUES": { "YES": "tak", "NO": "nie", @@ -1111,7 +1111,7 @@ "CUSTOMIZE_GRAPH_ADMIN": "Admin", "CUSTOMIZE_GRAPH_TITLE": "Set up the points and sprints through the Admin", "MOVE_US_TO_CURRENT_SPRINT": "Przejdź do bieżącego sprintu", - "MOVE_US_TO_LATEST_SPRINT": "Move to latest Sprint", + "MOVE_US_TO_LATEST_SPRINT": "Przejdź do ostatniego sprintu", "SHOW_FILTERS": "Pokaż filtry", "SHOW_TAGS": "Pokaż tagi", "EMPTY": "The backlog is empty!", @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "Linki", - "ACTION_ADD_LINK": "Dodaj link", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "razy
edytowano", @@ -1473,7 +1473,7 @@ "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", - "CREATED": "Created", + "CREATED": "Utworzone", "MODIFIED": "Modified", "CREATOR": "Creator", "LAST_MODIFIER": "Last modifier" @@ -1606,7 +1606,7 @@ "MOST_LIKED": "Most liked", "MOST_LIKED_EMPTY": "There are no LIKED projects yet", "VIEW_MORE": "View more", - "RECRUITING": "This project is looking for people", + "RECRUITING": "Ten projekt szuka ludzi", "FEATURED": "Featured Projects", "EMPTY": "There are no projects to show with this search criteria.
Try again!", "FILTERS": { diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index 29003627..c7974d9a 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -419,7 +419,7 @@ "ADD_BUTTON": "+ Novo Membro", "ADD_BUTTON_TITLE": "Adicionar novo membro", "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Infelizmente, este projeto atingiu o número máximo de ({{membros}}) membros permitidos.", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "This project has reached its limit of ({{members}}) allowed members. If you would like to increase that limit please contact the administrator." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Este projeto atingiu o limite de ({{members}}) membros permitidos. Se você deseja aumentar este limite entre em contato com o administrador." }, "PROJECT_EXPORT": { "TITLE": "Exportar", @@ -454,7 +454,7 @@ "MEETUP": "Reunião", "MEETUP_DESCRIPTION": "Selecione seu sistema de videoconferência.", "SELECT_VIDEOCONFERENCE": "Selecione um sistema de videoconferência", - "SALT_CHAT_ROOM": "Add a prefix to the chatroom name", + "SALT_CHAT_ROOM": "Adicionar um prefixo ao nome da sala de chat", "JITSI_CHAT_ROOM": "Jitsi", "APPEARIN_CHAT_ROOM": "AppearIn", "TALKY_CHAT_ROOM": "Talky", @@ -478,10 +478,10 @@ "LOGO_HELP": "A imagem deve ser na escala de 80x80px.", "CHANGE_LOGO": "Alterar logo", "ACTION_USE_DEFAULT_LOGO": "Usar imagem padrão", - "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects allowed by your current plan", - "MAX_PRIVATE_PROJECTS_MEMBERS": "The maximum number of members for private projects has been exceeded", + "MAX_PRIVATE_PROJECTS": "Você atingiu o número máximo de projetos privados permitidos para seu plano atual.", + "MAX_PRIVATE_PROJECTS_MEMBERS": "O número máximo de membros para projetos privados foi excedido.", "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", - "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds your maximum number of members for public projects", + "MAX_PUBLIC_PROJECTS_MEMBERS": "Este projeto atingiu o seu limite atual de membros para projetos públicos", "PROJECT_OWNER": "Dono do projeto", "REQUEST_OWNERSHIP": "Solicitar propriedade", "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Gostaria de se tornar o novo dono do projeto?", @@ -489,7 +489,7 @@ "REQUEST_OWNERSHIP_BUTTON": "Solicitação", "REQUEST_OWNERSHIP_SUCCESS": "Vamos notificar o dono do projeto", "CHANGE_OWNER": "Mudar dono", - "CHANGE_OWNER_SUCCESS_TITLE": "Ok, your request has been sent!", + "CHANGE_OWNER_SUCCESS_TITLE": "Ok, sua requisição foi enviada!", "CHANGE_OWNER_SUCCESS_DESC": "We will notify you by email if the project ownership request is accepted or declined" }, "REPORTS": { @@ -538,7 +538,7 @@ "PROJECT_VALUES_PRIORITIES": { "TITLE": "Prioridades", "SUBTITLE": "Especifique as prioridades que seus problemas terão", - "ISSUE_TITLE": "Prioridades do problema", + "ISSUE_TITLE": "Severidade dos apontamentos", "ACTION_ADD": "Adicionar nova prioridade" }, "PROJECT_VALUES_SEVERITIES": { @@ -562,9 +562,9 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Tags", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "Ver e editar as cores das stories de seu usuário", "EMPTY": "Atualmente não há tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "Parece que nada foi encontrado com os critérios de sua pesquisa." }, "ROLES": { "PAGE_TITLE": "Funções - {{projectName}}", @@ -575,7 +575,7 @@ "COUNT_MEMBERS": "{{ role.members_count }} membros com a mesma função", "TITLE_DELETE_ROLE": "Apagar Função", "REPLACEMENT_ROLE": "Todos os usuários com essa função serão movidos para", - "WARNING_DELETE_ROLE": "Be careful! All role estimations will be removed", + "WARNING_DELETE_ROLE": "Cuidado! Todas as estimativas de papéis serão removidas", "ERROR_DELETE_ALL": "Você não pode apagar todos os valores", "EXTERNAL_USER": "Usuário externo" }, @@ -707,20 +707,20 @@ }, "PROJECT_TRANSFER": { "DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "Would you like to become the new project owner?", - "PRIVATE": "Private", - "ACCEPTED_PROJECT_OWNERNSHIP": "Congratulations! You're now the new project owner.", - "REJECTED_PROJECT_OWNERNSHIP": "OK. We'll contact the current project owner", + "PRIVATE": "Privado", + "ACCEPTED_PROJECT_OWNERNSHIP": "Parabéns! Você é o proprietário do projeto agora.", + "REJECTED_PROJECT_OWNERNSHIP": "OK. Entraremos em contato com o atual dono do projeto.", "ACCEPT": "Aceitar", - "REJECT": "Reject", + "REJECT": "Rejeitar", "PROPOSE_OWNERSHIP": "{{owner}}, the current owner of the project {{project}} has asked that you become the new project owner.", "ADD_COMMENT": "Would you like to add a comment for the project owner?", - "UNLIMITED_PROJECTS": "Unlimited", + "UNLIMITED_PROJECTS": "Ilimitado", "OWNER_MESSAGE": { "PRIVATE": "Please remember that you can own up to {{maxProjects}} private projects. You currently own {{currentProjects}} private projects", "PUBLIC": "Please remember that you can own up to {{maxProjects}} public projects. You currently own {{currentProjects}} public projects" }, "CANT_BE_OWNED": "At the moment you cannot become an owner of a project of this type. If you would like to become the owner of this project, please contact the administrator so they change your account settings to enable project ownership.", - "CHANGE_MY_PLAN": "Change my plan" + "CHANGE_MY_PLAN": "Mudar meu plano" } }, "USER": { @@ -767,7 +767,7 @@ "FILTER_TYPE_TASKS": "Tarefas", "FILTER_TYPE_TASK_TITLES": "Mostrar apenas tarefas", "FILTER_TYPE_ISSUES": "Problemas", - "FILTER_TYPE_ISSUES_TITLE": "mostrar apenas problemas", + "FILTER_TYPE_ISSUES_TITLE": "mostrar apenas apontamentos", "EMPTY_TITLE": "Parece que não há nada para exibir aqui." } }, @@ -784,7 +784,7 @@ "BLOCKED_PROJECT": { "BLOCKED": "Projeto bloqueado", "THIS_PROJECT_IS_BLOCKED": "Este projeto está temporariamente bloqueado", - "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "In order to unblock your projects, contact the administrator." + "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "Para desbloquear seus projetos, contate o administrador." }, "STATS": { "PROJECT": "projetos
pontos", @@ -903,7 +903,7 @@ "NEWSLETTER_LABEL_TEXT": "Eu não quero receber mais os informativos", "CANCEL": "Voltar para configurações", "ACCEPT": "Remover conta", - "BLOCK_PROJECT": "Note that all the projects you own projects will be blocked after you delete your account. If you do want a project blocked, transfer ownership to another member of each project prior to deleting your account.", + "BLOCK_PROJECT": "Note que todos os projetos em que você é o dono serão bloqueados depois que você apagar sua conta. Se você quer um projeto bloqueado, transfira a posse para outro membro de cada projeto antes de apagar sua conta.", "SUBTITLE": "É uma pena vê-lo partir. Estaremos aqui se você algum dia considerar-nos novamente! :(" }, "DELETE_PROJECT": { @@ -961,17 +961,17 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opcional) Adicione uma mensagem de texto ao convite. Diga algo animador para os novos membros ;-)", "PLACEHOLDER_TYPE_EMAIL": "Digite um Email", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Infelizmente, este projeto não pode ter mais do que {{maxMembers}} membros.
Se você gostaria de aumentar o limite atual, por favor contate o administrador.", + "LIMIT_USERS_WARNING_MESSAGE": "Infelizmente este projeto não pode ter mais do que {{maxMembers}} membros." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Infelizmente, este projeto não pode ficar sem um dono", "CURRENT_USER_OWNER": { - "DESC": "You are the current owner of this project. Before leaving, please transfer ownership to someone else.", + "DESC": "Você é o dono atual deste projeto. Antes de sair, por favor transfira o projeto para outra pessoa.", "BUTTON": "Mude o dono do projeto" }, "OTHER_USER_OWNER": { - "DESC": "Unfortunately, you can't delete a member who is also the current project owner. First, please assign a new project owner.", + "DESC": "Infelizmnete, você não pode apagar um membro que também é o dono de um projeto. Primeiro designe um novo proprietário para o projeto.", "BUTTON": "Solicite a mudança do dono do projeto" } }, @@ -991,7 +991,7 @@ "ADD": "+ Adicionar uma nova História de Usuário", "ADD_BULK": "Adicionar Histórias de Usuários em lote", "PROMOTED": "Esta História de Usuário foi criada a partir do Problema:", - "TITLE_LINK_GO_TO_ISSUE": "Ir para problema", + "TITLE_LINK_GO_TO_ISSUE": "Adicionar comentários aos apontamentos", "EXTERNAL_REFERENCE": "Esta História de Usuário foi criada de", "GO_TO_EXTERNAL_REFERENCE": "Ir para a origem", "BLOCKED": "Esta história de usuário está bloqueada", @@ -1004,14 +1004,14 @@ "NOT_ESTIMATED": "Não estimado", "TOTAL_US_POINTS": "Total de pontos de histórias", "TRIBE": { - "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH": "Publicar como Gig no Taiga Tribe", "PUBLISH_INFO": "Mais informações", "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", - "EDIT_LINK": "Edit link", - "CLOSE": "Close", + "EDIT_LINK": "Editar link", + "CLOSE": "Fechar", "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", - "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TITLE": "Você precisa de alguém para esta tarefa?", "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" }, "FIELDS": { @@ -1021,23 +1021,23 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comment deleted by {{user}}", + "DELETED_INFO": "Comentário apagado por {{user}}", "TITLE": "Comentários", - "COMMENTS_COUNT": "{{comments}} Comments", - "ORDER": "Order", - "OLDER_FIRST": "Older first", - "RECENT_FIRST": "Recent first", + "COMMENTS_COUNT": "{{comments}} comentários", + "ORDER": "Ordenação", + "OLDER_FIRST": "Antigos primeiro", + "RECENT_FIRST": "Recentes primeiro", "COMMENT": "Comentário", - "EDIT_COMMENT": "Edit comment", - "EDITED_COMMENT": "Edited:", - "SHOW_HISTORY": "View historic", + "EDIT_COMMENT": "Editar comentário", + "EDITED_COMMENT": "Editado:", + "SHOW_HISTORY": "Ver histórico", "TYPE_NEW_COMMENT": "Escreva um novo comentário aqui", "SHOW_DELETED": "Mostrar comentários apagados", "HIDE_DELETED": "Esconder comentário apagado", "DELETE": "Apagar comentário", "RESTORE": "Restaurar comentário", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Atividade" } }, "ACTIVITY": { @@ -1045,23 +1045,23 @@ "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Mostrar entradas anteriores (mais {{showMore}})", "TITLE": "Atividade", - "ACTIVITIES_COUNT": "{{activities}} Activities", + "ACTIVITIES_COUNT": "{{activities}} atividades", "REMOVED": "removido", "ADDED": "adicionado", - "TAGS_ADDED": "tags added:", - "TAGS_REMOVED": "tags removed:", - "US_POINTS": "{{role}} points", - "NEW_ATTACHMENT": "new attachment:", - "DELETED_ATTACHMENT": "deleted attachment:", - "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", - "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", - "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", + "TAGS_ADDED": "tags adicionadas:", + "TAGS_REMOVED": "tags removidas:", + "US_POINTS": "{{role}} pontos", + "NEW_ATTACHMENT": "novo anexo:", + "DELETED_ATTACHMENT": "anexo apagado:", + "UPDATED_ATTACHMENT": "anexo atualizado ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "atributo personalizado criado", + "UPDATED_CUSTOM_ATTRIBUTE": "atributo personalizado atualizado", "SIZE_CHANGE": "Feito {size, plural, one{one change} other{# changes}}", - "BECAME_DEPRECATED": "became deprecated", + "BECAME_DEPRECATED": "foi depreciado", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "Requisitos da Equipe", + "CLIENT_REQUIREMENT": "Requisitos do Cliente", + "BLOCKED": "Bloqueado", "VALUES": { "YES": "sim", "NO": "não", @@ -1267,9 +1267,9 @@ "SUCCESS": "Nossos Oompa Loompas atualizaram seu email" }, "ISSUES": { - "PAGE_TITLE": "Problemas - {{projectName}}", + "PAGE_TITLE": "Apontamentos - {{projectName}}", "PAGE_DESCRIPTION": "O painel de problemas do projeto {{projectName}}: {{projectDescription}}", - "LIST_SECTION_NAME": "Problemas", + "LIST_SECTION_NAME": "Tipos de problemas", "SECTION_NAME": "Problema", "ACTION_NEW_ISSUE": "+ NOVO PROBLEMA", "ACTION_PROMOTE_TO_US": "Promover para História de Usuário", @@ -1277,7 +1277,7 @@ "PROMOTED": "Esse problema foi promovido para história de usuário", "EXTERNAL_REFERENCE": "Esse problema foi criado a partir de", "GO_TO_EXTERNAL_REFERENCE": "Ir para a origem", - "BLOCKED": "Esse problema está bloqueado", + "BLOCKED": "Esse apontamento está bloqueado", "TITLE_PREVIOUS_ISSUE": "problema anterior", "TITLE_NEXT_ISSUE": "próximo problema", "ACTION_DELETE": "Problema apagado", @@ -1374,7 +1374,7 @@ "APP_TITLE": "EQUIPE - {{projectName}}", "PLACEHOLDER_INPUT_SEARCH": "Procurar pelo nome completo...", "COLUMN_MR_WOLF": "Sr. Wolf", - "EXPLANATION_COLUMN_MR_WOLF": "Problemas fechados", + "EXPLANATION_COLUMN_MR_WOLF": "Adicionar apontamentos", "COLUMN_IOCAINE": "Bebedor de Iocaine", "EXPLANATION_COLUMN_IOCAINE": "Doses de Iocaine ingeridas", "COLUMN_CERVANTES": "Pero Vaz de Caminha", @@ -1439,7 +1439,7 @@ "WIZARD": { "SECTION_TITLE_CREATE_PROJECT": "Criar Projeto", "CREATE_PROJECT_TEXT": "Novo em folha. Tão excitante!", - "CHOOSE_TEMPLATE": "Which template fits your project best?", + "CHOOSE_TEMPLATE": "Qual template se encaixa melhor no seu projeto?", "CHOOSE_TEMPLATE_TITLE": "Mais informações sobre templates de projeto", "CHOOSE_TEMPLATE_INFO": "Mais informações", "PROJECT_DETAILS": "Detalhes do Projeto", @@ -1459,24 +1459,24 @@ "DELETE_LIGHTBOX_TITLE": "Apagar página Wiki", "DELETE_LINK_TITLE": "Remover link de Wiki", "NAVIGATION": { - "HOME": "Main Page", - "SECTION_NAME": "Links", - "ACTION_ADD_LINK": "Adicionar link", - "ALL_PAGES": "All pages" + "HOME": "Página principal", + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "vezes
editadas", "LAST_EDIT": "última
edição", "LAST_MODIFICATION": "ultima modificação" }, - "SECTION_PAGES_LIST": "All pages", + "SECTION_PAGES_LIST": "Todas as páginas", "PAGES_LIST_COLUMNS": { - "TITLE": "Title", - "EDITIONS": "Editions", - "CREATED": "Created", - "MODIFIED": "Modified", - "CREATOR": "Creator", - "LAST_MODIFIER": "Last modifier" + "TITLE": "Título", + "EDITIONS": "Edições", + "CREATED": "Criado", + "MODIFIED": "Modificado", + "CREATOR": "Criador", + "LAST_MODIFIER": "Último modificador" } }, "HINTS": { diff --git a/app/locales/taiga/locale-ru.json b/app/locales/taiga/locale-ru.json index 7a0c666e..00488431 100644 --- a/app/locales/taiga/locale-ru.json +++ b/app/locales/taiga/locale-ru.json @@ -1037,7 +1037,7 @@ "DELETE": "Удалить комментарий", "RESTORE": "Показать удаленный комментарий", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Действия" } }, "ACTIVITY": { @@ -1059,9 +1059,9 @@ "SIZE_CHANGE": "Сделано {size, plural, one{изменение} other{# изменений}}", "BECAME_DEPRECATED": "became deprecated", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "Требование от Команды", + "CLIENT_REQUIREMENT": "Требование клиента", + "BLOCKED": "Заблокирован", "VALUES": { "YES": "да", "NO": "нет", @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "Ссылки", - "ACTION_ADD_LINK": "Добавить ссылку", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "раз
отредактировано", @@ -1473,7 +1473,7 @@ "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", - "CREATED": "Created", + "CREATED": "Создан", "MODIFIED": "Modified", "CREATOR": "Creator", "LAST_MODIFIER": "Last modifier" diff --git a/app/locales/taiga/locale-sv.json b/app/locales/taiga/locale-sv.json index 54261c4b..7560899b 100644 --- a/app/locales/taiga/locale-sv.json +++ b/app/locales/taiga/locale-sv.json @@ -1037,7 +1037,7 @@ "DELETE": "Ta bort kommentar", "RESTORE": "Hämta tillbaka tidigare kommentarer", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Aktiviteter" } }, "ACTIVITY": { @@ -1059,9 +1059,9 @@ "SIZE_CHANGE": "Gjorde {size, plural, one{one change} other{# changes}}", "BECAME_DEPRECATED": "became deprecated", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "Teamets behov", + "CLIENT_REQUIREMENT": "Kräver beställare", + "BLOCKED": "Blockerad", "VALUES": { "YES": "ja", "NO": "nej", @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "Länkar", - "ACTION_ADD_LINK": "Lägg till länk", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "gånger
ändrad", @@ -1473,7 +1473,7 @@ "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", - "CREATED": "Created", + "CREATED": "Skapad", "MODIFIED": "Modified", "CREATOR": "Creator", "LAST_MODIFIER": "Last modifier" diff --git a/app/locales/taiga/locale-tr.json b/app/locales/taiga/locale-tr.json index db8c7d20..49f1bc7b 100644 --- a/app/locales/taiga/locale-tr.json +++ b/app/locales/taiga/locale-tr.json @@ -1037,7 +1037,7 @@ "DELETE": "Yorumu sil", "RESTORE": "Yorumu geri yükle", "HISTORY": { - "TITLE": "Activity" + "TITLE": "Aktivite" } }, "ACTIVITY": { @@ -1059,9 +1059,9 @@ "SIZE_CHANGE": "Yapılan {size, plural, one{tek değişiklik} other{# değişiklikler}}", "BECAME_DEPRECATED": "became deprecated", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "Takım Gereksinimi", + "CLIENT_REQUIREMENT": "İstemci Gereksinimi", + "BLOCKED": "Engelli", "VALUES": { "YES": "evet", "NO": "hayır", @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "Bağlantılar", - "ACTION_ADD_LINK": "Bağlantı ekle", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "kere
düzenlendi", @@ -1473,7 +1473,7 @@ "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", - "CREATED": "Created", + "CREATED": "Oluşturuldu", "MODIFIED": "Modified", "CREATOR": "Creator", "LAST_MODIFIER": "Last modifier" diff --git a/app/locales/taiga/locale-zh-hant.json b/app/locales/taiga/locale-zh-hant.json index ae5b83d1..135b08b8 100644 --- a/app/locales/taiga/locale-zh-hant.json +++ b/app/locales/taiga/locale-zh-hant.json @@ -1037,7 +1037,7 @@ "DELETE": "刪除評論 ", "RESTORE": "恢復原評論 ", "HISTORY": { - "TITLE": "Activity" + "TITLE": "動態" } }, "ACTIVITY": { @@ -1059,9 +1059,9 @@ "SIZE_CHANGE": "使 {size, plural, one{更改} other{變化}}", "BECAME_DEPRECATED": "became deprecated", "BECAME_UNDEPRECATED": "became undeprecated", - "TEAM_REQUIREMENT": "Team Requirement", - "CLIENT_REQUIREMENT": "Client Requirement", - "BLOCKED": "Blocked", + "TEAM_REQUIREMENT": "團隊要求", + "CLIENT_REQUIREMENT": "客戶要求", + "BLOCKED": "已封鎖", "VALUES": { "YES": "yes", "NO": "no", @@ -1460,9 +1460,9 @@ "DELETE_LINK_TITLE": "Delete Wiki link", "NAVIGATION": { "HOME": "Main Page", - "SECTION_NAME": "連結", - "ACTION_ADD_LINK": "新增連結", - "ALL_PAGES": "All pages" + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" }, "SUMMARY": { "TIMES_EDITED": "次數
編輯 ", @@ -1473,7 +1473,7 @@ "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", - "CREATED": "Created", + "CREATED": "已創建", "MODIFIED": "Modified", "CREATOR": "Creator", "LAST_MODIFIER": "Last modifier" From 065a3af3d660b39f7252ca48a5a628f6c2bfbddc Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 26 Jul 2016 13:01:28 +0200 Subject: [PATCH 098/315] log error when there are plugins with wrong path --- app-loader/app-loader.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app-loader/app-loader.coffee b/app-loader/app-loader.coffee index cee8fe74..2951fb09 100644 --- a/app-loader/app-loader.coffee +++ b/app-loader/app-loader.coffee @@ -33,7 +33,7 @@ loadStylesheet = (path) -> loadPlugin = (pluginPath) -> return new Promise (resolve, reject) -> - $.getJSON(pluginPath).then (plugin) -> + success = (plugin) -> window.taigaContribPlugins.push(plugin) if plugin.css @@ -45,6 +45,11 @@ loadPlugin = (pluginPath) -> else resolve() + fail = () -> + console.error("error loading", pluginPath); + + $.getJSON(pluginPath).then(success, fail) + loadPlugins = (plugins) -> promises = [] _.map plugins, (pluginPath) -> From 528457dbca8f70d575017f45f19f634180d86dcd Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 5 Jul 2016 12:08:11 +0200 Subject: [PATCH 099/315] show points per role in milestone --- CHANGELOG.md | 1 + app/coffee/modules/taskboard/main.coffee | 28 ++++++++ app/locales/taiga/locale-en.json | 3 +- .../includes/components/sprint-summary.jade | 53 +++++++++----- app/partials/includes/modules/sprint.jade | 3 +- app/styles/components/summary.scss | 70 +++++++++++++++++-- 6 files changed, 133 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c840d86..64e9cc8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Display the current user (me) at first in assignment lightbox (thanks to [@mikaoelitiana](https://github.com/mikaoelitiana)) - Divide the user dashboard in two columns in large screens, - Upvote and downvote issues from the issues list. +- Show points per role in statsection of the taskboard panel. - Comments: - Add a new permissions to allow add comments instead of use the existent modify permission for this purpose. - Ability to edit comments, view edition history and redesign comments module UI. diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee index 68620f62..d52d8dff 100644 --- a/app/coffee/modules/taskboard/main.coffee +++ b/app/coffee/modules/taskboard/main.coffee @@ -220,6 +220,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) return promise.then(=> @.loadProject()) .then(=> @.loadTaskboard()) + .then(=> @.setRolePoints()) refreshTasksOrder: (tasks) -> items = @.resortTasks(tasks) @@ -273,6 +274,33 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) editTaskAssignedTo: (task) -> @rootscope.$broadcast("assigned-to:add", task) + setRolePoints: () -> + computableRoles = _.filter(@scope.project.roles, "computable") + + getRole = (roleId) => + roleId = parseInt(roleId, 10) + return _.find computableRoles, (role) -> role.id == roleId + + getPoint = (pointId) => + poitnId = parseInt(pointId, 10) + return _.find @scope.project.points, (point) -> point.id == pointId + + pointsByRole = _.reduce @scope.userstories, (result, us, key) => + _.forOwn us.points, (pointId, roleId) -> + role = getRole(roleId) + point = getPoint(pointId) + + if !result[role.id] + result[role.id] = role + result[role.id].points = 0 + + result[role.id].points += point.value + + return result + , {} + + @scope.pointsByRole = Object.keys(pointsByRole).map (key) -> return pointsByRole[key] + module.controller("TaskboardController", TaskboardController) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 83b897de..16777909 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1151,7 +1151,8 @@ "CLOSED_TASKS": "closed
tasks", "IOCAINE_DOSES": "iocaine
doses", "SHOW_STATISTICS_TITLE": "Show statistics", - "TOGGLE_BAKLOG_GRAPH": "Show/Hide burndown graph" + "TOGGLE_BAKLOG_GRAPH": "Show/Hide burndown graph", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "project
points", diff --git a/app/partials/includes/components/sprint-summary.jade b/app/partials/includes/components/sprint-summary.jade index 364a336a..b4f5f6ac 100644 --- a/app/partials/includes/components/sprint-summary.jade +++ b/app/partials/includes/components/sprint-summary.jade @@ -1,29 +1,44 @@ div.summary.large-summary div.large-summary-wrapper - div.summary-progress-wrapper + .summary-progress-wrapper div.summary-progress-bar(tg-progress-bar="stats.completedPercentage") div.data - span.number(ng-bind="stats.completedPercentage + '%'") + span.number(ng-bind="stats.completedPercentage + '%'") - div.summary-stats - span.number(ng-bind="stats.totalPointsSum|default:'--'") - span.description(translate="BACKLOG.SPRINT_SUMMARY.TOTAL_POINTS") - div.summary-stats - span.number(ng-bind="stats.completedPointsSum|default:'--'") - span.description(translate="BACKLOG.SPRINT_SUMMARY.COMPLETED_POINTS") + .stats-wrapper(ng-class="{'show-role-points': showRolePoints}") + .main-summary-stats + span.summary-stats.toggle-points-per-role(ng-click="showRolePoints = true") + tg-svg(svg-icon="icon-arrow-down") + span.number(ng-bind="stats.totalPointsSum|default:'--'") + span.description(translate="BACKLOG.SPRINT_SUMMARY.TOTAL_POINTS") + div.summary-stats.summary-completed-points + span.number(ng-bind="stats.completedPointsSum|default:'--'") + span.description(translate="BACKLOG.SPRINT_SUMMARY.COMPLETED_POINTS") - div.summary-stats - tg-svg(svg-icon="icon-bulk") - span.number(ng-bind="stats.openTasks|default:'--'") - span.description(translate="BACKLOG.SPRINT_SUMMARY.OPEN_TASKS") - div.summary-stats - span.number(ng-bind="stats.completed_tasks|default:'--'") - span.description(translate="BACKLOG.SPRINT_SUMMARY.CLOSED_TASKS") + div.summary-stats.summary-open-tasks + tg-svg(svg-icon="icon-bulk") + span.number(ng-bind="stats.openTasks|default:'--'") + span.description(translate="BACKLOG.SPRINT_SUMMARY.OPEN_TASKS") + div.summary-stats.summary-closed-tasks + span.number(ng-bind="stats.completed_tasks|default:'--'") + span.description(translate="BACKLOG.SPRINT_SUMMARY.CLOSED_TASKS") - div.summary-stats(title="{{'COMMON.IOCAINE_TEXT' | translate}}") - tg-svg(svg-icon="icon-iocaine") - span.number(ng-bind="stats.iocaine_doses|default:'--'") - span.description(translate="BACKLOG.SPRINT_SUMMARY.IOCAINE_DOSES") + div.summary-stats.summary-iocaine(title="{{'COMMON.IOCAINE_TEXT' | translate}}") + tg-svg(svg-icon="icon-iocaine") + span.number(ng-bind="stats.iocaine_doses|default:'--'") + span.description(translate="BACKLOG.SPRINT_SUMMARY.IOCAINE_DOSES") + + .points-per-role-stats.toggle-points-per-role( + ng-click="showRolePoints = false" + ) + span.points-per-role-stats-title + tg-svg(svg-icon="icon-arrow-up") + span(translate="BACKLOG.SPRINT_SUMMARY.POINTS_PER_ROLE") + + .points-per-role-stats-content + .summary-stats(ng-repeat="rolePoint in pointsByRole") + span.number {{rolePoint.points}} + span.role {{rolePoint.name}} div.stats.toggle-analytics-visibility(title="{{'BACKLOG.SPRINT_SUMMARY.SHOW_STATISTICS_TITLE' | translate}}") tg-svg(svg-icon="icon-graph") diff --git a/app/partials/includes/modules/sprint.jade b/app/partials/includes/modules/sprint.jade index f56ca58b..d66c5ca7 100644 --- a/app/partials/includes/modules/sprint.jade +++ b/app/partials/includes/modules/sprint.jade @@ -1,6 +1,7 @@ header(tg-backlog-sprint-header, ng-model="sprint") -div.sprint-progress-bar(tg-progress-bar="100 * sprint.closed_points / sprint.total_points") +.summary-progress-wrapper + div.sprint-progress-bar(tg-progress-bar="100 * sprint.closed_points / sprint.total_points") div.sprint-table(tg-bind-scope, ng-class="{'sprint-empty-wrapper': !sprint.user_stories.length}") div.sprint-empty(ng-if="!sprint.user_stories.length") diff --git a/app/styles/components/summary.scss b/app/styles/components/summary.scss index 2b9a8cda..52e419fd 100644 --- a/app/styles/components/summary.scss +++ b/app/styles/components/summary.scss @@ -2,14 +2,18 @@ $summary-background: $grayer; .summary { align-content: center; + align-items: center; background: $summary-background; color: $white; display: flex; flex-wrap: wrap; + height: 65px; justify-content: flex-start; margin-bottom: 2rem; - padding: 1em; + overflow: hidden; + padding: 1rem; .summary-stats { + align-items: center; display: flex; margin: 0 .5rem; } @@ -20,7 +24,7 @@ $summary-background: $grayer; } .number { @include font-size(xlarge); - @include font-type(bold); + @include font-type(light); line-height: .9; margin-right: .3rem; } @@ -69,6 +73,21 @@ $summary-background: $grayer; transition: fill .2s; } } + .main-summary-stats { + display: flex; + transform: translateY(0); + transition: all .2s ease-in-out; + } + + + .show-role-points { + .points-per-role-stats { + transform: translateY(-35px); + } + .main-summary-stats { + transform: translateY(-65px); + } + } } .summary-progress-bar { @@ -102,7 +121,12 @@ $summary-background: $grayer; } .large-summary { + align-items: stretch; justify-content: space-between; + padding: .75rem 1rem; + .stats-wrapper { + padding-top: .35rem; + } .large-summary-wrapper { align-content: center; display: flex; @@ -110,6 +134,7 @@ $summary-background: $grayer; justify-content: flex-start; } .summary-progress-wrapper { + align-items: center; display: flex; } .summary-progress-bar { @@ -122,13 +147,23 @@ $summary-background: $grayer; border: 0; margin: 0; } + &.summary-completed-points, + &.summary-closed-tasks { + border-right: 1px solid $blackish; + margin-right: 0; + padding-right: 1rem; + +.summary-stats { + border-left: 1px solid $gray; + margin-left: 0; + padding-left: 1rem; + } + } } .icon { + @include svg-size(1.3rem); fill: currentColor; - height: 1.5rem; margin-right: .4rem; vertical-align: middle; - width: 1.5rem; &.icon-stats { color: $primary; float: right; @@ -146,6 +181,33 @@ $summary-background: $grayer; } } } + .points-per-role-stats-content { + display: flex; + padding-left: 1rem; + .summary-stats { + padding: 0; + } + } + .toggle-points-per-role { + color: $white; + cursor: pointer; + svg { + @include svg-size(); + } + } + .points-per-role-stats { + margin-left: .5rem; + transform: translateY(35px); + transition: all .2s ease-in-out; + .number { + @include font-size(large); + @include font-type(normal); + } + .role { + @include font-size(xsmall); + @include font-type(light); + } + } } .empty-burndown { From b9a825620db8897e02a148d0de6617214b34f028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Thu, 21 Jul 2016 11:24:10 +0200 Subject: [PATCH 100/315] Include gogs as builtin --- CHANGELOG.md | 2 + app/coffee/app.coffee | 6 ++ app/coffee/modules/admin/third-parties.coffee | 47 ++++++++++++++++ app/coffee/modules/base.coffee | 1 + app/locales/taiga/locale-ca.json | 6 +- app/locales/taiga/locale-de.json | 6 +- app/locales/taiga/locale-en.json | 4 ++ app/locales/taiga/locale-es.json | 6 +- app/locales/taiga/locale-fi.json | 6 +- app/locales/taiga/locale-fr.json | 6 +- app/locales/taiga/locale-it.json | 6 +- app/locales/taiga/locale-nl.json | 6 +- app/locales/taiga/locale-pl.json | 6 +- app/locales/taiga/locale-pt-br.json | 6 +- app/locales/taiga/locale-ru.json | 6 +- app/locales/taiga/locale-sv.json | 6 +- app/locales/taiga/locale-tr.json | 6 +- app/locales/taiga/locale-zh-hant.json | 6 +- .../admin/admin-third-parties-gitlab.jade | 2 +- .../admin/admin-third-parties-gogs.jade | 56 +++++++++++++++++++ .../modules/admin-submenu-third-parties.jade | 3 + 21 files changed, 185 insertions(+), 14 deletions(-) create mode 100644 app/partials/admin/admin-third-parties-gogs.jade diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e9cc8f..f1fe705a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ - Drag & Drop ordering for wiki links. - Add a list of all wiki pages - Add Wiki history +- Third party integrations: + - Included gogs as builtin integration. ### Misc - Lots of small and not so small bugfixes. diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index a1840ebf..7827d1a7 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -341,6 +341,12 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven section: "admin" } ) + $routeProvider.when("/project/:pslug/admin/third-parties/gogs", + { + templateUrl: "admin/admin-third-parties-gogs.html", + section: "admin" + } + ) # Admin - Contrib Plugins $routeProvider.when("/project/:pslug/admin/contrib/:plugin", {templateUrl: "contrib/main.html"}) diff --git a/app/coffee/modules/admin/third-parties.coffee b/app/coffee/modules/admin/third-parties.coffee index 076bf552..74ea9ed7 100644 --- a/app/coffee/modules/admin/third-parties.coffee +++ b/app/coffee/modules/admin/third-parties.coffee @@ -586,3 +586,50 @@ ValidOriginIpsDirective = -> } module.directive("tgValidOriginIps", ValidOriginIpsDirective) + +############################################################################# +## Gogs Controller +############################################################################# + +class GogsController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) + @.$inject = [ + "$scope", + "$tgRepo", + "$tgResources", + "$routeParams", + "tgAppMetaService", + "$translate" + ] + + constructor: (@scope, @repo, @rs, @params, @appMetaService, @translate) -> + bindMethods(@) + + @scope.sectionName = @translate.instant("ADMIN.GOGS.SECTION_NAME") + @scope.project = {} + + promise = @.loadInitialData() + + promise.then () => + title = @translate.instant("ADMIN.GOGS.PAGE_TITLE", {projectName: @scope.project.name}) + description = @scope.project.description + @appMetaService.setAll(title, description) + + promise.then null, @.onInitialDataError.bind(@) + + loadModules: -> + return @rs.modules.list(@scope.projectId, "gogs").then (gogs) => + @scope.gogs = gogs + + loadProject: -> + return @rs.projects.getBySlug(@params.pslug).then (project) => + @scope.projectId = project.id + @scope.project = project + @scope.$emit('project:loaded', project) + return project + + loadInitialData: -> + promise = @.loadProject() + promise.then(=> @.loadModules()) + return promise + +module.controller("GogsController", GogsController) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 75b0e23e..c4448294 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -108,6 +108,7 @@ urls = { "project-admin-third-parties-github": "/project/:project/admin/third-parties/github" "project-admin-third-parties-gitlab": "/project/:project/admin/third-parties/gitlab" "project-admin-third-parties-bitbucket": "/project/:project/admin/third-parties/bitbucket" + "project-admin-third-parties-gogs": "/project/:project/admin/third-parties/gogs" "project-admin-contrib": "/project/:project/admin/contrib/:plugin" # User settings diff --git a/app/locales/taiga/locale-ca.json b/app/locales/taiga/locale-ca.json index dfa78e74..b8ae371d 100644 --- a/app/locales/taiga/locale-ca.json +++ b/app/locales/taiga/locale-ca.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhooks", @@ -1628,4 +1632,4 @@ "RESULTS": "Search results" } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-de.json b/app/locales/taiga/locale-de.json index 5ec14239..42af6566 100644 --- a/app/locales/taiga/locale-de.json +++ b/app/locales/taiga/locale-de.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhooks", @@ -1628,4 +1632,4 @@ "RESULTS": "Suchergebnisse" } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 16777909..41abba09 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhooks", diff --git a/app/locales/taiga/locale-es.json b/app/locales/taiga/locale-es.json index 41fdf455..09f50131 100644 --- a/app/locales/taiga/locale-es.json +++ b/app/locales/taiga/locale-es.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhooks", @@ -1628,4 +1632,4 @@ "RESULTS": "Resultados de búsqueda" } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-fi.json b/app/locales/taiga/locale-fi.json index 4a7c2af8..99cad17b 100644 --- a/app/locales/taiga/locale-fi.json +++ b/app/locales/taiga/locale-fi.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhookit", @@ -1628,4 +1632,4 @@ "RESULTS": "Search results" } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-fr.json b/app/locales/taiga/locale-fr.json index 1e5348e7..d17a58e0 100644 --- a/app/locales/taiga/locale-fr.json +++ b/app/locales/taiga/locale-fr.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhooks", @@ -1628,4 +1632,4 @@ "RESULTS": "Résultats de la recherche" } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-it.json b/app/locales/taiga/locale-it.json index 86db70c8..a683b8f9 100644 --- a/app/locales/taiga/locale-it.json +++ b/app/locales/taiga/locale-it.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhooks", @@ -1628,4 +1632,4 @@ "RESULTS": "Risultati della ricerca" } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-nl.json b/app/locales/taiga/locale-nl.json index 0caba2ba..a730cce8 100644 --- a/app/locales/taiga/locale-nl.json +++ b/app/locales/taiga/locale-nl.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhooks", @@ -1628,4 +1632,4 @@ "RESULTS": "Search results" } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-pl.json b/app/locales/taiga/locale-pl.json index 8818b746..34ca3e7a 100644 --- a/app/locales/taiga/locale-pl.json +++ b/app/locales/taiga/locale-pl.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhooks", @@ -1628,4 +1632,4 @@ "RESULTS": "Search results" } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index c7974d9a..af05d090 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhooks", @@ -1628,4 +1632,4 @@ "RESULTS": "Resultado de pesquisa." } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-ru.json b/app/locales/taiga/locale-ru.json index 00488431..080afeda 100644 --- a/app/locales/taiga/locale-ru.json +++ b/app/locales/taiga/locale-ru.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Веб-хуки - {{projectName}}", "SECTION_NAME": "Веб-хуки", @@ -1628,4 +1632,4 @@ "RESULTS": "Результаты поиска" } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-sv.json b/app/locales/taiga/locale-sv.json index 7560899b..ab8efca2 100644 --- a/app/locales/taiga/locale-sv.json +++ b/app/locales/taiga/locale-sv.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webbkrok - {{projectName}}", "SECTION_NAME": "Webbkrokar", @@ -1628,4 +1632,4 @@ "RESULTS": "Search results" } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-tr.json b/app/locales/taiga/locale-tr.json index 49f1bc7b..a03a4b38 100644 --- a/app/locales/taiga/locale-tr.json +++ b/app/locales/taiga/locale-tr.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Github", "PAGE_TITLE": "Github - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks - {{projectName}}", "SECTION_NAME": "Webhooks", @@ -1628,4 +1632,4 @@ "RESULTS": "Arama sonuçları" } } -} \ No newline at end of file +} diff --git a/app/locales/taiga/locale-zh-hant.json b/app/locales/taiga/locale-zh-hant.json index 135b08b8..93fd5ac0 100644 --- a/app/locales/taiga/locale-zh-hant.json +++ b/app/locales/taiga/locale-zh-hant.json @@ -598,6 +598,10 @@ "SECTION_NAME": "Githun", "PAGE_TITLE": "Gitlab - {{projectName}}" }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, "WEBHOOKS": { "PAGE_TITLE": "Webhooks- {{projectName}}", "SECTION_NAME": "網頁觸發 ", @@ -1628,4 +1632,4 @@ "RESULTS": "搜尋結果" } } -} \ No newline at end of file +} diff --git a/app/partials/admin/admin-third-parties-gitlab.jade b/app/partials/admin/admin-third-parties-gitlab.jade index a8950fd1..2f2fbf71 100644 --- a/app/partials/admin/admin-third-parties-gitlab.jade +++ b/app/partials/admin/admin-third-parties-gitlab.jade @@ -60,7 +60,7 @@ div.wrapper.roles( ) a.help-button( - href="https://tree.taiga.io/support/integrations/gitlab-integration/" + href="https://tree.taiga.io/support/integrations/gogs-integration/" target="_blank" ) tg-svg(svg-icon="icon-question") diff --git a/app/partials/admin/admin-third-parties-gogs.jade b/app/partials/admin/admin-third-parties-gogs.jade new file mode 100644 index 00000000..b5763d6c --- /dev/null +++ b/app/partials/admin/admin-third-parties-gogs.jade @@ -0,0 +1,56 @@ +doctype html + +div.wrapper.roles( + tg-gogs-webhooks + ng-controller="GogsController as ctrl", + ng-init="section='admin'" +) + tg-project-menu + 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-gogs") + include ../includes/modules/admin-submenu-third-parties + + section.main.admin-common.admin-third-parties + include ../includes/components/mainTitle + + form + fieldset + label(for="secret-key", translate="ADMIN.THIRD_PARTIES.SECRET_KEY") + input( + id="secret-key" + type="text" + name="secret-key" + ng-model="gogs.secret" + placeholder="{{'ADMIN.THIRD_PARTIES.SECRET_KEY' | translate}}" + ) + + fieldset + .select-input-text(tg-select-input-text) + div + label(for="payload-url", translate="ADMIN.THIRD_PARTIES.PAYLOAD_URL") + .field-with-option + input( + id="payload-url" + type="text" + name="payload-url" + readonly="readonly" + ng-model="gogs.webhooks_url" + placeholder="{{'ADMIN.THIRD_PARTIES.PAYLOAD_URL' | translate}}" + ) + .option-wrapper.select-input-content + tg-svg(svg-icon="icon-clipboard") + .help-copy(translate="COMMON.COPY_TO_CLIPBOARD") + + button.button-green.submit-button( + type="submit" + title="{{'COMMON.SAVE' | translate}}" + translate="COMMON.SAVE" + ) + + a.help-button( + href="https://tree.taiga.io/support/integrations/gogs-integration/" + target="_blank" + ) + tg-svg(svg-icon="icon-question") + span(translate="ADMIN.HELP") diff --git a/app/partials/includes/modules/admin-submenu-third-parties.jade b/app/partials/includes/modules/admin-submenu-third-parties.jade index a0666cb1..ab39cb4c 100644 --- a/app/partials/includes/modules/admin-submenu-third-parties.jade +++ b/app/partials/includes/modules/admin-submenu-third-parties.jade @@ -13,3 +13,6 @@ section.admin-submenu li#adminmenu-third-parties-bitbucket a(href="", tg-nav="project-admin-third-parties-bitbucket:project=project.slug") span.title Bitbucket + li#adminmenu-third-parties-gogs + a(href="", tg-nav="project-admin-third-parties-gogs:project=project.slug") + span.title Gogs From 51b5897d88dd110684fc578b21f233a82f0f6fff Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 19 Jul 2016 15:02:00 +0200 Subject: [PATCH 101/315] new user avatar directive --- CHANGELOG.md | 3 +- app/coffee/modules/admin/memberships.coffee | 9 +- app/coffee/modules/common/components.coffee | 62 +++++++----- app/coffee/modules/common/lightboxes.coffee | 13 ++- app/coffee/modules/issues/list.coffee | 13 ++- app/coffee/modules/kanban/main.coffee | 16 +-- app/coffee/modules/related-tasks.coffee | 14 ++- app/coffee/modules/taskboard/main.coffee | 10 +- app/coffee/modules/wiki/main.coffee | 10 +- app/images/user-avatars/user-avatar-01.png | Bin 0 -> 1088 bytes app/images/user-avatars/user-avatar-02.png | Bin 0 -> 1005 bytes app/images/user-avatars/user-avatar-03.png | Bin 0 -> 1335 bytes app/images/user-avatars/user-avatar-04.png | Bin 0 -> 1170 bytes app/images/user-avatars/user-avatar-05.png | Bin 0 -> 1093 bytes .../components/avatar/avatar.directive.coffee | 49 +++++++++ app/modules/external-apps/external-app.jade | 5 +- app/modules/history/comments/comment.jade | 2 +- .../history-lightbox/history-entry.jade | 2 +- app/modules/history/history/history.jade | 2 +- app/modules/home/duties/duty.jade | 2 +- .../dropdown-user/dropdown-user.jade | 5 +- .../navigation-bar/navigation-bar.scss | 2 +- .../profile/profile-bar/profile-bar.jade | 5 +- .../profile-contacts/profile-contacts.jade | 5 +- .../profile/profile-favs/items/ticket.jade | 6 +- .../profile-projects/profile-projects.jade | 2 +- app/modules/projects/project/project.jade | 5 +- app/modules/services/avatar.service.coffee | 93 ++++++++++++++++++ .../user-timeline-item.jade | 15 ++- .../wiki/history/wiki-history-entry.jade | 2 +- .../admin/admin-project-change-owner.jade | 2 +- .../admin-project-request-ownership.jade | 2 +- .../admin/memberships-row-avatar.jade | 5 +- app/partials/auth/invitation.jade | 6 +- .../common/components/assigned-to.jade | 6 +- .../common/components/created-by.jade | 3 +- .../list-item-assigned-to-avatar.jade | 6 +- .../common/components/user-display.jade | 6 +- app/partials/common/components/watchers.jade | 17 ++-- .../lightbox/lightbox-assigned-to-users.jade | 10 +- .../lightbox/lightbox-change-owner.jade | 5 +- app/partials/taskboard/taskboard-user.jade | 15 ++- .../team/team-member-current-user.jade | 2 +- app/partials/team/team-members.jade | 2 +- app/partials/user/user-profile.jade | 2 +- app/partials/wiki/wiki-summary.jade | 12 ++- 46 files changed, 343 insertions(+), 110 deletions(-) create mode 100644 app/images/user-avatars/user-avatar-01.png create mode 100644 app/images/user-avatars/user-avatar-02.png create mode 100644 app/images/user-avatars/user-avatar-03.png create mode 100644 app/images/user-avatars/user-avatar-04.png create mode 100644 app/images/user-avatars/user-avatar-05.png create mode 100644 app/modules/components/avatar/avatar.directive.coffee create mode 100644 app/modules/services/avatar.service.coffee diff --git a/CHANGELOG.md b/CHANGELOG.md index f1fe705a..0551a4ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,10 @@ - Neew Attachments image slider in preview mode. - New admin area to edit the tag colors used in your project. - Display the current user (me) at first in assignment lightbox (thanks to [@mikaoelitiana](https://github.com/mikaoelitiana)) -- Divide the user dashboard in two columns in large screens, +- Divide the user dashboard in two columns in large screens. - Upvote and downvote issues from the issues list. - Show points per role in statsection of the taskboard panel. +- Show a funny randon animals/color for users with no avatar (like project logos). - Comments: - Add a new permissions to allow add comments instead of use the existent modify permission for this purpose. - Ability to edit comments, view edition history and redesign comments module UI. diff --git a/app/coffee/modules/admin/memberships.coffee b/app/coffee/modules/admin/memberships.coffee index 261d2f3f..1ab6c54f 100644 --- a/app/coffee/modules/admin/memberships.coffee +++ b/app/coffee/modules/admin/memberships.coffee @@ -241,16 +241,19 @@ module.directive("tgMemberships", ["$tgTemplate", "$compile", MembershipsDirecti ## Member Avatar Directive ############################################################################# -MembershipsRowAvatarDirective = ($log, $template, $translate, $compile) -> +MembershipsRowAvatarDirective = ($log, $template, $translate, $compile, avatarService) -> template = $template.get("admin/memberships-row-avatar.html", true) link = ($scope, $el, $attrs) -> pending = $translate.instant("ADMIN.MEMBERSHIP.STATUS_PENDING") render = (member) -> + avatar = avatarService.getAvatar(member) + ctx = { full_name: if member.full_name then member.full_name else "" email: if member.user_email then member.user_email else member.email - imgurl: if member.photo then member.photo else "/" + window._version + "/images/unnamed.png" + imgurl: avatar.url + bg: avatar.bg pending: if !member.is_user_active then pending else "" isOwner: member.is_owner } @@ -272,7 +275,7 @@ MembershipsRowAvatarDirective = ($log, $template, $translate, $compile) -> return {link: link} -module.directive("tgMembershipsRowAvatar", ["$log", "$tgTemplate", '$translate', "$compile", MembershipsRowAvatarDirective]) +module.directive("tgMembershipsRowAvatar", ["$log", "$tgTemplate", '$translate', "$compile", "tgAvatarService", MembershipsRowAvatarDirective]) ############################################################################# diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index eb4e2d6e..e42ad1a4 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -126,7 +126,7 @@ module.directive("tgSprintProgressbar", SprintProgressBarDirective) ## Created-by display directive ############################################################################# -CreatedByDisplayDirective = ($template, $compile, $translate, $navUrls)-> +CreatedByDisplayDirective = ($template, $compile, $translate, $navUrls, avatarService)-> # Display the owner information (full name and photo) and the date of # creation of an object (like USs, tasks and issues). # @@ -141,11 +141,15 @@ CreatedByDisplayDirective = ($template, $compile, $translate, $navUrls)-> link = ($scope, $el, $attrs) -> bindOnce $scope, $attrs.ngModel, (model) -> if model? + + avatar = avatarService.getAvatar(model.owner_extra_info) $scope.owner = model.owner_extra_info or { full_name_display: $translate.instant("COMMON.EXTERNAL_USER") - photo: "/" + window._version + "/images/user-noimage.png" } + $scope.owner.avatar = avatar.url + $scope.owner.bg = avatar.bg + $scope.url = if $scope.owner?.is_active then $navUrls.resolve("user-profile", {username: $scope.owner.username}) else "" @@ -162,11 +166,11 @@ CreatedByDisplayDirective = ($template, $compile, $translate, $navUrls)-> templateUrl: "common/components/created-by.html" } -module.directive("tgCreatedByDisplay", ["$tgTemplate", "$compile", "$translate", "$tgNavUrls", +module.directive("tgCreatedByDisplay", ["$tgTemplate", "$compile", "$translate", "$tgNavUrls", "tgAvatarService", CreatedByDisplayDirective]) -UserDisplayDirective = ($template, $compile, $translate, $navUrls)-> +UserDisplayDirective = ($template, $compile, $translate, $navUrls, avatarService)-> # Display the user information (full name and photo). # # Example: @@ -177,12 +181,15 @@ UserDisplayDirective = ($template, $compile, $translate, $navUrls)-> link = ($scope, $el, $attrs) -> id = $attrs.tgUserId - console.log($scope.usersById[id]) $scope.user = $scope.usersById[id] or { full_name_display: $translate.instant("COMMON.EXTERNAL_USER") - photo: "/" + window._version + "/images/user-noimage.png" } + avatar = avatarService.getAvatar($scope.usersById[id] or null) + + $scope.user.avatar = avatar.url + $scope.user.bg = avatar.bg + $scope.url = if $scope.user.is_active then $navUrls.resolve("user-profile", {username: $scope.user.username}) else "" $scope.$on "$destroy", -> @@ -195,7 +202,7 @@ UserDisplayDirective = ($template, $compile, $translate, $navUrls)-> templateUrl: "common/components/user-display.html" } -module.directive("tgUserDisplay", ["$tgTemplate", "$compile", "$translate", "$tgNavUrls", +module.directive("tgUserDisplay", ["$tgTemplate", "$compile", "$translate", "$tgNavUrls", "tgAvatarService", UserDisplayDirective]) ############################################################################# @@ -205,7 +212,6 @@ module.directive("tgUserDisplay", ["$tgTemplate", "$compile", "$translate", "$tg WatchersDirective = ($rootscope, $confirm, $repo, $modelTransform, $template, $compile, $translate) -> # You have to include a div with the tg-lb-watchers directive in the page # where use this directive - template = $template.get("common/components/watchers.html", true) link = ($scope, $el, $attrs, $model) -> isEditable = -> @@ -242,13 +248,8 @@ WatchersDirective = ($rootscope, $confirm, $repo, $modelTransform, $template, $c $confirm.notify("error") renderWatchers = (watchers) -> - ctx = { - watchers: watchers - isEditable: isEditable() - } - - html = $compile(template(ctx))($scope) - $el.html(html) + $scope.watchers = watchers + $scope.isEditable = isEditable() $el.on "click", ".js-delete-watcher", (event) -> event.preventDefault() @@ -282,7 +283,12 @@ WatchersDirective = ($rootscope, $confirm, $repo, $modelTransform, $template, $c $scope.$on "$destroy", -> $el.off() - return {link:link, require:"ngModel"} + return { + scope: true, + templateUrl: "common/components/watchers.html", + link:link, + require:"ngModel" + } module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQueueModelTransformation", "$tgTemplate", "$compile", "$translate", WatchersDirective]) @@ -292,7 +298,7 @@ module.directive("tgWatchers", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgQueue ## Assigned to directive ############################################################################# -AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $template, $translate, $compile, $currentUserService) -> +AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $template, $translate, $compile, $currentUserService, avatarService) -> # You have to include a div with the tg-lb-assignedto directive in the page # where use this directive template = $template.get("common/components/assigned-to.html", true) @@ -326,20 +332,23 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $ return transform renderAssignedTo = (assignedObject) -> + avatar = avatarService.getAvatar(assignedObject?.assigned_to_extra_info) + bg = null + if assignedObject?.assigned_to? fullName = assignedObject.assigned_to_extra_info.full_name_display - photo = assignedObject.assigned_to_extra_info.photo isUnassigned = false + bg = avatar.bg else fullName = $translate.instant("COMMON.ASSIGNED_TO.ASSIGN") - photo = "/#{window._version}/images/unnamed.png" isUnassigned = true isIocaine = assignedObject?.is_iocaine ctx = { fullName: fullName - photo: photo + avatar: avatar.url + bg: bg isUnassigned: isUnassigned isEditable: isEditable() isIocaine: isIocaine @@ -386,7 +395,7 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $modelTransform, $ require:"ngModel" } -module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$translate", "$compile","tgCurrentUserService", +module.directive("tgAssignedTo", ["$rootScope", "$tgConfirm", "$tgRepo", "$tgLoading", "$tgQueueModelTransformation", "$tgTemplate", "$translate", "$compile","tgCurrentUserService", "tgAvatarService", AssignedToDirective]) @@ -776,7 +785,7 @@ ListItemTaskStatusDirective = -> module.directive("tgListitemTaskStatus", ListItemTaskStatusDirective) -ListItemAssignedtoDirective = ($template, $translate) -> +ListItemAssignedtoDirective = ($template, $translate, avatarService) -> template = $template.get("common/components/list-item-assigned-to-avatar.html", true) link = ($scope, $el, $attrs) -> @@ -784,19 +793,22 @@ ListItemAssignedtoDirective = ($template, $translate) -> item = $scope.$eval($attrs.tgListitemAssignedto) ctx = { name: $translate.instant("COMMON.ASSIGNED_TO.NOT_ASSIGNED"), - imgurl: "/#{window._version}/images/unnamed.png" } member = usersById[item.assigned_to] + avatar = avatarService.getAvatar(member) + + ctx.imgurl = avatar.url + ctx.bg = avatar.bg + if member - ctx.imgurl = member.photo ctx.name = member.full_name_display $el.html(template(ctx)) return {link:link} -module.directive("tgListitemAssignedto", ["$tgTemplate", "$translate", ListItemAssignedtoDirective]) +module.directive("tgListitemAssignedto", ["$tgTemplate", "$translate", "tgAvatarService", ListItemAssignedtoDirective]) ListItemIssueStatusDirective = -> diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index 3fa982a3..0c8a8b4a 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -502,7 +502,7 @@ module.directive("tgLbCreateBulkUserstories", [ ## AssignedTo Lightbox Directive ############################################################################# -AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationService, $template, $compile) -> +AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationService, $template, $compile, avatarService) -> link = ($scope, $el, $attrs) -> selectedUser = null selectedItem = null @@ -530,10 +530,17 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic users = _.sortBy(users, (o) -> if o.id is $scope.user.id then 0 else o.id) users = _.filter(users, _.partial(filterUsers, text)) if text? + visibleUsers = _.slice(users, 0, 5) + + visibleUsers = _.map visibleUsers, (user) -> + user.avatar = avatarService.getAvatar(user) + + selected.avatar = avatarService.getAvatar(selected) + ctx = { selected: selected users: _.slice(users, 0, 5) - showMore: users.length > 5 + showMore: visibleUsers } html = usersTemplate(ctx) @@ -597,7 +604,7 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic } -module.directive("tgLbAssignedto", ["lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", AssignedToLightboxDirective]) +module.directive("tgLbAssignedto", ["lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", "tgAvatarService", AssignedToLightboxDirective]) ############################################################################# diff --git a/app/coffee/modules/issues/list.coffee b/app/coffee/modules/issues/list.coffee index 6dee03b5..a8cfa0b9 100644 --- a/app/coffee/modules/issues/list.coffee +++ b/app/coffee/modules/issues/list.coffee @@ -801,9 +801,9 @@ module.directive("tgIssueStatusInlineEdition", ["$tgRepo", "$tgTemplate", "$root ## Issue assigned to Directive ############################################################################# -IssueAssignedToInlineEditionDirective = ($repo, $rootscope, $translate) -> +IssueAssignedToInlineEditionDirective = ($repo, $rootscope, $translate, avatarService) -> template = _.template(""" - <%- name %> + <%- name %>
<%- name %>
""") @@ -815,9 +815,14 @@ IssueAssignedToInlineEditionDirective = ($repo, $rootscope, $translate) -> } member = $scope.usersById[issue.assigned_to] + + avatar = avatarService.getAvatar(member) + ctx.imgurl = avatar.url + ctx.bg = null + if member ctx.name = member.full_name_display - ctx.imgurl = member.photo + ctx.bg = avatar.bg $el.find(".avatar").html(template(ctx)) $el.find(".issue-assignedto").attr('title', ctx.name) @@ -849,5 +854,5 @@ IssueAssignedToInlineEditionDirective = ($repo, $rootscope, $translate) -> return {link: link} -module.directive("tgIssueAssignedToInlineEdition", ["$tgRepo", "$rootScope", "$translate" +module.directive("tgIssueAssignedToInlineEdition", ["$tgRepo", "$rootScope", "$translate", "tgAvatarService", IssueAssignedToInlineEditionDirective]) diff --git a/app/coffee/modules/kanban/main.coffee b/app/coffee/modules/kanban/main.coffee index 8b99feba..1d95219e 100644 --- a/app/coffee/modules/kanban/main.coffee +++ b/app/coffee/modules/kanban/main.coffee @@ -524,11 +524,11 @@ module.directive("tgKanbanWipLimit", KanbanWipLimitDirective) ## Kanban User Directive ############################################################################# -KanbanUserDirective = ($log, $compile, $translate) -> +KanbanUserDirective = ($log, $compile, $translate, avatarService) -> template = _.template("""
class="not-clickable"<% } %>> - <%- name %> + <%- name %>
""") @@ -551,16 +551,20 @@ KanbanUserDirective = ($log, $compile, $translate) -> render(user) render = (user) -> + avatar = avatarService.getAvatar(user) + if user is undefined ctx = { name: $translate.instant("COMMON.ASSIGNED_TO.NOT_ASSIGNED"), - imgurl: "/#{window._version}/images/unnamed.png", - clickable: clickable + imgurl: avatar.url, + clickable: clickable, + bg: null } else ctx = { name: user.full_name_display, - imgurl: user.photo, + imgurl: avatar.url, + bg: avatar.bg, clickable: clickable } @@ -593,4 +597,4 @@ KanbanUserDirective = ($log, $compile, $translate) -> return {link: link, require:"ngModel"} -module.directive("tgKanbanUserAvatar", ["$log", "$compile", "$translate", KanbanUserDirective]) +module.directive("tgKanbanUserAvatar", ["$log", "$compile", "$translate", "tgAvatarService", KanbanUserDirective]) diff --git a/app/coffee/modules/related-tasks.coffee b/app/coffee/modules/related-tasks.coffee index 3d6e197d..7bf26155 100644 --- a/app/coffee/modules/related-tasks.coffee +++ b/app/coffee/modules/related-tasks.coffee @@ -265,9 +265,9 @@ RelatedTasksDirective = ($repo, $rs, $rootscope) -> module.directive("tgRelatedTasks", ["$tgRepo", "$tgResources", "$rootScope", RelatedTasksDirective]) -RelatedTaskAssignedToInlineEditionDirective = ($repo, $rootscope, $translate) -> +RelatedTaskAssignedToInlineEditionDirective = ($repo, $rootscope, $translate, avatarService) -> template = _.template(""" - <%- name %> + <%- name %>
<%- name %>
""") @@ -275,11 +275,15 @@ RelatedTaskAssignedToInlineEditionDirective = ($repo, $rootscope, $translate) -> updateRelatedTask = (task) -> ctx = { name: $translate.instant("COMMON.ASSIGNED_TO.NOT_ASSIGNED"), - imgurl: "/" + window._version + "/images/unnamed.png" } + member = $scope.usersById[task.assigned_to] + + avatar = avatarService.getAvatar(member) + ctx.imgurl = avatar.url + ctx.bg = avatar.bg + if member - ctx.imgurl = member.photo ctx.name = member.full_name_display $el.find(".avatar").html(template(ctx)) @@ -318,5 +322,5 @@ RelatedTaskAssignedToInlineEditionDirective = ($repo, $rootscope, $translate) -> return {link: link} -module.directive("tgRelatedTaskAssignedToInlineEdition", ["$tgRepo", "$rootScope", "$translate", +module.directive("tgRelatedTaskAssignedToInlineEdition", ["$tgRepo", "$rootScope", "$translate", "tgAvatarService", RelatedTaskAssignedToInlineEditionDirective]) diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee index d52d8dff..15b3b753 100644 --- a/app/coffee/modules/taskboard/main.coffee +++ b/app/coffee/modules/taskboard/main.coffee @@ -463,7 +463,7 @@ module.directive("tgTaskboardSquishColumn", ["$tgResources", TaskboardSquishColu ## Taskboard User Directive ############################################################################# -TaskboardUserDirective = ($log, $translate) -> +TaskboardUserDirective = ($log, $translate, avatarService) -> clickable = false link = ($scope, $el, $attrs) -> @@ -473,16 +473,18 @@ TaskboardUserDirective = ($log, $translate) -> $scope.$watch 'task.assigned_to', (assigned_to) -> user = $scope.usersById[assigned_to] + avatar = avatarService.getAvatar(user) + if user is undefined _.assign($scope, { name: $translate.instant("COMMON.ASSIGNED_TO.NOT_ASSIGNED"), - imgurl: "/#{window._version}/images/unnamed.png", + avatar: avatar, clickable: clickable }) else _.assign($scope, { name: user.full_name_display, - imgurl: user.photo, + avatar: avatar, clickable: clickable }) @@ -519,4 +521,4 @@ TaskboardUserDirective = ($log, $translate) -> } -module.directive("tgTaskboardUserAvatar", ["$log", "$translate", TaskboardUserDirective]) +module.directive("tgTaskboardUserAvatar", ["$log", "$translate", "tgAvatarService", TaskboardUserDirective]) diff --git a/app/coffee/modules/wiki/main.coffee b/app/coffee/modules/wiki/main.coffee index f10479b9..745d22c0 100644 --- a/app/coffee/modules/wiki/main.coffee +++ b/app/coffee/modules/wiki/main.coffee @@ -174,7 +174,7 @@ module.controller("WikiDetailController", WikiDetailController) ## Wiki Summary Directive ############################################################################# -WikiSummaryDirective = ($log, $template, $compile, $translate) -> +WikiSummaryDirective = ($log, $template, $compile, $translate, avatarService) -> template = $template.get("wiki/wiki-summary.html", true) link = ($scope, $el, $attrs, $model) -> @@ -184,10 +184,12 @@ WikiSummaryDirective = ($log, $template, $compile, $translate) -> else user = $scope.usersById[wiki.last_modifier] + avatar = avatarService.getAvatar(user) + if user is undefined - user = {name: "unknown", imgUrl: "/" + window._version + "/images/user-noimage.png"} + user = {name: "unknown", avatar: avatar} else - user = {name: user.full_name_display, imgUrl: user.photo} + user = {name: user.full_name_display, avatar: avatar} ctx = { totalEditions: wiki.editions @@ -211,7 +213,7 @@ WikiSummaryDirective = ($log, $template, $compile, $translate) -> require: "ngModel" } -module.directive("tgWikiSummary", ["$log", "$tgTemplate", "$compile", "$translate", WikiSummaryDirective]) +module.directive("tgWikiSummary", ["$log", "$tgTemplate", "$compile", "$translate", "tgAvatarService", WikiSummaryDirective]) ############################################################################# diff --git a/app/images/user-avatars/user-avatar-01.png b/app/images/user-avatars/user-avatar-01.png new file mode 100644 index 0000000000000000000000000000000000000000..6695e8f6af78b3140c9ab02e2581847c0f23efb4 GIT binary patch literal 1088 zcmeAS@N?(olHy`uVBq!ia0y~yVAuk}9Lx+13|zwB7#J8BGXi`|BCE!eSDVQt}E)%Bq@L+Byb?M#d(VR@OGQ_KwbO?jD}rKE45g zK_Q{xkuh=c$tkJn8JStxxrL<_mDP1k&8;0hy?qlVO`bY^#=^x*m#tX2X5ISDTXyW) zbKvlaGuQ9ld-UYl^A~U4zW?y?)0b~Qe*XIX=ijf=Wl{_bOah)Rjv*Dd-rl+%EF37& z_HkGVSoXTS9&K8p6xq%ck1Rqu`|!>mR*~+)Vjv&+?o6^$y-0%KFvEj zy(sIX@y~T9`OiNI)--)O^GkM+RnX(^MftxNz2}{rqt-p|+gBa)H-g)lZ z$vu5hrqlh~7SHzUo_S`*>EFt3`#J2>ehT!-@y;)r4)c7U%w0<*Z>y}cG5du2^4^Mm zwt2pJXU&x8U})gn^SXGu%=6z`8Kc>?zAJln##E?rzh3z%T+-mP?bnrezkN$_{bjoH z{=f2jd9nQa?^b=fzju-{G!!;G`+ceGlxDj9<6y&!Py3U!KZVcbdcRbArSHAJ^DjLQ z%$rfepDhHnNX@FLEp7Rlbh|G`rf+7Jlvt(;!QzkMz-#d@VpiHc9Fe<0VxF#kF6*2U FngD%6z1;u+ literal 0 HcmV?d00001 diff --git a/app/images/user-avatars/user-avatar-02.png b/app/images/user-avatars/user-avatar-02.png new file mode 100644 index 0000000000000000000000000000000000000000..16034e353e6d86b919e6d47ad9adb81b975c3894 GIT binary patch literal 1005 zcmeAS@N?(olHy`uVBq!ia0y~yVAuk}9Lx+13|zwB7#J8Btpj{QTp1V`{*MB(fQ_w{ z1_J{_MoExgFaskq3kN3;AHRU0kcg<5l$^Yxs=BtGfuX6jt&_96r>}o-Sa?KCTzo=e zYFb8SPHtX7NqJRuO>JF$Q*S9f3k#7Q$|&7HS!>9XZ3R|vzPCG{L^2Qdx(L7@u#PYV@SoVw|63=g#slGeDsXb3eYlUzNDhP<-}4C z-#{Oc-1wMDI^W;^u5Vg*u3FFL>;Z$~v@8Grccss>{7}8)p}~i~#{z;d5Ey*7>e=e& z@6&3g^03tA-wmJr{NF29UMZ7|3;*AKqf+a7phB^}_rUE6W|5*?6V4*nfE)ymC&6?VSx>dORz#eD_~o zdd$q{;Nq`sD@8PR1x>m2bjy+Q@-GwXX5J2c*>Czq`0=CscQ0xmFN(adz_GcH%eF|T zH(sjb-Ez&{FO28f%_w&^|K@5PvSs7KYZs?Z5BfOu$w$MwPp(nXlgidCd>j9rfmh1p z#g7H2^YzSrzkGajTXM@gW1H~u*$*EWWPnU$tDSmL*guq0*vfM0FPYQ4!R?&2_Rn_P zfaQ3t3chUmvu5X}?W?UG>|%##0EfT=$L4KKeyK}ezPM=mQ~j&Ms*Q&0#4j%P_C0^B z5v*dpnD(ly+UTv)#arf8zr1vPx%>^M@)Hsv`(sPY*1pCzY3hatQn_sbKAab5r{2v8_81$|) zbYx&)=qd^F3ua(sVrF6E<`WPS77-Jdkdl^>m6KOgR#8<`*VNM1)zddHG&V7_vbM3c zcW`v^@beE22@Q{qjf+o6OiE5k&&bZn%P%M{si>}LYVGLk?&Jj z;i4r=m#tX6cHR0d+js2Sf8fxOqsLF2JazuU)tk3&-??}H$+PD#U%h_&{==uw-+w*% zBk+%bfoZp=i(^Q|t+zL?2MY&^G<@t96qVL8OLE0#W~(b3UyYL&IR>5soFen|)IIpg_K za^=bVlF4>jyiz6^7Z!+{RD~vQoYvo(bUr2UMUlPvO6Pm09+j@%a&mQX#KPwv;=(z< zKW-~I(W0}j^8a~eUMZ6oT29yhPCT}3$&nz!sA?@hR>I6pP-;Q8sdm8(3IcV!iq zy{_E*aO%2kET1C9L3TK{OW*w#Q}x5|*7G;lf~yWQ%U()odb{iQWV<{qQILG|I-6f% z%WjBPzrWP6<@(JHldGElu8!`t(YPFKRuy&bS@U^y*^7J47dSSv@m4i1-u_;v?BVV& z&SLRys_pXycR&5L(R1cU&*Nn$CfodWJvYP8wsZfcA5Z@+dAojjdfCqpYAhxh7aqF! z|NB4p^@c6?_7%71Epuv^cgeo@)vK1;^!<5#?7e;UQ`J&FUbg+U{fxx*jklYACRB&; z`p;b$|4K_Cu`?;_gthbj3s>1LEN~1ruKK=V%Ei$9<)K05n|G~REnzjQJXRO%4>sG? zuH{P|zrUz9dYonOqQkDzBEC)&QhD>H1(_MFobCK;`?D2Sz2q;{7R%m=+`H!Zy`GoPw(d-u!w8A9^zz?}rff53 zFEJL^+9KY_28xtd&gR>_{r4>4dv#;Qdig^6%FcKEFJ?1?t(MLEl9XA@dE}{c7_+Kg zw4Sk&+5@v`7fNnSS9SWj)C%PCaKWmRaxZTrYo^6tTRfZnYnxQ?X`zeEWyb7*Gc z-PLtLw|vPDsnx~1cHU~)toK{>cLQfNI6kcIy!DUBHFa@uaS_S+&+u1SJ*Q!U@<|2; O1_n=8KbLh*2~7ZW7gLA; literal 0 HcmV?d00001 diff --git a/app/images/user-avatars/user-avatar-04.png b/app/images/user-avatars/user-avatar-04.png new file mode 100644 index 0000000000000000000000000000000000000000..4c456402b4db4580f6f34688bfc8dc94a5565953 GIT binary patch literal 1170 zcmeAS@N?(olHy`uVBq!ia0y~yVAuk}9Lx+13|zwB7#J8BO9FgCTp1V`{*MCsf)8ae zE({C|)g?iG!3>Pdtn56zd;)?(BBEmA5|UEVvT_P4s_GhAI{F4i#-?WGme%%8&aQ6m z9^S!Wkx|hxaq$U>X<6AhxdnwK<+XM7jm<4>?Hye`{Szinoi=mU?78z7EMBsF)!KC% zHg4Lob=!`eyAB>ce(Lm@i6Kl%&|OhKM5jv*Dd-rjv} z9ug?i{;_vLOwh`gi&|8;V%?Xv#VB10Sf_Z&_2shH8vkoful_rC>zk|k#g>cbJ%4Lg z{rk)u^Xj*ya|-)9IyyT39bf0i^6Grf-KqYKY?~Y2Me2r~jaSMf7Cmj1o&MVTkF{qtcLlLUl`c5CDP;Psb*B#qURdA=R{g?$;cKfUM^f*< zEqqzGKYv-cl_;mxTGpK}Dl;zcl$-@I&#{?puX5D}{ea(`@lKr1XZO8Z>Y)FrY})i| znKK2$onqMZaw3_aO4A$qVkiCj)El=;b*|~+^%~DpI+xz7ygz%(+}Km*+s|LJoNKqi z`b*(uk-hToo$fzel(|w3>XwqYiys5;uljfOeD=PHOwQJ)_0Bg!JoH~-*NTWu3rpPs zRbFJq{#n2pyP7!|>Vb?4_09WsE4|$BSXwvPDs_QV`W?+RYct!RS{FFJXZ-Te?d8Fa z_sil+lY_LjuZiBO$otyk)71A{?RrFR-({aaJ-^l@H+$9h#fLV9RM_uTV_vCqM;Gd- zFJ+s#_iaqRWa~ORZcFgu7wNp0ZqMKMKJs3JDa-u%dQ(^5iu1~?(h;jk`<293-fLF! zfk6lq1&+;Zva-J}n7*92=(NrY=KV{)I|rS4y0T@_Y26oIvNK8pS->&n*nDsE3n|+? zuT$3Z=C69&zGvgl!;&voblcc|jONXC2wt}V7Owf<1L|&PseO4I@*129G_K@p*q;-O UG5EZffq{X+)78&qol`;+04}cLYybcN literal 0 HcmV?d00001 diff --git a/app/images/user-avatars/user-avatar-05.png b/app/images/user-avatars/user-avatar-05.png new file mode 100644 index 0000000000000000000000000000000000000000..265f94f0e4830c9c3df124fb8671b0d81282e58e GIT binary patch literal 1093 zcmeAS@N?(olHy`uVBq!ia0y~yVAuk}9Lx+13|zwB7#J8BBLjRwTp1V`{*MA00M$$9 z-ZC&S6qN+|1v4-*F|%@V@d^kEi%QDKDk`g}Y3k}3nwnc$S=-pzJ2*PI`}hR}hekw3 z#V01EWfql`R@c-vw6sm0GHvFZx$_n*UAA)d`VAX5Z`rzS$Ijh*_8&TY{Pd;E*RJ2b zbN}I^$4{TXdj0m@`wt&Keg5+G+xH*8|NKpNKGMy=z@+Kv;uunK>+PM$;L8aTZ4X11 zbTxD=P|(`Vu~s45u{$EQkdg7`!Psw0*M9iVFVp|&%Y(}%e}yhT+~+_0`&rZ6^jV(i zk1Sl^KrriB@7b)wj?eXSt}#fNWL#L_*vuyqTNtV({-tSN+K$Ufho4{i{oq2bnfdmY z&sagyY`juiL`y!k-oE~3<89{IFWM`&$IqX?Q?+L8>`UJj9<~HHows0x8l}e-tL`h_ zzi-)R^XxlX-R}FReVn!bp2ww?cjkQgIQdHAEO~>wZc+xmTk<^R)0VC|7hd5yKXplJ z?b6e7yS)3WR;Syr@Is9G?I!=CE8IJ1cjmv3%WiLrTl-~}n%rCO&r28YPxraIz1+0w zdW8_gc~)I7HZH%EE9sf*ZF?;KHE*U%+zwL>-v~G9|1w^$J@&s`QD>`_Yqd(aE1=j&X-pCsUCJvFHOny>8ku&qxN6F>eBVI^G`Re z>+v>UvWjD|HxDR?7dQHudWOHTOzyqp@$$toyO$B)kG@^_c-QwQoh9qPJl&nRpj@~9 zt^+8fO)@UjS8ac^?OyJCPszJCO}8IqSg&B6V>dbFG^aW=HcDzI8GcyMnBv#n%cU|{fc^>bP0l+XkKBCNP0 literal 0 HcmV?d00001 diff --git a/app/modules/components/avatar/avatar.directive.coffee b/app/modules/components/avatar/avatar.directive.coffee new file mode 100644 index 00000000..a8ac3a29 --- /dev/null +++ b/app/modules/components/avatar/avatar.directive.coffee @@ -0,0 +1,49 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: avatar.directive.coffee +### + +AvatarDirective = (avatarService) -> + link = (scope, el, attrs) -> + if attrs.tgAvatarBig + attributeName = 'avatarBig' + else + attributeName = 'avatar' + + unwatch = scope.$watch attributeName, (user) -> + avatar = avatarService.getAvatar(user, attributeName) + + el.attr('src', avatar.url) + if avatar.bg + el.css('background', avatar.bg) + + unwatch() if user + + return { + link: link + scope: { + avatar: "=tgAvatar" + avatarBig: "=tgAvatarBig" + } + } + +AvatarDirective.$inject = [ + 'tgAvatarService' +] + +angular.module("taigaComponents").directive("tgAvatar", AvatarDirective) +angular.module("taigaComponents").directive("tgAvatarBig", AvatarDirective) diff --git a/app/modules/external-apps/external-app.jade b/app/modules/external-apps/external-app.jade index 59d42918..0cb5600f 100644 --- a/app/modules/external-apps/external-app.jade +++ b/app/modules/external-apps/external-app.jade @@ -9,7 +9,10 @@ section.external-app-wrapper div.user-card.avatar .card-inner div.user-image - img(ng-src="{{::vm.user.get('photo')}}", alt="{{::vm.user.get('full_name_display')}}") + img( + tg-avatar="vm.user" + alt="{{::vm.user.get('full_name_display')}}" + ) div.user-data h3 {{ ::vm.user.get("full_name_display") }} p {{ ::vm.user.get("email") }} diff --git a/app/modules/history/comments/comment.jade b/app/modules/history/comments/comment.jade index 38d8a2a3..d607b991 100644 --- a/app/modules/history/comments/comment.jade +++ b/app/modules/history/comments/comment.jade @@ -2,7 +2,7 @@ include ../../../partials/common/components/wysiwyg.jade .comment-wrapper(ng-if="!vm.comment.delete_comment_date") img.comment-avatar( - ng-src="{{vm.comment.user.photo}}" + tg-avatar="vm.comment.user" ng-alt="{{vm.comment.user.name}}" ) .comment-main diff --git a/app/modules/history/history-lightbox/history-entry.jade b/app/modules/history/history-lightbox/history-entry.jade index 4df2d67b..b3c5ca4a 100644 --- a/app/modules/history/history-lightbox/history-entry.jade +++ b/app/modules/history/history-lightbox/history-entry.jade @@ -1,6 +1,6 @@ .entry-wrapper img.entry-avatar( - ng-src="{{entry.user.photo}}" + tg-avatar="entry.user" ng-alt="{{entry.user.name}}" ) .entry-main diff --git a/app/modules/history/history/history.jade b/app/modules/history/history/history.jade index d53d022c..9e3836fb 100644 --- a/app/modules/history/history/history.jade +++ b/app/modules/history/history/history.jade @@ -2,7 +2,7 @@ section.activities .activities-wrapper .activity(ng-repeat="activity in activities track by activity.id") img.activity-avatar( - ng-src="{{activity.user.photo}}" + tg-avatar="activity.user" ng-alt="{{activity.user.name}}" ) .activity-main diff --git a/app/modules/home/duties/duty.jade b/app/modules/home/duties/duty.jade index eb7e2601..970a3a4c 100644 --- a/app/modules/home/duties/duty.jade +++ b/app/modules/home/duties/duty.jade @@ -11,8 +11,8 @@ a.list-itemtype-ticket( div.list-itemtype-avatar(ng-if="vm.type == 'watching'") img( ng-if="vm.duty.get('assigned_to_extra_info')" - ng-src="{{ ::vm.duty.get('assigned_to_extra_info').get('photo') }}" title="{{ ::vm.duty.get('assigned_to_extra_info').get('full_name_display') }}" + tg-avatar="vm.duty.get('assigned_to_extra_info')" ) img( ng-if="!vm.duty.get('assigned_to_extra_info')" diff --git a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade index 8131593f..2db8f69a 100644 --- a/app/modules/navigation-bar/dropdown-user/dropdown-user.jade +++ b/app/modules/navigation-bar/dropdown-user/dropdown-user.jade @@ -3,11 +3,10 @@ a.user-avatar( title="{{ vm.user.get('full_name_display') }}" ) {{ vm.user.get('full_name_display') }} img( - ng-src="{{ vm.user.get('photo') }}" + tg-avatar="vm.user" alt="{{ vm.user.get('full_name_display') }}" - width="48px" height="40px" - ) + ) div.navbar-dropdown.dropdown-user ul diff --git a/app/modules/navigation-bar/navigation-bar.scss b/app/modules/navigation-bar/navigation-bar.scss index 8cdda49f..a04122cc 100644 --- a/app/modules/navigation-bar/navigation-bar.scss +++ b/app/modules/navigation-bar/navigation-bar.scss @@ -77,7 +77,7 @@ $dropdown-width: 350px; } img { height: 2.5rem; - padding-left: .5rem; + margin-left: .5rem; vertical-align: middle; } svg { diff --git a/app/modules/profile/profile-bar/profile-bar.jade b/app/modules/profile/profile-bar/profile-bar.jade index 8a53a4ad..a3ae25ec 100644 --- a/app/modules/profile/profile-bar/profile-bar.jade +++ b/app/modules/profile/profile-bar/profile-bar.jade @@ -1,6 +1,9 @@ section.profile-bar div.profile-image-wrapper(ng-class="::{'is-current-user': vm.isCurrentUser}") - img.profile-img(ng-src="{{::vm.user.get('big_photo')}}", alt="{{::vm.user.get('full_name')}}") + img.profile-img( + tg-avatar="vm.user" + alt="{{::vm.user.get('full_name')}}" + ) a.profile-edition(title="{{ 'USER.PROFILE.EDIT' | translate }}", tg-nav="user-settings-user-profile", translate="USER.PROFILE.EDIT") div.profile-data h1(ng-class="{'not-full-name': !vm.user.get('full_name')}") {{::vm.user.get("full_name_display")}} diff --git a/app/modules/profile/profile-contacts/profile-contacts.jade b/app/modules/profile/profile-contacts/profile-contacts.jade index c4dc8b71..5f8d2ef6 100644 --- a/app/modules/profile/profile-contacts/profile-contacts.jade +++ b/app/modules/profile/profile-contacts/profile-contacts.jade @@ -14,7 +14,10 @@ section.profile-contacts div.list-itemtype-user(tg-repeat="contact in ::vm.contacts") a.list-itemtype-avatar(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name')}}") - img(ng-src="{{::contact.get('photo')}}", alt="{{::contact.get('full_name')}}") + img( + tg-avatar="contact" + alt="{{::contact.get('full_name')}}" + ) div.list-itemtype-user-data h2 diff --git a/app/modules/profile/profile-favs/items/ticket.jade b/app/modules/profile/profile-favs/items/ticket.jade index 7f974ea4..170109d2 100644 --- a/app/modules/profile/profile-favs/items/ticket.jade +++ b/app/modules/profile/profile-favs/items/ticket.jade @@ -3,11 +3,11 @@ href="" ng-if="::vm.item.get('assigned_to')" tg-nav="user-profile:username=vm.item.get('assigned_to_username')" - title="{{ ::vm.item.get('assigned_to_full_name') }}" + title="{{ ::vm.item.getIn(['assigned_to_extra_info', 'full_name_display']) }}" ) img( - ng-src="{{ ::vm.item.get('assigned_to_photo') }}", - alt="{{ ::vm.item.get('assigned_to_full_name') }}" + tg-avatar="{{ ::vm.item.get('assigned_to_extra_info') }}", + alt="{{ ::vm.item.getIn(['assigned_to_extra_info', 'full_name_display']) }}" ) a.list-itemtype-avatar( diff --git a/app/modules/profile/profile-projects/profile-projects.jade b/app/modules/profile/profile-projects/profile-projects.jade index a1d361f8..df8de196 100644 --- a/app/modules/profile/profile-projects/profile-projects.jade +++ b/app/modules/profile/profile-projects/profile-projects.jade @@ -63,4 +63,4 @@ section.profile-projects tg-nav="user-profile:username=contact.get('username')" title="{{::contact.get('full_name')}}" ) - img(ng-src="{{::contact.get('photo')}}") + tg-avatar="contact" diff --git a/app/modules/projects/project/project.jade b/app/modules/projects/project/project.jade index 731d3d59..cc3acb5a 100644 --- a/app/modules/projects/project/project.jade +++ b/app/modules/projects/project/project.jade @@ -68,7 +68,10 @@ div.wrapper tg-nav="user-profile:username=member.get('username')", title="{{::member.get('full_name')}}" ) - img(ng-src="{{::member.get('photo')}}", alt="{{::member.get('full_name')}}") + img( + tg-avatar="member" + alt="{{::member.get('full_name')}}" + ) tg-svg( ng-if="member.get('id') == vm.project.getIn(['owner', 'id'])" svg-icon="icon-badge" diff --git a/app/modules/services/avatar.service.coffee b/app/modules/services/avatar.service.coffee new file mode 100644 index 00000000..add6b311 --- /dev/null +++ b/app/modules/services/avatar.service.coffee @@ -0,0 +1,93 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: avatar.service.coffee +### + +class AvatarService + constructor: () -> + IMAGES = [ + "/#{window._version}/images/user-avatars/user-avatar-01.png" + "/#{window._version}/images/user-avatars/user-avatar-02.png" + "/#{window._version}/images/user-avatars/user-avatar-03.png" + "/#{window._version}/images/user-avatars/user-avatar-04.png" + "/#{window._version}/images/user-avatars/user-avatar-05.png" + ] + + COLORS = [ + "rgba( 178, 176, 204, 1 )" + "rgba( 183, 203, 131, 1 )" + "rgba( 210, 198, 139, 1 )" + "rgba( 178, 178, 178, 1 )" + "rgba( 247, 154, 154, 1 )" + ] + + @.logos = _.cartesianProduct(IMAGES, COLORS) + + getDefault: (key) -> + idx = murmurhash3_32_gc(key, 42) %% @.logos.length + logo = @.logos[idx] + + return { src: logo[0], color: logo[1] } + + getUnnamed: () -> + return { + url: "/#{window._version}/images/unnamed.png" + } + + getAvatar: (user, type) -> + return getUnnamed() if !user + + avatarParamName = 'photo' + + if type == 'avatarBig' + avatarParamName = 'big_photo' + + photo = null + + if user instanceof Immutable.Map + gravatar = user.get('gravatar_id') + photo = user.get(avatarParamName) + else + gravatar = user.gravatar_id + photo = user[avatarParamName] + + return getUnnamed() if !gravatar + + if photo + return { + url: photo + } + else if location.host.indexOf('localhost') != -1 + root = location.protocol + '//' + location.host + logo = @.getDefault(gravatar) + + return { + url: root + logo.src, + bg: logo.color + } + else + root = location.protocol + '//' + location.host + logo = @.getDefault(gravatar) + + logoUrl = encodeURIComponent(root + logo.src) + + return { + url: 'https://www.gravatar.com/avatar/' + gravatar + "?d=" + logoUrl, + bg: logo.color + } + +angular.module("taigaCommon").service("tgAvatarService", AvatarService) 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 38a492ee..43604994 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 @@ -7,10 +7,16 @@ div.activity-item // profile image with url div.profile-contact-picture(ng-if="timeline.getIn(['data', 'user', 'is_profile_visible'])") a(tg-nav="user-profile:username=timeline.getIn(['data', 'user', 'username'])", title="{{::timeline.getIn(['data', 'user', 'name']) }}") - img(ng-src="{{::timeline.getIn(['data', 'user', 'photo']) || '/#{v}/images/user-noimage.png'}}", alt="{{::timeline.getIn(['data', 'user', 'name'])}}") + img( + tg-avatar="timeline.getIn(['data', 'user'])" + alt="{{::timeline.getIn(['data', 'user', 'name'])}}" + ) // profile image without url div.profile-contact-picture(ng-if="!timeline.getIn(['data', 'user', 'is_profile_visible'])") - img(ng-src="{{::timeline.getIn(['data', 'user', 'photo']) || '/#{v}/images/user-noimage.png'}}", alt="{{::timeline.getIn(['data', 'user', 'name'])}}") + img( + tg-avatar="timeline.getIn(['data', 'user'])" + alt="{{::timeline.getIn(['data', 'user', 'name'])}}" + ) p(tg-compile-html="timeline.get('title_html')") @@ -19,7 +25,10 @@ div.activity-item .activity-member-view(ng-if="::timeline.has('member')") a.profile-member-picture(tg-nav="user-profile:username=timeline.getIn(['member', 'user', 'username'])", title="{{::timeline.getIn(['member', 'user', 'name'])}}") - img(ng-src="{{::timeline.getIn(['member', 'user', 'photo'])}}", alt="{{::timeline.getIn(['member','user', 'name'])}}") + img( + tg-avatar="timeline.getIn(['member', 'user'])" + alt="{{::timeline.getIn(['member','user', 'name'])}}" + ) .activity-member-info a(tg-nav="user-profile:username=timeline.getIn(['member', 'user', 'username'])", title="{{::timeline.getIn(['member','user', 'name'])}}") span {{::timeline.getIn(['member','user', 'name'])}} diff --git a/app/modules/wiki/history/wiki-history-entry.jade b/app/modules/wiki/history/wiki-history-entry.jade index d8d2633c..aa25e102 100644 --- a/app/modules/wiki/history/wiki-history-entry.jade +++ b/app/modules/wiki/history/wiki-history-entry.jade @@ -1,6 +1,6 @@ .activity img.activity-avatar( - ng-src="{{singleHistoryEntry.user.photo}}" + tg-avatar="singleHistoryEntry.user" ng-alt="{{singleHistoryEntry.user.name}}" ) .activity-main diff --git a/app/partials/admin/admin-project-change-owner.jade b/app/partials/admin/admin-project-change-owner.jade index b7a4766a..f29c67d5 100644 --- a/app/partials/admin/admin-project-change-owner.jade +++ b/app/partials/admin/admin-project-change-owner.jade @@ -1,5 +1,5 @@ .owner-avatar - img(ng-src="{{owner.photo || '/#{v}/images/user-noimage.png'}}", alt="{{::owner.full_name_display}}") + img(tg-avatar="owner", alt="{{::owner.full_name_display}}") .owner-info .owner-info-title {{ 'ADMIN.PROJECT_PROFILE.PROJECT_OWNER' | translate }} diff --git a/app/partials/admin/admin-project-request-ownership.jade b/app/partials/admin/admin-project-request-ownership.jade index 713bd6b6..7e17b3e7 100644 --- a/app/partials/admin/admin-project-request-ownership.jade +++ b/app/partials/admin/admin-project-request-ownership.jade @@ -1,5 +1,5 @@ .owner-avatar - img(ng-src="{{owner.photo || '/#{v}/images/user-noimage.png'}}", alt="{{::owner.full_name_display}}") + img(tg-avatar="owner", alt="{{::owner.full_name_display}}") .owner-info .title {{ 'ADMIN.PROJECT_PROFILE.PROJECT_OWNER' | translate }} diff --git a/app/partials/admin/memberships-row-avatar.jade b/app/partials/admin/memberships-row-avatar.jade index a15f3b45..ccebb315 100644 --- a/app/partials/admin/memberships-row-avatar.jade +++ b/app/partials/admin/memberships-row-avatar.jade @@ -1,5 +1,8 @@ figure.avatar - img(src!="<%- imgurl %>", alt!="<%- full_name %>") + img( + style!="background-color: <%- bg %>" + src!="<%- imgurl %>", alt!="<%- full_name %>" + ) figcaption span.name(ng-non-bindable) <%- full_name %> <% if (isOwner) { %> diff --git a/app/partials/auth/invitation.jade b/app/partials/auth/invitation.jade index 6288b606..ececd510 100644 --- a/app/partials/auth/invitation.jade +++ b/app/partials/auth/invitation.jade @@ -2,8 +2,10 @@ div.wrapper div.invitation-main div.centered.invitation-container(tg-invitation) a.avatar(href="", tg-bo-title="invitation.invited_by.full_name_display") - img(tg-bo-src="invitation.invited_by.photo", - tg-bo-alt="invitation.invited_by.full_name_display") + img( + tg-avatar="invitation.invited_by" + tg-bo-alt="invitation.invited_by.full_name_display" + ) span.person-name(tg-bo-bind="invitation.invited_by.full_name_display") span.invitation-text diff --git a/app/partials/common/components/assigned-to.jade b/app/partials/common/components/assigned-to.jade index e5f68800..3a8f8eb1 100644 --- a/app/partials/common/components/assigned-to.jade +++ b/app/partials/common/components/assigned-to.jade @@ -1,5 +1,9 @@ .user-avatar(class!="<% if (isIocaine) { %> is-iocaine <% }; %>") - img(src!="<%- photo %>", alt!="<%- fullName %>") + img( + style!="background-color: <%- bg %>" + src!="<%- avatar %>" + alt!="<%- fullName %>" + ) <% if (isIocaine) { %> .iocaine-symbol(title="{{ 'TASK.TITLE_ACTION_IOCAINE' | translate }}") tg-svg(svg-icon="icon-iocaine") diff --git a/app/partials/common/components/created-by.jade b/app/partials/common/components/created-by.jade index 8486d0fa..606a014d 100644 --- a/app/partials/common/components/created-by.jade +++ b/app/partials/common/components/created-by.jade @@ -12,6 +12,7 @@ title="{{owner.full_name_display}}" ) img( - src="{{owner.photo}}" + ng-style="{'background': owner.bg}" + ng-src="{{owner.avatar}}" alt="{{owner.full_name_display}}" ) diff --git a/app/partials/common/components/list-item-assigned-to-avatar.jade b/app/partials/common/components/list-item-assigned-to-avatar.jade index bb2ead9c..e8535d23 100644 --- a/app/partials/common/components/list-item-assigned-to-avatar.jade +++ b/app/partials/common/components/list-item-assigned-to-avatar.jade @@ -1,3 +1,7 @@ div.avatar - img(src!="<%- imgurl %>", alt!="<%- name %>") + img( + style!="background-color: <%- bg %>" + src!="<%- imgurl %>" + alt!="<%- name %>" + ) span.avatar-caption <%- name %> diff --git a/app/partials/common/components/user-display.jade b/app/partials/common/components/user-display.jade index 3e6bb71a..3ac35373 100644 --- a/app/partials/common/components/user-display.jade +++ b/app/partials/common/components/user-display.jade @@ -4,7 +4,8 @@ title="{{user.full_name_display}}" ) img( - src="{{user.photo}}" + ng-style="{'background-color': user.bg}" + ng-src="{{user.avatar}}" alt="{{user.full_name_display}}" ) a.user-full-name( @@ -15,7 +16,8 @@ a.user-full-name( .user-avatar(ng-if="!url") img( - src="{{user.photo}}" + ng-style="{'background-color': user.bg}" + ng-src="{{user.avatar}}" alt="{{user.full_name_display}}" ) span.user-full-name(ng-if="!url") {{user.full_name_display}} diff --git a/app/partials/common/components/watchers.jade b/app/partials/common/components/watchers.jade index fa5da31e..b899353e 100644 --- a/app/partials/common/components/watchers.jade +++ b/app/partials/common/components/watchers.jade @@ -1,20 +1,15 @@ -<% _.each(watchers, function(watcher) { %> -<% if(watcher) { %> -.user-list-single +.user-list-single(ng-repeat="watcher in watchers") .user-list-avatar img( - src!="<%- watcher.photo %>" - alt!="<%- watcher.full_name_display %>" + tg-avatar="watcher" + alt="{{watcher.full_name_display}}" ) .user-list-name - span(ng-non-bindable) <%- watcher.full_name_display %> + span {{watcher.full_name_display}} - <% if(isEditable){ %> tg-svg.js-delete-watcher.delete-watcher( + ng-if="isEditable" svg-icon="icon-trash", svg-title-translate="COMMON.WATCHERS.DELETE", - data-watcher-id!="<%- watcher.id %>" + data-watcher-id="{{watcher.id}}" ) - <% }; %> -<% } %> -<% }); %> diff --git a/app/partials/common/lightbox/lightbox-assigned-to-users.jade b/app/partials/common/lightbox/lightbox-assigned-to-users.jade index 39065790..16c06377 100644 --- a/app/partials/common/lightbox/lightbox-assigned-to-users.jade +++ b/app/partials/common/lightbox/lightbox-assigned-to-users.jade @@ -5,7 +5,10 @@ href="" title="{{'COMMON.ASSIGNED_TO' | translate}}" ) - img(src!="<%- selected.photo %>") + img( + style!="background: <%- selected.avatar.bg %>" + src!="<%- selected.avatar.url %>" + ) a.user-list-name( href="" title!="<%- selected.full_name_display %>" @@ -25,7 +28,10 @@ href="#" title="{{'COMMON.ASSIGNED_TO.TITLE' | translate}}" ) - img(src!="<%- user.photo %>") + img( + style!="background: <%- user.avatar.bg %>" + src!="<%- user.avatar.url %>" + ) a.user-list-name( href="" title!="<%- user.full_name_display %>" diff --git a/app/partials/common/lightbox/lightbox-change-owner.jade b/app/partials/common/lightbox/lightbox-change-owner.jade index a1264699..dc4f912d 100644 --- a/app/partials/common/lightbox/lightbox-change-owner.jade +++ b/app/partials/common/lightbox/lightbox-change-owner.jade @@ -18,7 +18,7 @@ tg-lightbox-close href="#" title="{{'COMMON.ASSIGNED_TO.TITLE' | translate}}" ) - img(ng-src="{{vm.selected.photo}}") + img(tg-avatar="{{vm.selected}}") a.user-list-name( href="" title="{{vm.selected.full_name_display}}" @@ -33,7 +33,8 @@ tg-lightbox-close href="#" title="{{'COMMON.ASSIGNED_TO.TITLE' | translate}}" ) - img(ng-src="{{user.photo}}") + img(tg-avatar="user") + a.user-list-name( href="" title="{{user.full_name_display}}" diff --git a/app/partials/taskboard/taskboard-user.jade b/app/partials/taskboard/taskboard-user.jade index ca05113f..70f25205 100644 --- a/app/partials/taskboard/taskboard-user.jade +++ b/app/partials/taskboard/taskboard-user.jade @@ -1,6 +1,15 @@ figure.avatar.avatar-assigned-to a(href='#', title="{{'TASKBOARD.TITLE_ACTION_ASSIGN' | translate}}", ng-class="{'not-clickable': !clickable}") - img(ng-src='{{imgurl}}') + img( + ng-style="{'background-color': avatar.bg}" + ng-src='{{avatar.url}}' + ) figure.avatar.avatar-task-link - a(tg-nav='project-tasks-detail:project=project.slug,ref=task.ref', ng-attr-title='{{task.subject}}') - img(ng-src='{{imgurl}}') + a( + tg-nav='project-tasks-detail:project=project.slug,ref=task.ref' + ng-attr-title='{{task.subject}}' + ) + img( + ng-style="{'background-color': avatar.bg}" + ng-src='{{avatar.url}}' + ) diff --git a/app/partials/team/team-member-current-user.jade b/app/partials/team/team-member-current-user.jade index caefc7e5..683d79ad 100644 --- a/app/partials/team/team-member-current-user.jade +++ b/app/partials/team/team-member-current-user.jade @@ -1,7 +1,7 @@ .row .username .avatar - img(tg-bo-src="currentUser.photo", tg-bo-alt="currentUser.full_name_display") + img(tg-avatar="currentUser", tg-bo-alt="currentUser.full_name_display") .avatar-data .name diff --git a/app/partials/team/team-members.jade b/app/partials/team/team-members.jade index 2155d134..0a1fbae4 100644 --- a/app/partials/team/team-members.jade +++ b/app/partials/team/team-members.jade @@ -1,7 +1,7 @@ .row.member(ng-repeat="user in memberships | membersFilter:filtersQ:filtersRole") .username .avatar - img(tg-bo-src="user.photo", tg-bo-alt="user.full_name_display") + img(tg-avatar="user", tg-bo-alt="user.full_name_display") .avatar-data a.name( diff --git a/app/partials/user/user-profile.jade b/app/partials/user/user-profile.jade index 2eb62e65..fc3518bd 100644 --- a/app/partials/user/user-profile.jade +++ b/app/partials/user/user-profile.jade @@ -16,7 +16,7 @@ div.wrapper( form .project-details-image(tg-user-avatar) fieldset.image-container - img.image(ng-src="{{user.big_photo}}" alt="avatar") + img.image(tg-avatar="user" alt="avatar") .loading-overlay img.loading-spinner( src="/#{v}/svg/spinner-circle.svg", diff --git a/app/partials/wiki/wiki-summary.jade b/app/partials/wiki/wiki-summary.jade index e1708e7b..8acc644e 100644 --- a/app/partials/wiki/wiki-summary.jade +++ b/app/partials/wiki/wiki-summary.jade @@ -1,6 +1,10 @@ .wiki-username-edition .avatar - img(src!="<%- user.imgUrl %>" alt!="<%- user.name %>") + img( + style!="background-color: <%- user.avatar.bg %>" + src!="<%- user.avatar.url %>" + alt!="<%- user.name %>" + ) .wiki-user-modification span.description(translate="WIKI.SUMMARY.LAST_MODIFICATION") span.username <%- user.name %> @@ -8,16 +12,16 @@ .wiki-last-modified span.number <%- lastModifiedDate %> span.description(translate="WIKI.SUMMARY.LAST_EDIT") - + .wiki-times-edited span.number <%- totalEditions %> span.description(translate="WIKI.SUMMARY.TIMES_EDITED") - + tg-svg.remove( tg-check-permission="delete_wiki_page" title="{{'WIKI.REMOVE' | translate}}" ng-click="ctrl.delete()" svg-icon="icon-trash" ng-if="wiki.id" - + ) From eabcf6316c7d9f76d86be447bdf0d0c4ef9754db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 29 Jul 2016 18:17:54 +0200 Subject: [PATCH 102/315] Fix a sybtax error in avatar service --- app/modules/services/avatar.service.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/services/avatar.service.coffee b/app/modules/services/avatar.service.coffee index add6b311..0b55dca7 100644 --- a/app/modules/services/avatar.service.coffee +++ b/app/modules/services/avatar.service.coffee @@ -49,7 +49,7 @@ class AvatarService } getAvatar: (user, type) -> - return getUnnamed() if !user + return @.getUnnamed() if !user avatarParamName = 'photo' @@ -65,7 +65,7 @@ class AvatarService gravatar = user.gravatar_id photo = user[avatarParamName] - return getUnnamed() if !gravatar + return @.getUnnamed() if !gravatar if photo return { From e6ef8ffa347521d44006a09098a6c66b9b66e5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Sat, 30 Jul 2016 12:44:13 +0200 Subject: [PATCH 103/315] [i18n] Update locales --- app/locales/taiga/locale-ca.json | 5 ++-- app/locales/taiga/locale-de.json | 5 ++-- app/locales/taiga/locale-es.json | 5 ++-- app/locales/taiga/locale-fi.json | 5 ++-- app/locales/taiga/locale-fr.json | 5 ++-- app/locales/taiga/locale-it.json | 5 ++-- app/locales/taiga/locale-nl.json | 5 ++-- app/locales/taiga/locale-pl.json | 5 ++-- app/locales/taiga/locale-pt-br.json | 5 ++-- app/locales/taiga/locale-ru.json | 41 ++++++++++++++------------- app/locales/taiga/locale-sv.json | 5 ++-- app/locales/taiga/locale-tr.json | 5 ++-- app/locales/taiga/locale-zh-hant.json | 5 ++-- 13 files changed, 57 insertions(+), 44 deletions(-) diff --git a/app/locales/taiga/locale-ca.json b/app/locales/taiga/locale-ca.json index b8ae371d..977c8ac1 100644 --- a/app/locales/taiga/locale-ca.json +++ b/app/locales/taiga/locale-ca.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "tasques
tancades", "IOCAINE_DOSES": "dosis
iocaína", "SHOW_STATISTICS_TITLE": "Mostrar estadístiques", - "TOGGLE_BAKLOG_GRAPH": "Show/Hide burndown graph" + "TOGGLE_BAKLOG_GRAPH": "Show/Hide burndown graph", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "punts
projecte", @@ -1632,4 +1633,4 @@ "RESULTS": "Search results" } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-de.json b/app/locales/taiga/locale-de.json index 42af6566..9de20cf3 100644 --- a/app/locales/taiga/locale-de.json +++ b/app/locales/taiga/locale-de.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "geschlossene
Aufgaben", "IOCAINE_DOSES": "Iocaine
Dosen", "SHOW_STATISTICS_TITLE": "Statistik anzeigen", - "TOGGLE_BAKLOG_GRAPH": "Zeige/Verstecke Burndowngraph" + "TOGGLE_BAKLOG_GRAPH": "Zeige/Verstecke Burndowngraph", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "Projekt
Punkte", @@ -1632,4 +1633,4 @@ "RESULTS": "Suchergebnisse" } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-es.json b/app/locales/taiga/locale-es.json index 09f50131..da2c4135 100644 --- a/app/locales/taiga/locale-es.json +++ b/app/locales/taiga/locale-es.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "tareas
cerradas", "IOCAINE_DOSES": "dosis de
iocaína", "SHOW_STATISTICS_TITLE": "Ver estadísticas", - "TOGGLE_BAKLOG_GRAPH": "Ver/Ocultar gráfica de burndown" + "TOGGLE_BAKLOG_GRAPH": "Ver/Ocultar gráfica de burndown", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "puntos
proyecto", @@ -1632,4 +1633,4 @@ "RESULTS": "Resultados de búsqueda" } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-fi.json b/app/locales/taiga/locale-fi.json index 99cad17b..2492cebc 100644 --- a/app/locales/taiga/locale-fi.json +++ b/app/locales/taiga/locale-fi.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "suljettu
tehtävää", "IOCAINE_DOSES": "myrkkye-
annosta", "SHOW_STATISTICS_TITLE": "Näytä tilastot", - "TOGGLE_BAKLOG_GRAPH": "Show/Hide burndown graph" + "TOGGLE_BAKLOG_GRAPH": "Show/Hide burndown graph", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "projekti
pistettä", @@ -1632,4 +1633,4 @@ "RESULTS": "Search results" } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-fr.json b/app/locales/taiga/locale-fr.json index d17a58e0..7040e1b2 100644 --- a/app/locales/taiga/locale-fr.json +++ b/app/locales/taiga/locale-fr.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "tâches
fermées", "IOCAINE_DOSES": "doses
de iocaine", "SHOW_STATISTICS_TITLE": "Afficher les statistiques", - "TOGGLE_BAKLOG_GRAPH": "Afficher/masquer le graphique d'avancement" + "TOGGLE_BAKLOG_GRAPH": "Afficher/masquer le graphique d'avancement", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "projet
points", @@ -1632,4 +1633,4 @@ "RESULTS": "Résultats de la recherche" } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-it.json b/app/locales/taiga/locale-it.json index a683b8f9..cbf22204 100644 --- a/app/locales/taiga/locale-it.json +++ b/app/locales/taiga/locale-it.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "
compiti chiusi", "IOCAINE_DOSES": "
pasticche di aspirina", "SHOW_STATISTICS_TITLE": "Mostra statistiche", - "TOGGLE_BAKLOG_GRAPH": "Mostra/nascondi i grafici burndown" + "TOGGLE_BAKLOG_GRAPH": "Mostra/nascondi i grafici burndown", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "
punti di progetto", @@ -1632,4 +1633,4 @@ "RESULTS": "Risultati della ricerca" } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-nl.json b/app/locales/taiga/locale-nl.json index a730cce8..fc3bd95c 100644 --- a/app/locales/taiga/locale-nl.json +++ b/app/locales/taiga/locale-nl.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "gesloten
taken", "IOCAINE_DOSES": "iocaine
dosissen", "SHOW_STATISTICS_TITLE": "Toon statistieken", - "TOGGLE_BAKLOG_GRAPH": "Toon/Verstop burndown grafiek" + "TOGGLE_BAKLOG_GRAPH": "Toon/Verstop burndown grafiek", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "project
punten", @@ -1632,4 +1633,4 @@ "RESULTS": "Search results" } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-pl.json b/app/locales/taiga/locale-pl.json index 34ca3e7a..385461c5 100644 --- a/app/locales/taiga/locale-pl.json +++ b/app/locales/taiga/locale-pl.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "zamkniętych
zadań", "IOCAINE_DOSES": "dawek
Iokainy", "SHOW_STATISTICS_TITLE": "Pokaż statystyki", - "TOGGLE_BAKLOG_GRAPH": "Pokaż/Ukryj wykres spalania" + "TOGGLE_BAKLOG_GRAPH": "Pokaż/Ukryj wykres spalania", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "punktów w
projekcie", @@ -1632,4 +1633,4 @@ "RESULTS": "Search results" } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index af05d090..97becb3d 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "tarefas
fechadas", "IOCAINE_DOSES": "iocaine
doses", "SHOW_STATISTICS_TITLE": "Mostrar estatísticas", - "TOGGLE_BAKLOG_GRAPH": "Mostrar/Esconder gráfico de burndown" + "TOGGLE_BAKLOG_GRAPH": "Mostrar/Esconder gráfico de burndown", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "pontos do
projeto", @@ -1632,4 +1633,4 @@ "RESULTS": "Resultado de pesquisa." } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-ru.json b/app/locales/taiga/locale-ru.json index 080afeda..496c408e 100644 --- a/app/locales/taiga/locale-ru.json +++ b/app/locales/taiga/locale-ru.json @@ -483,7 +483,7 @@ "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds your maximum number of members for public projects", "PROJECT_OWNER": "Владелец проекта", - "REQUEST_OWNERSHIP": "Request ownership", + "REQUEST_OWNERSHIP": "Запрос владельца", "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Хотите стать новым владельцем проекта?", "REQUEST_OWNERSHIP_DESC": "Request that current project owner {{name}} transfer ownership of this project to you.", "REQUEST_OWNERSHIP_BUTTON": "Запрос", @@ -1012,8 +1012,8 @@ "PUBLISH_INFO": "Больше инфо", "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", - "EDIT_LINK": "Edit link", - "CLOSE": "Close", + "EDIT_LINK": "Редактировать ссылку", + "CLOSE": "Закрыть", "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" @@ -1025,15 +1025,15 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comment deleted by {{user}}", + "DELETED_INFO": "Комментарий удалён {{user}}", "TITLE": "Комментарии", "COMMENTS_COUNT": "{{comments}} Comments", - "ORDER": "Order", - "OLDER_FIRST": "Older first", - "RECENT_FIRST": "Recent first", + "ORDER": "Порядок", + "OLDER_FIRST": "Сначала старые", + "RECENT_FIRST": "Сначала новые", "COMMENT": "Комментарий", - "EDIT_COMMENT": "Edit comment", - "EDITED_COMMENT": "Edited:", + "EDIT_COMMENT": "Редактировать комментарий", + "EDITED_COMMENT": "Изменено:", "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Добавить комментарий", "SHOW_DELETED": "Показать удаленный комментарий", @@ -1052,11 +1052,11 @@ "ACTIVITIES_COUNT": "{{activities}} Activities", "REMOVED": "удален", "ADDED": "добавлено", - "TAGS_ADDED": "tags added:", + "TAGS_ADDED": "тэги добавлены:", "TAGS_REMOVED": "tags removed:", "US_POINTS": "{{role}} points", - "NEW_ATTACHMENT": "new attachment:", - "DELETED_ATTACHMENT": "deleted attachment:", + "NEW_ATTACHMENT": "новое вложение:", + "DELETED_ATTACHMENT": "удалённое вложение:", "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "завершённые
задачи", "IOCAINE_DOSES": "иокаина
дозы", "SHOW_STATISTICS_TITLE": "Показать статистику", - "TOGGLE_BAKLOG_GRAPH": "Показать/Скрыть график решения задач" + "TOGGLE_BAKLOG_GRAPH": "Показать/Скрыть график решения задач", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "проектные
очки", @@ -1448,7 +1449,7 @@ "CHOOSE_TEMPLATE_INFO": "Больше инфо", "PROJECT_DETAILS": "Project Details", "PUBLIC_PROJECT": "Public Project", - "PRIVATE_PROJECT": "Private Project", + "PRIVATE_PROJECT": "Частный проект", "CREATE_PROJECT": "Создать проект", "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects", "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects", @@ -1461,11 +1462,11 @@ "PLACEHOLDER_PAGE": "Создать вики страницу", "REMOVE": "Удалить эту вики страницу", "DELETE_LIGHTBOX_TITLE": "Удалить вики страницу", - "DELETE_LINK_TITLE": "Delete Wiki link", + "DELETE_LINK_TITLE": "Удалить ссылку wiki", "NAVIGATION": { - "HOME": "Main Page", - "SECTION_NAME": "BOOKMARKS", - "ACTION_ADD_LINK": "Add bookmark", + "HOME": "Главная страница", + "SECTION_NAME": "ЗАКЛАДКИ", + "ACTION_ADD_LINK": "Добавить закладку", "ALL_PAGES": "All wiki pages" }, "SUMMARY": { @@ -1473,7 +1474,7 @@ "LAST_EDIT": "последняя
правка", "LAST_MODIFICATION": "последнее изменение" }, - "SECTION_PAGES_LIST": "All pages", + "SECTION_PAGES_LIST": "Все страницы", "PAGES_LIST_COLUMNS": { "TITLE": "Title", "EDITIONS": "Editions", @@ -1632,4 +1633,4 @@ "RESULTS": "Результаты поиска" } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-sv.json b/app/locales/taiga/locale-sv.json index ab8efca2..9f8a4118 100644 --- a/app/locales/taiga/locale-sv.json +++ b/app/locales/taiga/locale-sv.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "stängd
uppgifer", "IOCAINE_DOSES": "iocaine
doser", "SHOW_STATISTICS_TITLE": "Visa statistik", - "TOGGLE_BAKLOG_GRAPH": "Visa/Dölj burn down-graf" + "TOGGLE_BAKLOG_GRAPH": "Visa/Dölj burn down-graf", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "projekt
poäng", @@ -1632,4 +1633,4 @@ "RESULTS": "Search results" } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-tr.json b/app/locales/taiga/locale-tr.json index a03a4b38..296a85f0 100644 --- a/app/locales/taiga/locale-tr.json +++ b/app/locales/taiga/locale-tr.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "kapatılmış
görevler", "IOCAINE_DOSES": "baldıran zehri
dozu", "SHOW_STATISTICS_TITLE": "İstatistikleri göster", - "TOGGLE_BAKLOG_GRAPH": "Eritme grafiğini göster/gizle" + "TOGGLE_BAKLOG_GRAPH": "Eritme grafiğini göster/gizle", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "proje
puanları", @@ -1632,4 +1633,4 @@ "RESULTS": "Arama sonuçları" } } -} +} \ No newline at end of file diff --git a/app/locales/taiga/locale-zh-hant.json b/app/locales/taiga/locale-zh-hant.json index 93fd5ac0..1ed57f83 100644 --- a/app/locales/taiga/locale-zh-hant.json +++ b/app/locales/taiga/locale-zh-hant.json @@ -1155,7 +1155,8 @@ "CLOSED_TASKS": "已關閉
任務", "IOCAINE_DOSES": "毒物(全新任務挑戰)
劑量", "SHOW_STATISTICS_TITLE": "顯示統計", - "TOGGLE_BAKLOG_GRAPH": "顯示/隱藏 剩餘工作量圖" + "TOGGLE_BAKLOG_GRAPH": "顯示/隱藏 剩餘工作量圖", + "POINTS_PER_ROLE": "Points per role" }, "SUMMARY": { "PROJECT_POINTS": "專案
點數", @@ -1632,4 +1633,4 @@ "RESULTS": "搜尋結果" } } -} +} \ No newline at end of file From 59bf55fc30336e784dc18e01a38865b9f640f4c1 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 3 Jun 2016 08:20:35 +0200 Subject: [PATCH 104/315] cards & filters ui refactor --- CHANGELOG.md | 4 + app/coffee/modules/backlog/filters.coffee | 185 ----- app/coffee/modules/backlog/main.coffee | 417 ++++------- app/coffee/modules/backlog/sortable.coffee | 26 +- app/coffee/modules/base/repository.coffee | 17 +- app/coffee/modules/common/lightboxes.coffee | 28 +- app/coffee/modules/controllerMixins.coffee | 175 +++++ app/coffee/modules/issues/list.coffee | 656 ++++++------------ .../modules/kanban/kanban-usertories.coffee | 189 +++++ app/coffee/modules/kanban/main.coffee | 424 ++++------- app/coffee/modules/kanban/sortable.coffee | 12 +- app/coffee/modules/resources.coffee | 4 +- app/coffee/modules/resources/issues.coffee | 49 -- app/coffee/modules/resources/kanban.coffee | 10 - app/coffee/modules/resources/tasks.coffee | 12 +- .../modules/resources/userstories.coffee | 14 +- .../modules/taskboard/lightboxes.coffee | 19 +- app/coffee/modules/taskboard/main.coffee | 430 +++++++----- app/coffee/modules/taskboard/sortable.coffee | 30 +- .../modules/taskboard/taskboard-tasks.coffee | 173 +++++ app/coffee/utils.coffee | 2 +- app/locales/taiga/locale-en.json | 53 +- .../board-zoom/board-zoom.directive.coffee | 29 + .../components/board-zoom/board-zoom.jade | 9 + .../components/board-zoom/board-zoom.scss | 108 +++ .../card-slideshow.controller.coffee | 38 + .../card-slideshow.directive.coffee | 33 + .../card-slideshow/card-slideshow.jade | 18 + .../card/card-templates/card-completion.jade | 4 + .../card/card-templates/card-data.jade | 21 + .../card/card-templates/card-owner.jade | 43 ++ .../card/card-templates/card-tags.jade | 7 + .../card/card-templates/card-tasks.jade | 7 + .../card/card-templates/card-title.jade | 9 + .../card/card-templates/card-unfold.jade | 6 + .../components/card/card.controller.coffee | 82 +++ .../card/card.controller.spec.coffee | 142 ++++ .../components/card/card.directive.coffee | 43 ++ app/modules/components/card/card.jade | 16 + app/modules/components/card/card.scss | 326 +++++++++ .../filter/filter-remote.service.coffee | 68 ++ .../filter/filter-slide-down.directive.coffee | 45 ++ .../filter/filter.controller.coffee | 70 ++ .../filter/filter.controller.spec.coffee | 87 +++ .../components/filter/filter.directive.coffee | 44 ++ app/modules/components/filter/filter.jade | 110 +++ app/modules/components/filter/filter.scss | 150 ++++ .../joy-ride/joy-ride.service.coffee | 2 +- .../kanban-board-zoom.directive.coffee | 69 ++ .../taskboard-zoom.directive.coffee | 62 ++ app/partials/backlog/backlog.jade | 28 +- app/partials/backlog/filter-selected.jade | 9 - app/partials/backlog/filters.jade | 17 - .../includes/components/taskboard-task.jade | 18 - .../includes/modules/backlog-filters.jade | 36 - .../includes/modules/issues-filters.jade | 84 --- .../includes/modules/issues-table.jade | 24 +- .../includes/modules/kanban-table.jade | 53 +- .../includes/modules/taskboard-table.jade | 87 ++- .../issue/issues-filters-selected.jade | 9 - app/partials/issue/issues-filters.jade | 21 - app/partials/issue/issues.jade | 16 +- app/partials/kanban/kanban-task.jade | 33 - app/partials/kanban/kanban.jade | 30 +- app/partials/taskboard/taskboard.jade | 29 +- app/styles/components/buttons.scss | 11 + app/styles/components/filter.scss | 50 -- app/styles/components/kanban-task.scss | 224 ------ app/styles/components/taskboard-task.scss | 140 ---- app/styles/core/base.scss | 15 - app/styles/dependencies/helpers.scss | 2 +- app/styles/layout/backlog.scss | 21 + app/styles/layout/issues.scss | 6 +- app/styles/layout/kanban.scss | 10 + app/styles/layout/taskboard.scss | 7 + .../modules/backlog/taskboard-table.scss | 111 ++- app/styles/modules/filters/filters.scss | 114 --- app/styles/modules/kanban/kanban-table.scss | 30 +- app/styles/shame/shame.scss | 10 +- app/svg/sprite.svg | 9 + e2e/helpers/filters-helper.js | 81 +++ e2e/helpers/issues-helper.js | 54 -- e2e/helpers/kanban-helper.js | 34 +- e2e/helpers/taskboard-helper.js | 32 +- e2e/shared/filters.js | 76 ++ e2e/suites/backlog.e2e.js | 144 +--- e2e/suites/issues/issues.e2e.js | 195 +----- e2e/suites/kanban.e2e.js | 26 +- e2e/suites/tasks/taskboard.e2e.js | 28 +- gulpfile.js | 6 +- 90 files changed, 3695 insertions(+), 2812 deletions(-) delete mode 100644 app/coffee/modules/backlog/filters.coffee create mode 100644 app/coffee/modules/kanban/kanban-usertories.coffee create mode 100644 app/coffee/modules/taskboard/taskboard-tasks.coffee create mode 100644 app/modules/components/board-zoom/board-zoom.directive.coffee create mode 100644 app/modules/components/board-zoom/board-zoom.jade create mode 100644 app/modules/components/board-zoom/board-zoom.scss create mode 100644 app/modules/components/card-slideshow/card-slideshow.controller.coffee create mode 100644 app/modules/components/card-slideshow/card-slideshow.directive.coffee create mode 100644 app/modules/components/card-slideshow/card-slideshow.jade create mode 100644 app/modules/components/card/card-templates/card-completion.jade create mode 100644 app/modules/components/card/card-templates/card-data.jade create mode 100644 app/modules/components/card/card-templates/card-owner.jade create mode 100644 app/modules/components/card/card-templates/card-tags.jade create mode 100644 app/modules/components/card/card-templates/card-tasks.jade create mode 100644 app/modules/components/card/card-templates/card-title.jade create mode 100644 app/modules/components/card/card-templates/card-unfold.jade create mode 100644 app/modules/components/card/card.controller.coffee create mode 100644 app/modules/components/card/card.controller.spec.coffee create mode 100644 app/modules/components/card/card.directive.coffee create mode 100644 app/modules/components/card/card.jade create mode 100644 app/modules/components/card/card.scss create mode 100644 app/modules/components/filter/filter-remote.service.coffee create mode 100644 app/modules/components/filter/filter-slide-down.directive.coffee create mode 100644 app/modules/components/filter/filter.controller.coffee create mode 100644 app/modules/components/filter/filter.controller.spec.coffee create mode 100644 app/modules/components/filter/filter.directive.coffee create mode 100644 app/modules/components/filter/filter.jade create mode 100644 app/modules/components/filter/filter.scss create mode 100644 app/modules/components/kanban-board-zoom/kanban-board-zoom.directive.coffee create mode 100644 app/modules/components/taskboard-zoom/taskboard-zoom.directive.coffee delete mode 100644 app/partials/backlog/filter-selected.jade delete mode 100644 app/partials/backlog/filters.jade delete mode 100644 app/partials/includes/components/taskboard-task.jade delete mode 100644 app/partials/includes/modules/backlog-filters.jade delete mode 100644 app/partials/includes/modules/issues-filters.jade delete mode 100644 app/partials/issue/issues-filters-selected.jade delete mode 100644 app/partials/issue/issues-filters.jade delete mode 100644 app/partials/kanban/kanban-task.jade delete mode 100644 app/styles/components/filter.scss delete mode 100644 app/styles/components/kanban-task.scss delete mode 100644 app/styles/components/taskboard-task.scss delete mode 100644 app/styles/modules/filters/filters.scss create mode 100644 e2e/helpers/filters-helper.js create mode 100644 e2e/shared/filters.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0551a4ca..2e970fec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ - Add Wiki history - Third party integrations: - Included gogs as builtin integration. +- Filters refactor +- Cards ui refactor with zoom +- Kanban filters +- Taskboard filters ### Misc - Lots of small and not so small bugfixes. diff --git a/app/coffee/modules/backlog/filters.coffee b/app/coffee/modules/backlog/filters.coffee deleted file mode 100644 index 39a0dfa9..00000000 --- a/app/coffee/modules/backlog/filters.coffee +++ /dev/null @@ -1,185 +0,0 @@ -### -# Copyright (C) 2014-2016 Andrey Antukh -# Copyright (C) 2014-2016 Jesús Espino Garcia -# Copyright (C) 2014-2016 David Barragán Merino -# Copyright (C) 2014-2016 Alejandro Alonso -# Copyright (C) 2014-2016 Juan Francisco Alcántara -# Copyright (C) 2014-2016 Xavi Julian -# -# 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 -toggleText = @.taiga.toggleText -scopeDefer = @.taiga.scopeDefer -bindOnce = @.taiga.bindOnce -groupBy = @.taiga.groupBy -debounceLeading = @.taiga.debounceLeading - - -module = angular.module("taigaBacklog") - -############################################################################# -## Issues Filters Directive -############################################################################# - -BacklogFiltersDirective = ($q, $log, $location, $template, $compile) -> - template = $template.get("backlog/filters.html", true) - templateSelected = $template.get("backlog/filter-selected.html", true) - - link = ($scope, $el, $attrs) -> - currentFiltersType = '' - - $ctrl = $el.closest(".wrapper").controller() - selectedFilters = [] - - showFilters = (title, type) -> - $el.find(".filters-cats").hide() - $el.find(".filter-list").removeClass("hidden") - $el.find("h2.breadcrumb").removeClass("hidden") - $el.find("h2 a.subfilter span.title").html(title) - $el.find("h2 a.subfilter span.title").prop("data-type", type) - - currentFiltersType = getFiltersType() - - showCategories = -> - $el.find(".filters-cats").show() - $el.find(".filter-list").addClass("hidden") - $el.find("h2.breadcrumb").addClass("hidden") - - initializeSelectedFilters = () -> - showCategories() - selectedFilters = [] - - for name, values of $scope.filters - for val in values - selectedFilters.push(val) if val.selected - - renderSelectedFilters() - - renderSelectedFilters = -> - _.map selectedFilters, (f) => - if f.color - f.style = "border-left: 3px solid #{f.color}" - - html = templateSelected({filters: selectedFilters}) - html = $compile(html)($scope) - - $el.find(".filters-applied").html(html) - - renderFilters = (filters) -> - _.map filters, (f) => - if f.color - f.style = "border-left: 3px solid #{f.color}" - - html = template({filters:filters}) - html = $compile(html)($scope) - $el.find(".filter-list").html(html) - - getFiltersType = () -> - return $el.find("h2 a.subfilter span.title").prop('data-type') - - reloadUserstories = () -> - currentFiltersType = getFiltersType() - - $q.all([$ctrl.loadUserstories(true), $ctrl.generateFilters()]).then () -> - currentFilters = $scope.filters[currentFiltersType] - renderFilters(_.reject(currentFilters, "selected")) - - toggleFilterSelection = (type, id) -> - currentFiltersType = getFiltersType() - - filters = $scope.filters[type] - filter = _.find(filters, {id: id}) - filter.selected = (not filter.selected) - - if filter.selected - selectedFilters.push(filter) - $scope.$apply -> - $ctrl.selectFilter(type, id) - else - selectedFilters = _.reject selectedFilters, (selected) -> - return filter.type == selected.type && filter.id == selected.id - - $ctrl.unselectFilter(type, id) - - renderSelectedFilters(selectedFilters) - - if type == currentFiltersType - renderFilters(_.reject(filters, "selected")) - - reloadUserstories() - - selectQFilter = debounceLeading 100, (value) -> - return if value is undefined - - if value.length == 0 - $ctrl.replaceFilter("q", null) - else - $ctrl.replaceFilter("q", value) - - reloadUserstories() - - $scope.$watch("filtersQ", selectQFilter) - - ## Angular Watchers - $scope.$on "backlog:loaded", (ctx) -> - initializeSelectedFilters() - - $scope.$on "filters:update", (ctx) -> - $ctrl.generateFilters().then () -> - filters = $scope.filters[currentFiltersType] - - if currentFiltersType - renderFilters(_.reject(filters, "selected")) - - ## Dom Event Handlers - $el.on "click", ".filters-cats > ul > li > a", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget) - tags = $scope.filters[target.data("type")] - - renderFilters(_.reject(tags, "selected")) - showFilters(target.attr("title"), target.data('type')) - - $el.on "click", ".filters-inner > .filters-step-cat > .breadcrumb > .back", (event) -> - event.preventDefault() - showCategories() - - $el.on "click", ".remove-filter", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget).parent() - id = target.data("id") - type = target.data("type") - toggleFilterSelection(type, id) - - $el.on "click", ".filter-list .single-filter", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget) - if target.hasClass("active") - target.removeClass("active") - else - target.addClass("active") - - id = target.data("id") - type = target.data("type") - toggleFilterSelection(type, id) - - return {link:link} - -module.directive("tgBacklogFilters", ["$q", "$log", "$tgLocation", "$tgTemplate", "$compile", BacklogFiltersDirective]) diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index ea9a256a..a9819327 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -39,7 +39,7 @@ module = angular.module("taigaBacklog") ## Backlog Controller ############################################################################# -class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) +class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin, taiga.UsFiltersMixin) @.$inject = [ "$scope", "$rootScope", @@ -57,18 +57,30 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F "$tgLoading", "tgResources", "$tgQueueModelTransformation", - "tgErrorHandlingService" + "tgErrorHandlingService", + "$tgStorage", + "tgFilterRemoteStorageService" ] + storeCustomFiltersName: 'backlog-custom-filters' + storeFiltersName: 'backlog-filters' + backlogOrder: {} + milestonesOrder: {} + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @appMetaService, @navUrls, - @events, @analytics, @translate, @loading, @rs2, @modelTransform, @errorHandlingService) -> + @events, @analytics, @translate, @loading, @rs2, @modelTransform, @errorHandlingService, @storage, @filterRemoteStorageService) -> bindMethods(@) + @.backlogOrder = {} + @.milestonesOrder = {} + @.page = 1 @.disablePagination = false @.firstLoadComplete = false @scope.userstories = [] + return if @.applyStoredFilters(@params.pslug, "backlog-filters") + @scope.sectionName = @translate.instant("BACKLOG.SECTION_NAME") @showTags = false @activeFilters = false @@ -97,6 +109,9 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F # On Error promise.then null, @.onInitialDataError.bind(@) + filtersReloadContent: () -> + @.loadUserstories(true) + initializeEventHandlers: -> @scope.$on "usform:bulk:success", => @.loadUserstories(true) @@ -175,6 +190,12 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F @scope.showGraphPlaceholder = !(stats.total_points? && stats.total_milestones?) return stats + setMilestonesOrder: (sprints) -> + for sprint in sprints + @.milestonesOrder[sprint.id] = {} + for it in sprint.user_stories + @.milestonesOrder[sprint.id][it.id] = it.sprint_order + unloadClosedSprints: -> @scope.$apply => @scope.closedSprints = [] @@ -185,6 +206,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F return @rs.sprints.list(@scope.projectId, params).then (result) => sprints = result.milestones + @.setMilestonesOrder(sprints) + @scope.totalClosedMilestones = result.closed # NOTE: Fix order of USs because the filter orderBy does not work propertly in partials files @@ -200,6 +223,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F return @rs.sprints.list(@scope.projectId, params).then (result) => sprints = result.milestones + @.setMilestonesOrder(sprints) + @scope.totalMilestones = sprints @scope.totalClosedMilestones = result.closed @scope.totalOpenMilestones = result.open @@ -221,47 +246,6 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F return sprints - restoreFilters: -> - selectedTags = @scope.oldSelectedTags - selectedStatuses = @scope.oldSelectedStatuses - - return if !selectedStatuses and !selectedStatuses - - @scope.filtersQ = @scope.filtersQOld - - @.replaceFilter("q", @scope.filtersQ) - - _.each [selectedTags, selectedStatuses], (filterGrp) => - _.each filterGrp, (item) => - filters = @scope.filters[item.type] - filter = _.find(filters, {id: item.id}) - filter.selected = true - - @.selectFilter(item.type, item.id) - - @.loadUserstories() - - resetFilters: -> - selectedTags = _.filter(@scope.filters.tags, "selected") - selectedStatuses = _.filter(@scope.filters.status, "selected") - - @scope.oldSelectedTags = selectedTags - @scope.oldSelectedStatuses = selectedStatuses - - @scope.filtersQOld = @scope.filtersQ - @scope.filtersQ = undefined - @.replaceFilter("q", @scope.filtersQ) - - _.each [selectedTags, selectedStatuses], (filterGrp) => - _.each filterGrp, (item) => - filters = @scope.filters[item.type] - filter = _.find(filters, {id: item.id}) - filter.selected = false - - @.unselectFilter(item.type, item.id) - - @.loadUserstories() - loadAllPaginatedUserstories: () -> page = @.page @@ -273,15 +257,15 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F @.loadingUserstories = true @.disablePagination = true - @scope.httpParams = @.getUrlFilters() - @rs.userstories.storeQueryParams(@scope.projectId, @scope.httpParams) + params = _.clone(@location.search()) + @rs.userstories.storeQueryParams(@scope.projectId, params) if resetPagination @.page = 1 - @scope.httpParams.page = @.page + params.page = @.page - promise = @rs.userstories.listUnassigned(@scope.projectId, @scope.httpParams, pageSize) + promise = @rs.userstories.listUnassigned(@scope.projectId, params, pageSize) return promise.then (result) => userstories = result[0] @@ -293,7 +277,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F # NOTE: Fix order of USs because the filter orderBy does not work propertly in the partials files @scope.userstories = @scope.userstories.concat(_.sortBy(userstories, "backlog_order")) - @.setSearchDataFilters() + for it in @scope.userstories + @.backlogOrder[it.id] = it.backlog_order @.loadingUserstories = false @@ -354,242 +339,142 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F return items + # --move us api behavior-- + # if your are moving multiples USs you must use the bulk api + # if there is only one US you must use patch (repo.save) + # the new US position is the position of the previous US + 1 + # if the previous US has a position value that it is equal to other USs, you must send all the USs with that position value only if they are before of the target position + # with this USs if it's a patch you must add them to the header, if is a bulk you must send them with the other USs moveUs: (ctx, usList, newUsIndex, newSprintId) -> oldSprintId = usList[0].milestone project = usList[0].project - movedFromClosedSprint = false - movedToClosedSprint = false + if oldSprintId + sprint = @scope.sprintsById[oldSprintId] || @scope.closedSprintsById[oldSprintId] - sprint = @scope.sprintsById[oldSprintId] + if newSprintId + newSprint = @scope.sprintsById[newSprintId] || @scope.closedSprintsById[newSprintId] - # Move from closed sprint - if !sprint && @scope.closedSprintsById - sprint = @scope.closedSprintsById[oldSprintId] - movedFromClosedSprint = true if sprint + currentSprintId = if newSprintId != oldSprintId then newSprintId else oldSprintId - newSprint = @scope.sprintsById[newSprintId] + orderList = null + orderField = "" - # Move to closed sprint - if !newSprint && newSprintId - newSprint = @scope.closedSprintsById[newSprintId] - movedToClosedSprint = true if newSprint + if newSprintId != oldSprintId + if newSprintId == null # From sprint to backlog + for us, key in usList # delete from sprint userstories + _.remove sprint.user_stories, (it) -> it.id == us.id - # In the same sprint or in the backlog - if newSprintId == oldSprintId - items = null - userstories = null + orderField = "backlog_order" + orderList = @.backlogOrder - if newSprintId == null - userstories = @scope.userstories - else - userstories = newSprint.user_stories + beforeDestination = _.slice(@scope.userstories, 0, newUsIndex) + afterDestination = _.slice(@scope.userstories, newUsIndex) - @scope.$apply -> - for us, key in usList - r = userstories.indexOf(us) - userstories.splice(r, 1) + @scope.userstories = @scope.userstories.concat(usList) + else # From backlog to sprint + for us in usList # delete from sprint userstories + _.remove @scope.userstories, (it) -> it.id == us.id - args = [newUsIndex, 0].concat(usList) - Array.prototype.splice.apply(userstories, args) + orderField = "sprint_order" + orderList = @.milestonesOrder[newSprint.id] - # If in backlog - if newSprintId == null - # Rehash userstories order field + beforeDestination = _.slice(newSprint.user_stories, 0, newUsIndex) + afterDestination = _.slice(newSprint.user_stories, newUsIndex) - items = @.resortUserStories(userstories, "backlog_order") - data = @.prepareBulkUpdateData(items, "backlog_order") - - # Persist in bulk all affected - # userstories with order change - @rs.userstories.bulkUpdateBacklogOrder(project, data).then => - @rootscope.$broadcast("sprint:us:moved") - - # For sprint - else - # Rehash userstories order field - items = @.resortUserStories(userstories, "sprint_order") - data = @.prepareBulkUpdateData(items, "sprint_order") - - # Persist in bulk all affected - # userstories with order change - @rs.userstories.bulkUpdateSprintOrder(project, data).then => - @rootscope.$broadcast("sprint:us:moved") - - return promise - - # From sprint to backlog - if newSprintId == null - us.milestone = null for us in usList - - @scope.$apply => - # Add new us to backlog userstories list - # @scope.userstories.splice(newUsIndex, 0, us) - args = [newUsIndex, 0].concat(usList) - Array.prototype.splice.apply(@scope.userstories, args) - - for us, key in usList - r = sprint.user_stories.indexOf(us) - sprint.user_stories.splice(r, 1) - - # Persist the milestone change of userstory - promise = @repo.save(us) - - # Rehash userstories order field - # and persist in bulk all changes. - promise = promise.then => - items = @.resortUserStories(@scope.userstories, "backlog_order") - data = @.prepareBulkUpdateData(items, "backlog_order") - return @rs.userstories.bulkUpdateBacklogOrder(us.project, data).then => - @rootscope.$broadcast("sprint:us:moved") - - if movedFromClosedSprint - @rootscope.$broadcast("backlog:load-closed-sprints") - - promise.then null, -> - console.log "FAIL" # TODO - - return promise - - # From backlog to sprint - if oldSprintId == null - us.milestone = newSprintId for us in usList - args = [newUsIndex, 0].concat(usList) - - # Add moving us to sprint user stories list - Array.prototype.splice.apply(newSprint.user_stories, args) - - # Remove moving us from backlog userstories lists. - for us, key in usList - r = @scope.userstories.indexOf(us) - @scope.userstories.splice(r, 1) - - # From sprint to sprint + newSprint.user_stories = newSprint.user_stories.concat(usList) else - us.milestone = newSprintId for us in usList + if oldSprintId == null # backlog + orderField = "backlog_order" + orderList = @.backlogOrder - @scope.$apply => - args = [newUsIndex, 0].concat(usList) + list = _.filter @scope.userstories, (listIt) -> # Remove moved US from list + return !_.find usList, (moveIt) -> return listIt.id == moveIt.id - # Add new us to backlog userstories list - Array.prototype.splice.apply(newSprint.user_stories, args) + beforeDestination = _.slice(list, 0, newUsIndex) + afterDestination = _.slice(list, newUsIndex) + else # sprint + orderField = "sprint_order" + orderList = @.milestonesOrder[sprint.id] - # Remove the us from the sprint list. - for us in usList - r = sprint.user_stories.indexOf(us) - sprint.user_stories.splice(r, 1) + list = _.filter newSprint.user_stories, (listIt) -> # Remove moved US from list + return !_.find usList, (moveIt) -> return listIt.id == moveIt.id - #Persist the milestone change of userstory - promises = _.map usList, (us) => @repo.save(us) + beforeDestination = _.slice(list, 0, newUsIndex) + afterDestination = _.slice(list, newUsIndex) - #Rehash userstories order field - #and persist in bulk all changes. - promise = @q.all(promises).then => - items = @.resortUserStories(newSprint.user_stories, "sprint_order") - data = @.prepareBulkUpdateData(items, "sprint_order") + # previous us + previous = beforeDestination[beforeDestination.length - 1] - @rs.userstories.bulkUpdateSprintOrder(project, data).then (result) => - @rootscope.$broadcast("sprint:us:moved") + # this will store the previous us with the same position + setPreviousOrders = [] - @rs.userstories.bulkUpdateBacklogOrder(project, data).then => - @rootscope.$broadcast("sprint:us:moved") + if !previous + startIndex = 0 + else if previous + startIndex = orderList[previous.id] + 1 - if movedToClosedSprint || movedFromClosedSprint - @scope.$broadcast("backlog:load-closed-sprints") + previousWithTheSameOrder = _.filter beforeDestination, (it) -> it[orderField] == orderList[previous.id] - promise.then null, -> - console.log "FAIL" # TODO + # we must send the USs previous to the dropped USs to tell the backend which USs are before the dropped USs, + # if they have the same value to order, the backend doens't know after which one do you want to drop the USs + if previousWithTheSameOrder.length > 1 + setPreviousOrders = _.map previousWithTheSameOrder, (it) -> {us_id: it.id, order: orderList[it.id]} + + modifiedUs = [] + + for us, key in usList # update sprint and new position + us.milestone = currentSprintId + us[orderField] = startIndex + key + orderList[us.id] = us[orderField] + + modifiedUs.push({us_id: us.id, order: us[orderField]}) + + startIndex = orderList[usList[usList.length - 1].id] + + for it, key in afterDestination # increase position of the us after the dragged us's + orderList[it.id] = startIndex + key + 1 + + # refresh order + @scope.userstories = _.sortBy @scope.userstories, (it) => @.backlogOrder[it.id] + + for sprint in @scope.sprints + sprint.user_stories = _.sortBy sprint.user_stories, (it) => @.milestonesOrder[sprint.id][it.id] + + for sprint in @scope.closedSprints + sprint.user_stories = _.sortBy sprint.user_stories, (it) => @.milestonesOrder[sprint.id][it.id] + + #saving + if usList.length > 1 && (newSprintId != oldSprintId) # drag multiple to sprint + data = modifiedUs.concat(setPreviousOrders) + promise = @rs.userstories.bulkUpdateMilestone(project, newSprintId, data) + else if usList.length > 1 # drag multiple in backlog + data = modifiedUs.concat(setPreviousOrders) + promise = @rs.userstories.bulkUpdateBacklogOrder(project, data) + else # drag single + setOrders = {} + for it in setPreviousOrders + setOrders[it.us_id] = it.order + + options = { + headers: { + "set-orders": JSON.stringify(setOrders) + } + } + + promise = @repo.save(usList[0], true, {}, options, true) + + promise.then () => + @rootscope.$broadcast("sprint:us:moved") + + if @scope.closedSprintsById && @scope.closedSprintsById[oldSprintId] + @rootscope.$broadcast("backlog:load-closed-sprints") return promise - isFilterSelected: (type, id) -> - if @searchdata[type]? and @searchdata[type][id] - return true - return false - - setSearchDataFilters: () -> - urlfilters = @.getUrlFilters() - - if urlfilters.q - @scope.filtersQ = @scope.filtersQ or urlfilters.q - - @searchdata = {} - for name, value of urlfilters - if not @searchdata[name]? - @searchdata[name] = {} - - for val in taiga.toString(value).split(",") - @searchdata[name][val] = true - - getUrlFilters: -> - return _.pick(@location.search(), "status", "tags", "q") - - generateFilters: -> - urlfilters = @.getUrlFilters() - @scope.filters = {} - - loadFilters = {} - loadFilters.project = @scope.projectId - loadFilters.tags = urlfilters.tags - loadFilters.status = urlfilters.status - loadFilters.q = urlfilters.q - loadFilters.milestone = 'null' - - return @rs.userstories.filtersData(loadFilters).then (data) => - choicesFiltersFormat = (choices, type, byIdObject) => - _.map choices, (t) -> - t.type = type - return t - - tagsFilterFormat = (tags) => - return _.map tags, (t) -> - t.id = t.name - t.type = 'tags' - return t - - # Build filters data structure - @scope.filters.status = choicesFiltersFormat(data.statuses, "status", @scope.usStatusById) - @scope.filters.tags = tagsFilterFormat(data.tags) - - selectedTags = _.filter(@scope.filters.tags, "selected") - selectedTags = _.map(selectedTags, "id") - - selectedStatuses = _.filter(@scope.filters.status, "selected") - selectedStatuses = _.map(selectedStatuses, "id") - - @.markSelectedFilters(@scope.filters, urlfilters) - - #store query params - @rs.userstories.storeQueryParams(@scope.projectId, { - "status": selectedStatuses, - "tags": selectedTags, - "project": @scope.projectId - "milestone": null - }) - - markSelectedFilters: (filters, urlfilters) -> - # Build selected filters (from url) fast lookup data structure - searchdata = {} - for name, value of _.omit(urlfilters, "page", "orderBy") - if not searchdata[name]? - searchdata[name] = {} - - for val in "#{value}".split(",") - searchdata[name][val] = true - - isSelected = (type, id) -> - if searchdata[type]? and searchdata[type][id] - return true - return false - - for key, value of filters - for obj in value - obj.selected = if isSelected(obj.type, obj.id) then true else undefined - ## Template actions updateUserStoryStatus: () -> - @.setSearchDataFilters() @.generateFilters().then () => @rootscope.$broadcast("filters:update") @.loadProjectStats() @@ -807,8 +692,15 @@ BacklogDirective = ($repo, $rootscope, $translate) -> text = $translate.instant("BACKLOG.TAGS.SHOW") elm.text(text) + openFilterInit = ($scope, $el, $ctrl) -> + sidebar = $el.find("sidebar.backlog-filter") + + sidebar.addClass("active") + + $ctrl.activeFilters = true + showHideFilter = ($scope, $el, $ctrl) -> - sidebar = $el.find("sidebar.filters-bar") + sidebar = $el.find("sidebar.backlog-filter") sidebar.one "transitionend", () -> timeout 150, -> $rootscope.$broadcast("resize") @@ -824,11 +716,6 @@ BacklogDirective = ($repo, $rootscope, $translate) -> toggleText(target, [hideText, showText]) - if !sidebar.hasClass("active") - $ctrl.resetFilters() - else - $ctrl.restoreFilters() - $ctrl.toggleActiveFilters() ## Filters Link @@ -847,11 +734,13 @@ BacklogDirective = ($repo, $rootscope, $translate) -> linkFilters($scope, $el, $attrs, $ctrl) linkDoomLine($scope, $el, $attrs, $ctrl) - filters = $ctrl.getUrlFilters() + filters = $ctrl.location.search() if filters.status || filters.tags || - filters.q - showHideFilter($scope, $el, $ctrl) + filters.q || + filters.assigned_to || + filters.owner + openFilterInit($scope, $el, $ctrl) $scope.$on "showTags", () -> showHideTags($ctrl) diff --git a/app/coffee/modules/backlog/sortable.coffee b/app/coffee/modules/backlog/sortable.coffee index eeb2948a..0575c96b 100644 --- a/app/coffee/modules/backlog/sortable.coffee +++ b/app/coffee/modules/backlog/sortable.coffee @@ -42,7 +42,7 @@ deleteElement = (el) -> $(el).off() $(el).remove() -BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm, $translate) -> +BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm) -> link = ($scope, $el, $attrs) -> bindOnce $scope, "project", (project) -> # If the user has not enough permissions we don't enable the sortable @@ -51,10 +51,6 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm, $translate) -> initIsBacklog = false - filterError = -> - text = $translate.instant("BACKLOG.SORTABLE_FILTER_ERROR") - $tgConfirm.notify("error", text) - drake = dragula([$el[0], $('.empty-backlog')[0]], { copySortSource: false, copy: false, @@ -63,18 +59,11 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm, $translate) -> if !$(item).hasClass('row') return false - # it doesn't move is the filter is open - parent = $(item).parent() - initIsBacklog = parent.hasClass('backlog-table-body') - - if initIsBacklog && $el.hasClass("active-filters") - filterError() - return false - return true }) drake.on 'drag', (item, container) -> + # it doesn't move is the filter is open parent = $(item).parent() initIsBacklog = parent.hasClass('backlog-table-body') @@ -88,6 +77,8 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm, $translate) -> $(item).addClass('backlog-us-mirror') drake.on 'dragend', (item) -> + parent = $(item).parent() + $('.doom-line').remove() parent = $(item).parent() @@ -102,8 +93,6 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm, $translate) -> $(document.body).removeClass("drag-active") - items = $(item).parent().find('.row') - sprint = null firstElement = if dragMultipleItems.length then dragMultipleItems[0] else item @@ -131,11 +120,7 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm, $translate) -> usList = _.map dragMultipleItems, (item) -> return item = $(item).scope().us else - usList = _.map items, (item) -> - item = $(item) - itemUs = item.scope().us - - return itemUs + usList = [$(item).scope().us] $scope.$emit("sprint:us:move", usList, index, sprint) @@ -158,6 +143,5 @@ module.directive("tgBacklogSortable", [ "$tgResources", "$rootScope", "$tgConfirm", - "$translate", BacklogSortableDirective ]) diff --git a/app/coffee/modules/base/repository.coffee b/app/coffee/modules/base/repository.coffee index 268a86a6..290bed50 100644 --- a/app/coffee/modules/base/repository.coffee +++ b/app/coffee/modules/base/repository.coffee @@ -41,7 +41,7 @@ class RepositoryService extends taiga.Service defered = @q.defer() url = @urls.resolve(name) - promise = @http.post(url, JSON.stringify(data)) + promise = @http.post(url, JSON.stringify(data), extraParams) promise.success (_data, _status) => defered.resolve(@model.make_model(name, _data, null, dataTypes)) @@ -67,7 +67,7 @@ class RepositoryService extends taiga.Service promises = _.map(models, (x) => @.save(x, true)) return @q.all(promises) - save: (model, patch=true) -> + save: (model, patch=true, params = {}, options, returnHeaders = false) -> defered = @q.defer() if not model.isModified() and patch @@ -75,20 +75,25 @@ class RepositoryService extends taiga.Service return defered.promise url = @.resolveUrlForModel(model) + data = JSON.stringify(model.getAttrs(patch)) if patch - promise = @http.patch(url, data) + promise = @http.patch(url, data, params, options) else - promise = @http.put(url, data) + promise = @http.put(url, data, params, options) - promise.success (data, status) => + promise.success (data, status, headers, response) => model._isModified = false model._attrs = _.extend(model.getAttrs(), data) model._modifiedAttrs = {} model.applyCasts() - defered.resolve(model) + + if returnHeaders + defered.resolve([model, headers()]) + else + defered.resolve(model) promise.error (data, status) -> defered.reject(data) diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index 0c8a8b4a..9f4c28c3 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -378,22 +378,28 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, .target(submitButton) .start() + params = { + include_attachments: true, + include_tasks: true + } + if $scope.isNew promise = $repo.create("userstories", $scope.us) broadcastEvent = "usform:new:success" else - promise = $repo.save($scope.us) + promise = $repo.save($scope.us, true) broadcastEvent = "usform:edit:success" promise.then (data) -> - deleteAttachments(data).then () => createAttachments(data) + deleteAttachments(data) + .then () => createAttachments(data) + .then () => + currentLoading.finish() + lightboxService.close($el) - return data + $rs.userstories.getByRef(data.project, data.ref, params).then (us) -> + $rootScope.$broadcast(broadcastEvent, us) - promise.then (data) -> - currentLoading.finish() - lightboxService.close($el) - $rootScope.$broadcast(broadcastEvent, data) promise.then null, (data) -> currentLoading.finish() @@ -433,7 +439,7 @@ module.directive("tgLbCreateEditUserstory", [ "$translate", "$tgConfirm", "$q", - "tgAttachmentsService", + "tgAttachmentsService" CreateEditUserstoryDirective ]) @@ -442,7 +448,7 @@ module.directive("tgLbCreateEditUserstory", [ ## Creare Bulk Userstories Lightbox Directive ############################################################################# -CreateBulkUserstoriesDirective = ($repo, $rs, $rootscope, lightboxService, $loading) -> +CreateBulkUserstoriesDirective = ($repo, $rs, $rootscope, lightboxService, $loading, $model) -> link = ($scope, $el, attrs) -> form = null @@ -469,6 +475,7 @@ CreateBulkUserstoriesDirective = ($repo, $rs, $rootscope, lightboxService, $load promise = $rs.userstories.bulkCreate($scope.new.projectId, $scope.new.statusId, $scope.new.bulk) promise.then (result) -> + result = _.map(result.data, (x) => $model.make_model('userstories', x)) currentLoading.finish() $rootscope.$broadcast("usform:bulk:success", result) lightboxService.close($el) @@ -494,6 +501,7 @@ module.directive("tgLbCreateBulkUserstories", [ "$rootScope", "lightboxService", "$tgLoading", + "$tgModel", CreateBulkUserstoriesDirective ]) @@ -535,7 +543,7 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic visibleUsers = _.map visibleUsers, (user) -> user.avatar = avatarService.getAvatar(user) - selected.avatar = avatarService.getAvatar(selected) + selected.avatar = avatarService.getAvatar(selected) if selected ctx = { selected: selected diff --git a/app/coffee/modules/controllerMixins.coffee b/app/coffee/modules/controllerMixins.coffee index 18724b4b..68454e25 100644 --- a/app/coffee/modules/controllerMixins.coffee +++ b/app/coffee/modules/controllerMixins.coffee @@ -110,4 +110,179 @@ class FiltersMixin location = if load then @location else @location.noreload(@scope) location.search(name, value) + applyStoredFilters: (projectSlug, key) -> + if _.isEmpty(@location.search()) + filters = @.getFilters(projectSlug, key) + if Object.keys(filters).length + @location.search(filters) + @location.replace() + + return true + + return false + + storeFilters: (projectSlug, params, filtersHashSuffix) -> + ns = "#{projectSlug}:#{filtersHashSuffix}" + hash = taiga.generateHash([projectSlug, ns]) + @storage.set(hash, params) + + getFilters: (projectSlug, filtersHashSuffix) -> + ns = "#{projectSlug}:#{filtersHashSuffix}" + hash = taiga.generateHash([projectSlug, ns]) + + return @storage.get(hash) or {} + + formatSelectedFilters: (type, list, urlIds) -> + selectedIds = urlIds.split(',') + selectedFilters = _.filter list, (it) -> + selectedIds.indexOf(_.toString(it.id)) != -1 + + return _.map selectedFilters, (it) -> + return { + id: it.id + key: type + ":" + it.id + dataType: type, + name: it.name + color: it.color + } + taiga.FiltersMixin = FiltersMixin + +############################################################################# +## Us Filters Mixin +############################################################################# + +class UsFiltersMixin + changeQ: (q) -> + @.replaceFilter("q", q) + @.filtersReloadContent() + @.generateFilters() + + removeFilter: (filter) -> + @.unselectFilter(filter.dataType, filter.id) + @.filtersReloadContent() + @.generateFilters() + + addFilter: (newFilter) -> + @.selectFilter(newFilter.category.dataType, newFilter.filter.id) + @.filtersReloadContent() + @.generateFilters() + + selectCustomFilter: (customFilter) -> + @.replaceAllFilters(customFilter.filter) + @.filtersReloadContent() + @.generateFilters() + + saveCustomFilter: (name) -> + filters = {} + urlfilters = @location.search() + filters.tags = urlfilters.tags + filters.status = urlfilters.status + filters.assigned_to = urlfilters.assigned_to + filters.owner = urlfilters.owner + + @filterRemoteStorageService.getFilters(@scope.projectId, @.storeCustomFiltersName).then (userFilters) => + userFilters[name] = filters + + @filterRemoteStorageService.storeFilters(@scope.projectId, userFilters, @.storeCustomFiltersName).then(@.generateFilters) + + removeCustomFilter: (customFilter) -> + @filterRemoteStorageService.getFilters(@scope.projectId, @.storeCustomFiltersName).then (userFilters) => + delete userFilters[customFilter.id] + + @filterRemoteStorageService.storeFilters(@scope.projectId, userFilters, @.storeCustomFiltersName).then(@.generateFilters) + @.generateFilters() + + generateFilters: -> + @.storeFilters(@params.pslug, @location.search(), @.storeFiltersName) + + urlfilters = @location.search() + + loadFilters = {} + loadFilters.project = @scope.projectId + loadFilters.tags = urlfilters.tags + loadFilters.status = urlfilters.status + loadFilters.assigned_to = urlfilters.assigned_to + loadFilters.owner = urlfilters.owner + loadFilters.q = urlfilters.q + + return @q.all([ + @rs.userstories.filtersData(loadFilters), + @filterRemoteStorageService.getFilters(@scope.projectId, @.storeCustomFiltersName) + ]).then (result) => + data = result[0] + customFiltersRaw = result[1] + + statuses = _.map data.statuses, (it) -> + it.id = it.id.toString() + + return it + tags = _.map data.tags, (it) -> + it.id = it.name + + return it + assignedTo = _.map data.assigned_to, (it) -> + if it.id + it.id = it.id.toString() + else + it.id = "null" + + it.name = it.full_name || "Unassigned" + + return it + owner = _.map data.owners, (it) -> + it.id = it.id.toString() + it.name = it.full_name + + return it + + @.selectedFilters = [] + + if loadFilters.status + selected = @.formatSelectedFilters("status", statuses, loadFilters.status) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.tags + selected = @.formatSelectedFilters("tags", tags, loadFilters.tags) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.assigned_to + selected = @.formatSelectedFilters("assigned_to", assignedTo, loadFilters.assigned_to) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.owner + selected = @.formatSelectedFilters("owner", owner, loadFilters.owner) + @.selectedFilters = @.selectedFilters.concat(selected) + + @.filterQ = loadFilters.q + + @.filters = [ + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.STATUS"), + dataType: "status", + content: statuses + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.TAGS"), + dataType: "tags", + content: tags, + hideEmpty: true + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.ASSIGNED_TO"), + dataType: "assigned_to", + content: assignedTo + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.CREATED_BY"), + dataType: "owner", + content: owner + } + ]; + + @.customFilters = [] + _.forOwn customFiltersRaw, (value, key) => + @.customFilters.push({id: key, name: key, filter: value}) + + +taiga.UsFiltersMixin = UsFiltersMixin diff --git a/app/coffee/modules/issues/list.coffee b/app/coffee/modules/issues/list.coffee index a8cfa0b9..8c5c3c18 100644 --- a/app/coffee/modules/issues/list.coffee +++ b/app/coffee/modules/issues/list.coffee @@ -32,6 +32,7 @@ groupBy = @.taiga.groupBy bindOnce = @.taiga.bindOnce debounceLeading = @.taiga.debounceLeading startswith = @.taiga.startswith +bindMethods = @.taiga.bindMethods module = angular.module("taigaIssues") @@ -55,21 +56,23 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi "$tgEvents", "$tgAnalytics", "$translate", - "tgErrorHandlingService" + "tgErrorHandlingService", + "$tgStorage", + "tgFilterRemoteStorageService" ] + filtersHashSuffix: "issues-filters" + myFiltersHashSuffix: "issues-my-filters" + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @urls, @params, @q, @location, @appMetaService, - @navUrls, @events, @analytics, @translate, @errorHandlingService) -> + @navUrls, @events, @analytics, @translate, @errorHandlingService, @storage, @filterRemoteStorageService) -> + bindMethods(@) + @scope.sectionName = "Issues" @scope.filters = {} @.voting = false - if _.isEmpty(@location.search()) - filters = @rs.issues.getFilters(@params.pslug) - filters.page = 1 - @location.search(filters) - @location.replace() - return + return if @.applyStoredFilters(@params.pslug, @.filtersHashSuffix) promise = @.loadInitialData() @@ -89,13 +92,196 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi @analytics.trackEvent("issue", "create", "create issue on issues list", 1) @.loadIssues() + changeQ: (q) -> + @.unselectFilter("page") + @.replaceFilter("q", q) + @.loadIssues() + @.generateFilters() + + removeFilter: (filter) -> + @.unselectFilter("page") + @.unselectFilter(filter.dataType, filter.id) + @.loadIssues() + @.generateFilters() + + addFilter: (newFilter) -> + @.unselectFilter("page") + @.selectFilter(newFilter.category.dataType, newFilter.filter.id) + @.loadIssues() + @.generateFilters() + + selectCustomFilter: (customFilter) -> + orderBy = @location.search().order_by + + if orderBy + customFilter.filter.order_by = orderBy + + @.unselectFilter("page") + @.replaceAllFilters(customFilter.filter) + @.loadIssues() + @.generateFilters() + + removeCustomFilter: (customFilter) -> + console.log "oooo" + @filterRemoteStorageService.getFilters(@scope.projectId, @.myFiltersHashSuffix).then (userFilters) => + console.log userFilters[customFilter.id] + delete userFilters[customFilter.id] + + @filterRemoteStorageService.storeFilters(@scope.projectId, userFilters, @.myFiltersHashSuffix).then(@.generateFilters) + + saveCustomFilter: (name) -> + filters = {} + urlfilters = @location.search() + filters.tags = urlfilters.tags + filters.status = urlfilters.status + filters.type = urlfilters.type + filters.severity = urlfilters.severity + filters.priority = urlfilters.priority + filters.assigned_to = urlfilters.assigned_to + filters.owner = urlfilters.owner + + @filterRemoteStorageService.getFilters(@scope.projectId, @.myFiltersHashSuffix).then (userFilters) => + userFilters[name] = filters + + @filterRemoteStorageService.storeFilters(@scope.projectId, userFilters, @.myFiltersHashSuffix).then(@.generateFilters) + + generateFilters: -> + @.storeFilters(@params.pslug, @location.search(), @.filtersHashSuffix) + + urlfilters = @location.search() + + loadFilters = {} + loadFilters.project = @scope.projectId + loadFilters.tags = urlfilters.tags + loadFilters.status = urlfilters.status + loadFilters.type = urlfilters.type + loadFilters.severity = urlfilters.severity + loadFilters.priority = urlfilters.priority + loadFilters.assigned_to = urlfilters.assigned_to + loadFilters.owner = urlfilters.owner + loadFilters.q = urlfilters.q + + return @q.all([ + @rs.issues.filtersData(loadFilters), + @filterRemoteStorageService.getFilters(@scope.projectId, @.myFiltersHashSuffix) + ]).then (result) => + data = result[0] + customFiltersRaw = result[1] + + statuses = _.map data.statuses, (it) -> + it.id = it.id.toString() + + return it + type = _.map data.types, (it) -> + it.id = it.id.toString() + + return it + severity = _.map data.severities, (it) -> + it.id = it.id.toString() + + return it + priority = _.map data.priorities, (it) -> + it.id = it.id.toString() + + return it + tags = _.map data.tags, (it) -> + it.id = it.name + + return it + assignedTo = _.map data.assigned_to, (it) -> + if it.id + it.id = it.id.toString() + else + it.id = "null" + + it.name = it.full_name || "Unassigned" + + return it + owner = _.map data.owners, (it) -> + it.id = it.id.toString() + it.name = it.full_name + + return it + + @.selectedFilters = [] + + if loadFilters.status + selected = @.formatSelectedFilters("status", statuses, loadFilters.status) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.tags + selected = @.formatSelectedFilters("tags", tags, loadFilters.tags) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.assigned_to + selected = @.formatSelectedFilters("assigned_to", assignedTo, loadFilters.assigned_to) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.owner + selected = @.formatSelectedFilters("owner", owner, loadFilters.owner) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.type + selected = @.formatSelectedFilters("type", type, loadFilters.type) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.severity + selected = @.formatSelectedFilters("severity", severity, loadFilters.severity) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.priority + selected = @.formatSelectedFilters("priority", priority, loadFilters.priority) + @.selectedFilters = @.selectedFilters.concat(selected) + + @.filterQ = loadFilters.q + + @.filters = [ + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.TYPE"), + dataType: "type", + content: type + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.SEVERITY"), + dataType: "severity", + content: severity + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.PRIORITIES"), + dataType: "priority", + content: priority + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.STATUS"), + dataType: "status", + content: statuses + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.TAGS"), + dataType: "tags", + content: tags + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.ASSIGNED_TO"), + dataType: "assigned_to", + content: assignedTo + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.CREATED_BY"), + dataType: "owner", + content: owner + } + ]; + + @.customFilters = [] + _.forOwn customFiltersRaw, (value, key) => + @.customFilters.push({id: key, name: key, filter: value}) + initializeSubscription: -> routingKey = "changes.project.#{@scope.projectId}.issues" @events.subscribe @scope, routingKey, (message) => @.loadIssues() - storeFilters: -> - @rs.issues.storeFilters(@params.pslug, @location.search()) loadProject: -> return @rs.projects.getBySlug(@params.pslug).then (project) => @@ -117,160 +303,15 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi return project - getUrlFilters: -> - filters = _.pick(@location.search(), "page", "tags", "status", "types", - "q", "severities", "priorities", - "assignedTo", "createdBy", "orderBy") - - filters.page = 1 if not filters.page - return filters - - getUrlFilter: (name) -> - filters = _.pick(@location.search(), name) - return filters[name] - - loadMyFilters: -> - return @rs.issues.getMyFilters(@scope.projectId).then (filters) => - return _.map filters, (value, key) => - return {id: key, name: key, type: "myFilters", selected: false} - - removeNotExistingFiltersFromUrl: -> - currentSearch = @location.search() - urlfilters = @.getUrlFilters() - - for filterName, filterValue of urlfilters - if filterName == "page" or filterName == "orderBy" or filterName == "q" - continue - - if filterName == "tags" - splittedValues = _.map("#{filterValue}".split(",")) - else - splittedValues = _.map("#{filterValue}".split(","), (x) -> if x == "null" then null else parseInt(x)) - - existingValues = _.intersection(splittedValues, _.map(@scope.filters[filterName], "id")) - if splittedValues.length != existingValues.length - @location.search(filterName, existingValues.join()) - - if currentSearch != @location.search() - @location.replace() - - markSelectedFilters: (filters, urlfilters) -> - # Build selected filters (from url) fast lookup data structure - searchdata = {} - for name, value of _.omit(urlfilters, "page", "orderBy") - if not searchdata[name]? - searchdata[name] = {} - - for val in "#{value}".split(",") - searchdata[name][val] = true - - isSelected = (type, id) -> - if searchdata[type]? and searchdata[type][id] - return true - return false - - for key, value of filters - for obj in value - obj.selected = if isSelected(obj.type, obj.id) then true else undefined - - loadFilters: () -> - urlfilters = @.getUrlFilters() - - if urlfilters.q - @scope.filtersQ = urlfilters.q - - # Load My Filters - promise = @.loadMyFilters().then (myFilters) => - @scope.filters.myFilters = myFilters - return myFilters - - loadFilters = {} - loadFilters.project = @scope.projectId - loadFilters.tags = urlfilters.tags - loadFilters.status = urlfilters.status - loadFilters.q = urlfilters.q - loadFilters.types = urlfilters.types - loadFilters.severities = urlfilters.severities - loadFilters.priorities = urlfilters.priorities - loadFilters.assigned_to = urlfilters.assignedTo - loadFilters.owner = urlfilters.createdBy - - # Load default filters data - promise = promise.then => - return @rs.issues.filtersData(loadFilters) - - # Format filters and set them on scope - return promise.then (data) => - usersFiltersFormat = (users, type, unknownOption) => - reformatedUsers = _.map users, (t) => - t.type = type - t.name = if t.full_name then t.full_name else unknownOption - - return t - - unknownItem = _.remove(reformatedUsers, (u) -> not u.id) - reformatedUsers = _.sortBy(reformatedUsers, (u) -> u.name.toUpperCase()) - if unknownItem.length > 0 - reformatedUsers.unshift(unknownItem[0]) - return reformatedUsers - - choicesFiltersFormat = (choices, type, byIdObject) => - _.map choices, (t) -> - t.type = type - return t - - tagsFilterFormat = (tags) => - return _.map tags, (t) -> - t.id = t.name - t.type = 'tags' - return t - - # Build filters data structure - @scope.filters.status = choicesFiltersFormat(data.statuses, "status", @scope.issueStatusById) - @scope.filters.severities = choicesFiltersFormat(data.severities, "severities", @scope.severityById) - @scope.filters.priorities = choicesFiltersFormat(data.priorities, "priorities", @scope.priorityById) - @scope.filters.assignedTo = usersFiltersFormat(data.assigned_to, "assignedTo", "Unassigned") - @scope.filters.createdBy = usersFiltersFormat(data.owners, "createdBy", "Unknown") - @scope.filters.types = choicesFiltersFormat(data.types, "types", @scope.issueTypeById) - @scope.filters.tags = tagsFilterFormat(data.tags) - - @.removeNotExistingFiltersFromUrl() - @.markSelectedFilters(@scope.filters, urlfilters) - - @rootscope.$broadcast("filters:loaded", @scope.filters) - # We need to guarantee that the last petition done here is the finally used # When searching by text loadIssues can be called fastly with different parameters and # can be resolved in a different order than generated # We count the requests made and only if the callback is for the last one data is updated loadIssuesRequests: 0 loadIssues: => - @scope.urlFilters = @.getUrlFilters() + params = @location.search() - # Convert stored filters to http parameters - # ready filters (the name difference exists - # because of some automatic lookups and is - # the simplest way todo it without adding - # additional complexity to code. - @scope.httpParams = {} - for name, values of @scope.urlFilters - if name == "severities" - name = "severity" - else if name == "orderBy" - name = "order_by" - else if name == "priorities" - name = "priority" - else if name == "assignedTo" - name = "assigned_to" - else if name == "createdBy" - name = "owner" - else if name == "status" - name = "status" - else if name == "types" - name = "type" - @scope.httpParams[name] = values - - promise = @rs.issues.list(@scope.projectId, @scope.httpParams) + promise = @rs.issues.list(@scope.projectId, params) @.loadIssuesRequests += 1 promise.index = @.loadIssuesRequests promise.then (data) => @@ -289,26 +330,10 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi return promise.then (project) => @.fillUsersAndRoles(project.members, project.roles) @.initializeSubscription() - @.loadFilters() + @.generateFilters() return @.loadIssues() - saveCurrentFiltersTo: (newFilter) -> - deferred = @q.defer() - @rs.issues.getMyFilters(@scope.projectId).then (filters) => - filters[newFilter] = @location.search() - @rs.issues.storeMyFilters(@scope.projectId, filters).then => - deferred.resolve() - return deferred.promise - - deleteMyFilter: (filter) -> - deferred = @q.defer() - @rs.issues.getMyFilters(@scope.projectId).then (filters) => - delete filters[filter] - @rs.issues.storeMyFilters(@scope.projectId, filters).then => - deferred.resolve() - return deferred.promise - # Functions used from templates addNewIssue: -> @rootscope.$broadcast("issueform:new", @scope.project) @@ -338,6 +363,12 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi return @rs.issues.downvote(issueId).then(onSuccess, onError) + getOrderBy: -> + if _.isString(@location.search().order_by) + return @location.search().order_by + else + return "created_date" + module.controller("IssuesController", IssuesController) ############################################################################# @@ -431,28 +462,40 @@ IssuesDirective = ($log, $location, $template, $compile) -> ## Issues Filters linkOrdering = ($scope, $el, $attrs, $ctrl) -> # Draw the arrow the first time - currentOrder = $ctrl.getUrlFilter("orderBy") or "created_date" + + currentOrder = $ctrl.getOrderBy() + if currentOrder - icon = if startswith(currentOrder, "-") then "icon-arrow-up" else "icon-arrow-bottom" + icon = if startswith(currentOrder, "-") then "icon-arrow-up" else "icon-arrow-down" colHeadElement = $el.find(".row.title > div[data-fieldname='#{trim(currentOrder, "-")}']") - colHeadElement.html("#{colHeadElement.html()}") + + svg = $("").attr("svg-icon", icon) + + colHeadElement.append(svg) + $compile(colHeadElement.contents())($scope); $el.on "click", ".row.title > div", (event) -> target = angular.element(event.currentTarget) - currentOrder = $ctrl.getUrlFilter("orderBy") + currentOrder = $ctrl.getOrderBy() newOrder = target.data("fieldname") finalOrder = if currentOrder == newOrder then "-#{newOrder}" else newOrder $scope.$apply -> - $ctrl.replaceFilter("orderBy", finalOrder) - $ctrl.storeFilters() + $ctrl.replaceFilter("order_by", finalOrder) + + $ctrl.storeFilters($ctrl.params.pslug, $location.search(), $ctrl.filtersHashSuffix) $ctrl.loadIssues().then -> # Update the arrow - $el.find(".row.title > div > span.icon").remove() - icon = if startswith(finalOrder, "-") then "icon-arrow-up" else "icon-arrow-bottom" - target.html("#{target.html()}") + $el.find(".row.title > div > tg-svg").remove() + icon = if startswith(finalOrder, "-") then "icon-arrow-up" else "icon-arrow-down" + + svg = $("") + .attr("svg-icon", icon) + + target.append(svg) + $compile(target.contents())($scope); ## Issues Link link = ($scope, $el, $attrs) -> @@ -468,253 +511,6 @@ IssuesDirective = ($log, $location, $template, $compile) -> module.directive("tgIssues", ["$log", "$tgLocation", "$tgTemplate", "$compile", IssuesDirective]) -############################################################################# -## Issues Filters Directive -############################################################################# - -IssuesFiltersDirective = ($q, $log, $location, $rs, $confirm, $loading, $template, $translate, $compile, $auth) -> - template = $template.get("issue/issues-filters.html", true) - templateSelected = $template.get("issue/issues-filters-selected.html", true) - - link = ($scope, $el, $attrs) -> - $ctrl = $el.closest(".wrapper").controller() - - selectedFilters = [] - - showFilters = (title, type) -> - $el.find(".filters-cats").hide() - $el.find(".filter-list").removeClass("hidden") - $el.find(".breadcrumb").removeClass("hidden") - $el.find("h2 .subfilter .title").html(title) - $el.find("h2 .subfilter .title").prop("data-type", type) - - showCategories = -> - $el.find(".filters-cats").show() - $el.find(".filter-list").addClass("hidden") - $el.find(".breadcrumb").addClass("hidden") - - initializeSelectedFilters = (filters) -> - selectedFilters = [] - for name, values of filters - for val in values - selectedFilters.push(val) if val.selected - - renderSelectedFilters(selectedFilters) - - renderSelectedFilters = (selectedFilters) -> - _.filter selectedFilters, (f) => - if f.color - f.style = "border-left: 3px solid #{f.color}" - - html = templateSelected({filters:selectedFilters}) - html = $compile(html)($scope) - $el.find(".filters-applied").html(html) - - if $auth.isAuthenticated() && selectedFilters.length > 0 - $el.find(".save-filters").show() - else - $el.find(".save-filters").hide() - - renderFilters = (filters) -> - _.filter filters, (f) => - if f.color - f.style = "border-left: 3px solid #{f.color}" - - html = template({filters:filters}) - html = $compile(html)($scope) - $el.find(".filter-list").html(html) - - getFiltersType = () -> - return $el.find(".subfilter .title").prop('data-type') - - reloadIssues = () -> - currentFiltersType = getFiltersType() - - $q.all([$ctrl.loadIssues(), $ctrl.loadFilters()]).then () -> - filters = $scope.filters[currentFiltersType] - renderFilters(_.reject(filters, "selected")) - - toggleFilterSelection = (type, id) -> - if type == "myFilters" - $rs.issues.getMyFilters($scope.projectId).then (data) -> - myFilters = data - filters = myFilters[id] - filters.page = 1 - $ctrl.replaceAllFilters(filters) - $ctrl.storeFilters() - $ctrl.loadIssues() - $ctrl.markSelectedFilters($scope.filters, filters) - initializeSelectedFilters($scope.filters) - return null - - filters = $scope.filters[type] - filterId = if type == 'tags' then taiga.toString(id) else id - filter = _.find(filters, {id: filterId}) - filter.selected = (not filter.selected) - - # Convert id to null as string for properly - # put null value on url parameters - id = "null" if id is null - - if filter.selected - selectedFilters.push(filter) - $ctrl.selectFilter(type, id) - $ctrl.selectFilter("page", 1) - $ctrl.storeFilters() - else - selectedFilters = _.reject selectedFilters, (f) -> - return f.id == filter.id && f.type == filter.type - - $ctrl.unselectFilter(type, id) - $ctrl.selectFilter("page", 1) - $ctrl.storeFilters() - - reloadIssues() - - renderSelectedFilters(selectedFilters) - - currentFiltersType = getFiltersType() - - if type == currentFiltersType - renderFilters(_.reject(filters, "selected")) - - # Angular Watchers - $scope.$on "filters:loaded", (ctx, filters) -> - initializeSelectedFilters(filters) - - $scope.$on "filters:issueupdate", (ctx, filters) -> - html = template({filters:filters.status}) - html = $compile(html)($scope) - $el.find(".filter-list").html(html) - - selectQFilter = debounceLeading 100, (value, oldValue) -> - return if value is undefined or value == oldValue - - $ctrl.replaceFilter("page", null, true) - - if value.length == 0 - $ctrl.replaceFilter("q", null) - $ctrl.storeFilters() - else - $ctrl.replaceFilter("q", value) - $ctrl.storeFilters() - - reloadIssues() - - unwatchIssues = $scope.$watch "issues", (newValue) -> - if !_.isUndefined(newValue) - $scope.$watch("filtersQ", selectQFilter) - unwatchIssues() - - # Dom Event Handlers - $el.on "click", ".filters-cat-single", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget) - tags = $scope.filters[target.data("type")] - renderFilters(_.reject(tags, "selected")) - showFilters(target.attr("title"), target.data("type")) - - $el.on "click", ".back", (event) -> - event.preventDefault() - showCategories($el) - - $el.on "click", ".filters-applied .remove-filter", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget).parent() - - id = target.data("id") or null - type = target.data("type") - toggleFilterSelection(type, id) - - $el.on "click", ".filter-list .single-filter", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget) - target.toggleClass("active") - - id = target.data("id") or null - type = target.data("type") - - # A saved filter can't be active - if type == "myFilters" - target.removeClass("active") - - toggleFilterSelection(type, id) - - $el.on "click", ".filter-list .remove-filter", (event) -> - event.preventDefault() - event.stopPropagation() - - target = angular.element(event.currentTarget) - customFilterName = target.parent().data('id') - title = $translate.instant("ISSUES.FILTERS.CONFIRM_DELETE.TITLE") - message = $translate.instant("ISSUES.FILTERS.CONFIRM_DELETE.MESSAGE", {customFilterName: customFilterName}) - - $confirm.askOnDelete(title, message).then (askResponse) -> - promise = $ctrl.deleteMyFilter(customFilterName) - promise.then -> - promise = $ctrl.loadMyFilters() - promise.then (filters) -> - askResponse.finish() - $scope.filters.myFilters = filters - renderFilters($scope.filters.myFilters) - promise.then null, -> - askResponse.finish() - promise.then null, -> - askResponse.finish(false) - $confirm.notify("error") - - - $el.on "click", ".save-filters", (event) -> - event.preventDefault() - renderFilters($scope.filters["myFilters"]) - showFilters("My filters", "myFilters") - $el.find('.save-filters').hide() - $el.find('.my-filter-name').removeClass("hidden") - $el.find('.my-filter-name').focus() - $scope.$apply() - - $el.on "keyup", ".my-filter-name", (event) -> - event.preventDefault() - if event.keyCode == 13 - target = angular.element(event.currentTarget) - newFilter = target.val() - currentLoading = $loading() - .target($el.find(".new")) - .start() - promise = $ctrl.saveCurrentFiltersTo(newFilter) - promise.then -> - loadPromise = $ctrl.loadMyFilters() - loadPromise.then (filters) -> - currentLoading.finish() - $scope.filters.myFilters = filters - - currentfilterstype = $el.find("h2 .subfilter .title").prop('data-type') - if currentfilterstype == "myFilters" - renderFilters($scope.filters.myFilters) - - $el.find('.my-filter-name').addClass("hidden") - $el.find('.save-filters').show() - - loadPromise.then null, -> - currentLoading.finish() - $confirm.notify("error", "Error loading custom filters") - - promise.then null, -> - currentLoading.finish() - $el.find(".my-filter-name").val(newFilter).focus().select() - $confirm.notify("error", "Filter not saved") - - else if event.keyCode == 27 - $el.find('.my-filter-name').val('') - $el.find('.my-filter-name').addClass("hidden") - $el.find('.save-filters').show() - - return {link:link} - -module.directive("tgIssuesFilters", ["$q", "$log", "$tgLocation", "$tgResources", "$tgConfirm", "$tgLoading", - "$tgTemplate", "$translate", "$compile", "$tgAuth", IssuesFiltersDirective]) - - ############################################################################# ## Issue status Directive (popover for change status) ############################################################################# diff --git a/app/coffee/modules/kanban/kanban-usertories.coffee b/app/coffee/modules/kanban/kanban-usertories.coffee new file mode 100644 index 00000000..0e811b13 --- /dev/null +++ b/app/coffee/modules/kanban/kanban-usertories.coffee @@ -0,0 +1,189 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: kanban-userstories.service.coffee +### + +groupBy = @.taiga.groupBy + +class KanbanUserstoriesService extends taiga.Service + @.$inject = [] + + constructor: () -> + @.reset() + + reset: () -> + @.userstoriesRaw = [] + @.archivedStatus = [] + @.statusHide = [] + @.foldStatusChanged = {} + @.usByStatus = Immutable.Map() + + init: (project, usersById) -> + @.project = project + @.usersById = usersById + + resetFolds: () -> + @.foldStatusChanged = {} + @.refresh() + + toggleFold: (usId) -> + @.foldStatusChanged[usId] = !@.foldStatusChanged[usId] + @.refresh() + + set: (userstories) -> + @.userstoriesRaw = userstories + @.refreshRawOrder() + @.refresh() + + add: (us) -> + @.userstoriesRaw = @.userstoriesRaw.concat(us) + @.refreshRawOrder() + @.refresh() + + addArchivedStatus: (statusId) -> + @.archivedStatus.push(statusId) + + isUsInArchivedHiddenStatus: (usId) -> + us = @.getUsModel(usId) + + return @.archivedStatus.indexOf(us.status) != -1 && + @.statusHide.indexOf(us.status) != -1 + + hideStatus: (statusId) -> + @.deleteStatus(statusId) + @.statusHide.push(statusId) + + showStatus: (statusId) -> + _.remove @.statusHide, (it) -> return it == statusId + + getStatus: (statusId) -> + return _.filter @.userstoriesRaw, (us) -> return us.status == statusId + + deleteStatus: (statusId) -> + toDelete = _.filter @.userstoriesRaw, (us) -> return us.status == statusId + toDelete = _.map (it) -> return it.id + + @.archived = _.difference(@.archived, toDelete) + + @.userstoriesRaw = _.filter @.userstoriesRaw, (us) -> return us.status != statusId + + @.refresh() + + refreshRawOrder: () -> + @.order = {} + + @.order[it.id] = it.kanban_order for it in @.userstoriesRaw + + assignOrders: (order) -> + order = _.invert(order) + @.order = _.assign(@.order, order) + + @.refresh() + + move: (id, statusId, index) -> + us = @.getUsModel(id) + + usByStatus = _.filter @.userstoriesRaw, (it) => + return it.status == statusId + + usByStatus = _.sortBy usByStatus, (it) => @.order[it.id] + + usByStatusWithoutMoved = _.filter usByStatus, (it) => it.id != id + beforeDestination = _.slice(usByStatusWithoutMoved, 0, index) + afterDestination = _.slice(usByStatusWithoutMoved, index) + + setOrders = {} + + previous = beforeDestination[beforeDestination.length - 1] + + previousWithTheSameOrder = _.filter beforeDestination, (it) => + @.order[it.id] == @.order[previous.id] + + if previousWithTheSameOrder.length > 1 + for it in previousWithTheSameOrder + setOrders[it.id] = @.order[it.id] + + if !previous + @.order[us.id] = 0 + else if previous + @.order[us.id] = @.order[previous.id] + 1 + + for it, key in afterDestination + @.order[it.id] = @.order[us.id] + key + 1 + + us.status = statusId + us.kanban_order = @.order[us.id] + + @.refresh() + + return {"us_id": us.id, "order": @.order[us.id], "set_orders": setOrders} + + replace: (us) -> + @.usByStatus = @.usByStatus.map (status) -> + findedIndex = status.findIndex (usItem) -> + return usItem.get('id') == us.get('id') + + if findedIndex != -1 + status = status.set(findedIndex, us) + + return status + + replaceModel: (us) -> + @.userstoriesRaw = _.map @.userstoriesRaw, (usItem) -> + if us.id == usItem.id + return us + else + return usItem + + @.refresh() + + getUs: (id) -> + findedUs = null + + @.usByStatus.forEach (status) -> + findedUs = status.find (us) -> return us.get('id') == id + + return false if findedUs + + return findedUs + + getUsModel: (id) -> + return _.find @.userstoriesRaw, (us) -> return us.id == id + + refresh: -> + @.userstoriesRaw = _.sortBy @.userstoriesRaw, (it) => @.order[it.id] + + userstories = @.userstoriesRaw + userstories = _.map userstories, (usModel) => + us = {} + us.foldStatusChanged = @.foldStatusChanged[usModel.id] + us.model = usModel.getAttrs() + us.images = _.filter usModel.attachments, (it) -> return !!it.thumbnail_card_url + us.id = usModel.id + us.assigned_to = @.usersById[usModel.assigned_to] + us.colorized_tags = _.map us.model.tags, (tag) => + color = @.project.tags_colors[tag] + return {name: tag, color: color} + + return us + + usByStatus = _.groupBy userstories, (us) -> + return us.model.status + + @.usByStatus = Immutable.fromJS(usByStatus) + +angular.module("taigaKanban").service("tgKanbanUserstories", KanbanUserstoriesService) diff --git a/app/coffee/modules/kanban/main.coffee b/app/coffee/modules/kanban/main.coffee index 1d95219e..7b875662 100644 --- a/app/coffee/modules/kanban/main.coffee +++ b/app/coffee/modules/kanban/main.coffee @@ -34,26 +34,18 @@ bindMethods = @.taiga.bindMethods module = angular.module("taigaKanban") -# Vars - -defaultViewMode = "maximized" -viewModes = [ - "maximized", - "minimized" -] - - ############################################################################# ## Kanban Controller ############################################################################# -class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) +class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin, taiga.UsFiltersMixin) @.$inject = [ "$scope", "$rootScope", "$tgRepo", "$tgConfirm", "$tgResources", + "tgResources", "$routeParams", "$q", "$tgLocation", @@ -62,16 +54,26 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi "$tgEvents", "$tgAnalytics", "$translate", - "tgErrorHandlingService" + "tgErrorHandlingService", + "$tgModel", + "tgKanbanUserstories", + "$tgStorage", + "tgFilterRemoteStorageService" ] - constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, - @appMetaService, @navUrls, @events, @analytics, @translate, @errorHandlingService) -> + storeCustomFiltersName: 'kanban-custom-filters' + storeFiltersName: 'kanban-filters' + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @rs2, @params, @q, @location, + @appMetaService, @navUrls, @events, @analytics, @translate, @errorHandlingService, + @model, @kanbanUserstoriesService, @storage, @filterRemoteStorageService) -> bindMethods(@) + @kanbanUserstoriesService.reset() + @.openFilter = false + + return if @.applyStoredFilters(@params.pslug, "kanban-filters") @scope.sectionName = @translate.instant("KANBAN.SECTION_NAME") - @scope.statusViewModes = {} @.initializeEventHandlers() promise = @.loadInitialData() @@ -88,80 +90,106 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi # On Error promise.then null, @.onInitialDataError.bind(@) + taiga.defineImmutableProperty @.scope, "usByStatus", () => + return @kanbanUserstoriesService.usByStatus + + setZoom: (zoomLevel, zoom) -> + if @.zoomLevel != zoomLevel + @kanbanUserstoriesService.resetFolds() + + @.zoomLevel = zoomLevel + @.zoom = zoom + + filtersReloadContent: () -> + @.loadUserstories().then () => + openArchived = _.difference(@kanbanUserstoriesService.archivedStatus, @kanbanUserstoriesService.statusHide) + if openArchived.length + for statusId in openArchived + @.loadUserStoriesForStatus({}, statusId) + initializeEventHandlers: -> - @scope.$on "usform:new:success", => - @.loadUserstories() - @.refreshTagsColors() + @scope.$on "usform:new:success", (event, us) => + @.refreshTagsColors().then () => + @kanbanUserstoriesService.add(us) + @analytics.trackEvent("userstory", "create", "create userstory on kanban", 1) - @scope.$on "usform:bulk:success", => - @.loadUserstories() + @scope.$on "usform:bulk:success", (event, uss) => + @.refreshTagsColors().then () => + @kanbanUserstoriesService.add(uss) + @analytics.trackEvent("userstory", "create", "bulk create userstory on kanban", 1) - @scope.$on "usform:edit:success", => - @.loadUserstories() - @.refreshTagsColors() + @scope.$on "usform:edit:success", (event, us) => + @.refreshTagsColors().then () => + @kanbanUserstoriesService.replaceModel(us) @scope.$on("assigned-to:added", @.onAssignedToChanged) @scope.$on("kanban:us:move", @.moveUs) @scope.$on("kanban:show-userstories-for-status", @.loadUserStoriesForStatus) @scope.$on("kanban:hide-userstories-for-status", @.hideUserStoriesForStatus) - # Template actions - addNewUs: (type, statusId) -> switch type when "standard" then @rootscope.$broadcast("usform:new", @scope.projectId, statusId, @scope.usStatusList) when "bulk" then @rootscope.$broadcast("usform:bulk", @scope.projectId, statusId) - changeUsAssignedTo: (us) -> + editUs: (id) -> + us = @kanbanUserstoriesService.getUs(id) + us = us.set('loading', true) + @kanbanUserstoriesService.replace(us) + + @rs.userstories.getByRef(us.getIn(['model', 'project']), us.getIn(['model', 'ref'])) + .then (editingUserStory) => + @rs2.attachments.list("us", us.get('id'), us.getIn(['model', 'project'])).then (attachments) => + @rootscope.$broadcast("usform:edit", editingUserStory, attachments.toJS()) + + us = us.set('loading', false) + @kanbanUserstoriesService.replace(us) + + showPlaceHolder: (statusId) -> + if @scope.usStatusList[0].id == statusId && + !@kanbanUserstoriesService.userstoriesRaw.length + return true + + return false + + toggleFold: (id) -> + @kanbanUserstoriesService.toggleFold(id) + + isUsInArchivedHiddenStatus: (usId) -> + return @kanbanUserstoriesService.isUsInArchivedHiddenStatus(usId) + + changeUsAssignedTo: (id) -> + us = @kanbanUserstoriesService.getUsModel(id) + @rootscope.$broadcast("assigned-to:add", us) - # Scope Events Handlers + onAssignedToChanged: (ctx, userid, usModel) -> + usModel.assigned_to = userid - onAssignedToChanged: (ctx, userid, us) -> - us.assigned_to = userid + @kanbanUserstoriesService.replaceModel(usModel) - promise = @repo.save(us) + promise = @repo.save(usModel) promise.then null, -> console.log "FAIL" # TODO - # Load data methods refreshTagsColors: -> return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) => @scope.project.tags_colors = tags_colors loadUserstories: -> params = { - status__is_archived: false + status__is_archived: false, + include_attachments: true, + include_tasks: true } + params = _.merge params, @location.search() + promise = @rs.userstories.listAll(@scope.projectId, params).then (userstories) => - @scope.userstories = userstories - - usByStatus = _.groupBy(userstories, "status") - us_archived = [] - for status in @scope.usStatusList - if not usByStatus[status.id]? - usByStatus[status.id] = [] - if @scope.usByStatus? - for us in @scope.usByStatus[status.id] - if us.status != status.id - us_archived.push(us) - - # Must preserve the archived columns if loaded - if status.is_archived and @scope.usByStatus? and @scope.usByStatus[status.id].length != 0 - for us in @scope.usByStatus[status.id].concat(us_archived) - if us.status == status.id - usByStatus[status.id].push(us) - - usByStatus[status.id] = _.sortBy(usByStatus[status.id], "kanban_order") - - if userstories.length == 0 - status = @scope.usStatusList[0] - usByStatus[status.id].push({isPlaceholder: true}) - - @scope.usByStatus = usByStatus + @kanbanUserstoriesService.init(@scope.project, @scope.usersById) + @kanbanUserstoriesService.set(userstories) # The broadcast must be executed when the DOM has been fully reloaded. # We can't assure when this exactly happens so we need a defer @@ -175,14 +203,28 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi return promise loadUserStoriesForStatus: (ctx, statusId) -> - params = { status: statusId } + filteredStatus = @location.search().status + + # if there are filters applied the action doesn't end if the statusId is not in the url + if filteredStatus + filteredStatus = filteredStatus.split(",").map (it) -> parseInt(it, 10) + + return if filteredStatus.indexOf(statusId) == -1 + + params = { + status: statusId + include_attachments: true, + include_tasks: true + } + + params = _.merge params, @location.search() + return @rs.userstories.listAll(@scope.projectId, params).then (userstories) => - @scope.usByStatus[statusId] = _.sortBy(userstories, "kanban_order") @scope.$broadcast("kanban:shown-userstories-for-status", statusId, userstories) + return userstories hideUserStoriesForStatus: (ctx, statusId) -> - @scope.usByStatus[statusId] = [] @scope.$broadcast("kanban:hidden-userstories-for-status", statusId) loadKanban: -> @@ -204,8 +246,6 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi @scope.usStatusById = groupBy(project.us_statuses, (x) -> x.id) @scope.usStatusList = _.sortBy(project.us_statuses, "order") - @.generateStatusViewModes() - @scope.$emit("project:loaded", project) return project @@ -220,82 +260,40 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi @.fillUsersAndRoles(project.members, project.roles) @.initializeSubscription() @.loadKanban() - - - ## View Mode methods - - generateStatusViewModes: -> - storedStatusViewModes = @rs.kanban.getStatusViewModes(@scope.projectId) - - @scope.statusViewModes = {} - for status in @scope.usStatusList - mode = storedStatusViewModes[status.id] || defaultViewMode - - @scope.statusViewModes[status.id] = mode - - @.storeStatusViewModes() - - storeStatusViewModes: -> - @rs.kanban.storeStatusViewModes(@scope.projectId, @scope.statusViewModes) - - updateStatusViewMode: (statusId, newViewMode) -> - @scope.statusViewModes[statusId] = newViewMode - @.storeStatusViewModes() - - isMaximized: (statusId) -> - mode = @scope.statusViewModes[statusId] or defaultViewMode - return mode == 'maximized' - - isMinimized: (statusId) -> - mode = @scope.statusViewModes[statusId] or defaultViewMode - return mode == 'minimized' + @.generateFilters() # Utils methods prepareBulkUpdateData: (uses, field="kanban_order") -> return _.map(uses, (x) -> {"us_id": x.id, "order": x[field]}) - resortUserStories: (uses) -> - items = [] - for item, index in uses - item.kanban_order = index - if item.isModified() - items.push(item) - - return items - moveUs: (ctx, us, oldStatusId, newStatusId, index) -> - if oldStatusId != newStatusId - # Remove us from old status column - r = @scope.usByStatus[oldStatusId].indexOf(us) - @scope.usByStatus[oldStatusId].splice(r, 1) + us = @kanbanUserstoriesService.getUsModel(us.get('id')) - # Add us to new status column. - @scope.usByStatus[newStatusId].splice(index, 0, us) - us.status = newStatusId - else - r = @scope.usByStatus[newStatusId].indexOf(us) - @scope.usByStatus[newStatusId].splice(r, 1) - @scope.usByStatus[newStatusId].splice(index, 0, us) + moveUpdateData = @kanbanUserstoriesService.move(us.id, newStatusId, index) - itemsToSave = @.resortUserStories(@scope.usByStatus[newStatusId]) - @scope.usByStatus[newStatusId] = _.sortBy(@scope.usByStatus[newStatusId], "kanban_order") + params = { + include_attachments: true, + include_tasks: true + } - # Persist the userstory - promise = @repo.save(us) + options = { + headers: { + "set-orders": JSON.stringify(moveUpdateData.set_orders) + } + } - # Rehash userstories order field - # and persist in bulk all changes. - promise = promise.then => - itemsToSave = _.reject(itemsToSave, {"id": us.id}) - data = @.prepareBulkUpdateData(itemsToSave) + promise = @repo.save(us, true, params, options, true) - return @rs.userstories.bulkUpdateKanbanOrder(us.project, data).then => - return itemsToSave + promise = promise.then (result) => + headers = result[1] + + if headers && headers['taiga-info-order-updated'] + order = JSON.parse(headers['taiga-info-order-updated']) + @kanbanUserstoriesService.assignOrders(order) return promise - module.controller("KanbanController", KanbanController) ############################################################################# @@ -322,7 +320,7 @@ module.directive("tgKanban", ["$tgRepo", "$rootScope", KanbanDirective]) ## Kanban Archived Status Column Header Control ############################################################################# -KanbanArchivedStatusHeaderDirective = ($rootscope, $translate) -> +KanbanArchivedStatusHeaderDirective = ($rootscope, $translate, kanbanUserstoriesService) -> showArchivedText = $translate.instant("KANBAN.ACTION_SHOW_ARCHIVED") hideArchivedText = $translate.instant("KANBAN.ACTION_HIDE_ARCHIVED") @@ -330,6 +328,9 @@ KanbanArchivedStatusHeaderDirective = ($rootscope, $translate) -> status = $scope.$eval($attrs.tgKanbanArchivedStatusHeader) hidden = true + kanbanUserstoriesService.addArchivedStatus(status.id) + kanbanUserstoriesService.hideStatus(status.id) + $scope.class = "icon-watch" $scope.title = showArchivedText @@ -342,24 +343,27 @@ KanbanArchivedStatusHeaderDirective = ($rootscope, $translate) -> $scope.title = showArchivedText $rootscope.$broadcast("kanban:hide-userstories-for-status", status.id) + kanbanUserstoriesService.hideStatus(status.id) else $scope.class = "icon-unwatch" $scope.title = hideArchivedText $rootscope.$broadcast("kanban:show-userstories-for-status", status.id) + kanbanUserstoriesService.showStatus(status.id) + $scope.$on "$destroy", -> $el.off() return {link:link} -module.directive("tgKanbanArchivedStatusHeader", [ "$rootScope", "$translate", KanbanArchivedStatusHeaderDirective]) +module.directive("tgKanbanArchivedStatusHeader", [ "$rootScope", "$translate", "tgKanbanUserstories", KanbanArchivedStatusHeaderDirective]) ############################################################################# ## Kanban Archived Status Column Intro Directive ############################################################################# -KanbanArchivedStatusIntroDirective = ($translate) -> +KanbanArchivedStatusIntroDirective = ($translate, kanbanUserstoriesService) -> userStories = [] link = ($scope, $el, $attrs) -> @@ -367,105 +371,40 @@ KanbanArchivedStatusIntroDirective = ($translate) -> status = $scope.$eval($attrs.tgKanbanArchivedStatusIntro) $el.text(hiddenUserStoriexText) - updateIntroText = -> - if userStories.length > 0 + updateIntroText = (hasArchived) -> + if hasArchived $el.text("") else $el.text(hiddenUserStoriexText) $scope.$on "kanban:us:move", (ctx, itemUs, oldStatusId, newStatusId, itemIndex) -> - # The destination columnd is this one - if status.id == newStatusId - # Reorder - if status.id == oldStatusId - r = userStories.indexOf(itemUs) - userStories.splice(r, 1) - userStories.splice(itemIndex, 0, itemUs) - - # Archiving user story - else - itemUs.isArchived = true - userStories.splice(itemIndex, 0, itemUs) - - # Unarchiving user story - else if status.id == oldStatusId - itemUs.isArchived = false - r = userStories.indexOf(itemUs) - userStories.splice(r, 1) - - updateIntroText() + hasArchived = !!kanbanUserstoriesService.getStatus(newStatusId).length + updateIntroText(hasArchived) $scope.$on "kanban:shown-userstories-for-status", (ctx, statusId, userStoriesLoaded) -> if statusId == status.id - userStories = _.filter(userStoriesLoaded, (us) -> us.status == status.id) - updateIntroText() + kanbanUserstoriesService.deleteStatus(statusId) + kanbanUserstoriesService.add(userStoriesLoaded) + + hasArchived = !!kanbanUserstoriesService.getStatus(statusId).length + updateIntroText(hasArchived) $scope.$on "kanban:hidden-userstories-for-status", (ctx, statusId) -> if statusId == status.id - userStories = [] - updateIntroText() + updateIntroText(false) $scope.$on "$destroy", -> $el.off() return {link:link} -module.directive("tgKanbanArchivedStatusIntro", ["$translate", KanbanArchivedStatusIntroDirective]) - - -############################################################################# -## Kanban User Story Directive -############################################################################# - -KanbanUserstoryDirective = ($rootscope, $loading, $rs, $rs2) -> - link = ($scope, $el, $attrs, $model) -> - $scope.$watch "us", (us) -> - if us.is_blocked and not $el.hasClass("blocked") - $el.addClass("blocked") - else if not us.is_blocked and $el.hasClass("blocked") - $el.removeClass("blocked") - - $el.on 'click', '.edit-us', (event) -> - if $el.find(".icon-edit").hasClass("noclick") - return - - target = $(event.target) - - currentLoading = $loading() - .target(target) - .timeout(200) - .removeClasses("icon-edit") - .start() - - us = $model.$modelValue - $rs.userstories.getByRef(us.project, us.ref).then (editingUserStory) => - $rs2.attachments.list("us", us.id, us.project).then (attachments) => - $rootscope.$broadcast("usform:edit", editingUserStory, attachments.toJS()) - currentLoading.finish() - - $scope.getTemplateUrl = () -> - if $scope.us.isPlaceholder - return "common/components/kanban-placeholder.html" - else - return "kanban/kanban-task.html" - - $scope.$on "$destroy", -> - $el.off() - - return { - template: '', - link: link - require: "ngModel" - } - -module.directive("tgKanbanUserstory", ["$rootScope", "$tgLoading", "$tgResources", "tgResources", KanbanUserstoryDirective]) +module.directive("tgKanbanArchivedStatusIntro", ["$translate", "tgKanbanUserstories", KanbanArchivedStatusIntroDirective]) ############################################################################# ## Kanban Squish Column Directive ############################################################################# KanbanSquishColumnDirective = (rs) -> - link = ($scope, $el, $attrs) -> $scope.$on "project:loaded", (event, project) -> $scope.folds = rs.kanban.getStatusColumnModes(project.id) @@ -485,6 +424,7 @@ KanbanSquishColumnDirective = (rs) -> return 310 totalWidth = _.reduce columnWidths, (total, width) -> return total + width + $el.find('.kanban-table-inner').css("width", totalWidth) return {link: link} @@ -502,7 +442,7 @@ KanbanWipLimitDirective = -> redrawWipLimit = => $el.find(".kanban-wip-limit").remove() timeout 200, => - element = $el.find(".kanban-task")[status.wip_limit] + element = $el.find("tg-card")[status.wip_limit] if element angular.element(element).before("
") @@ -518,83 +458,3 @@ KanbanWipLimitDirective = -> return {link: link} module.directive("tgKanbanWipLimit", KanbanWipLimitDirective) - - -############################################################################# -## Kanban User Directive -############################################################################# - -KanbanUserDirective = ($log, $compile, $translate, avatarService) -> - template = _.template(""" -
- class="not-clickable"<% } %>> - <%- name %> - -
- """) - - clickable = false - - link = ($scope, $el, $attrs, $model) -> - username_label = $el.parent().find("a.task-assigned") - username_label.addClass("not-clickable") - - if not $attrs.tgKanbanUserAvatar - return $log.error "KanbanUserDirective: no attr is defined" - - wtid = $scope.$watch $attrs.tgKanbanUserAvatar, (v) -> - if not $scope.usersById? - $log.error "KanbanUserDirective requires userById set in scope." - wtid() - else - user = $scope.usersById[v] - render(user) - - render = (user) -> - avatar = avatarService.getAvatar(user) - - if user is undefined - ctx = { - name: $translate.instant("COMMON.ASSIGNED_TO.NOT_ASSIGNED"), - imgurl: avatar.url, - clickable: clickable, - bg: null - } - else - ctx = { - name: user.full_name_display, - imgurl: avatar.url, - bg: avatar.bg, - clickable: clickable - } - - html = $compile(template(ctx))($scope) - $el.html(html) - username_label.text(ctx.name) - - bindOnce $scope, "project", (project) -> - if project.my_permissions.indexOf("modify_us") > -1 - clickable = true - $el.on "click", (event) => - if $el.find("a").hasClass("noclick") - return - - us = $model.$modelValue - $ctrl = $el.controller() - $ctrl.changeUsAssignedTo(us) - - username_label.removeClass("not-clickable") - username_label.on "click", (event) -> - if $el.find("a").hasClass("noclick") - return - - us = $model.$modelValue - $ctrl = $el.controller() - $ctrl.changeUsAssignedTo(us) - - $scope.$on "$destroy", -> - $el.off() - - return {link: link, require:"ngModel"} - -module.directive("tgKanbanUserAvatar", ["$log", "$compile", "$translate", "tgAvatarService", KanbanUserDirective]) diff --git a/app/coffee/modules/kanban/sortable.coffee b/app/coffee/modules/kanban/sortable.coffee index bcdd55d6..0ae86a41 100644 --- a/app/coffee/modules/kanban/sortable.coffee +++ b/app/coffee/modules/kanban/sortable.coffee @@ -40,8 +40,12 @@ module = angular.module("taigaKanban") KanbanSortableDirective = ($repo, $rs, $rootscope) -> link = ($scope, $el, $attrs) -> - bindOnce $scope, "project", (project) -> - if not (project.my_permissions.indexOf("modify_us") > -1) + unwatch = $scope.$watch "usByStatus", (usByStatus) -> + return if !usByStatus || !usByStatus.size + + unwatch() + + if not ($scope.project.my_permissions.indexOf("modify_us") > -1) return oldParentScope = null @@ -63,7 +67,7 @@ KanbanSortableDirective = ($repo, $rs, $rootscope) -> copy: false, mirrorContainer: tdom[0], moves: (item) -> - return $(item).hasClass('kanban-task') + return $(item).is('tg-card') }) drake.on 'drag', (item) -> @@ -83,7 +87,7 @@ KanbanSortableDirective = ($repo, $rs, $rootscope) -> deleteElement(itemEl) $scope.$apply -> - $rootscope.$broadcast("kanban:us:move", itemUs, itemUs.status, newStatusId, itemIndex) + $rootscope.$broadcast("kanban:us:move", itemUs, itemUs.getIn(['model', 'status']), newStatusId, itemIndex) scroll = autoScroll(containers, { margin: 100, diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 8d30fb0f..59e8743a 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -96,7 +96,8 @@ urls = { "userstories": "/userstories" "bulk-create-us": "/userstories/bulk_create" "bulk-update-us-backlog-order": "/userstories/bulk_update_backlog_order" - "bulk-update-us-sprint-order": "/userstories/bulk_update_sprint_order" + "bulk-update-us-milestone": "/userstories/bulk_update_milestone" + "bulk-update-us-miles-order": "/userstories/bulk_update_sprint_order" "bulk-update-us-kanban-order": "/userstories/bulk_update_kanban_order" "userstories-filters": "/userstories/filters_data" "userstory-upvote": "/userstories/%s/upvote" @@ -112,6 +113,7 @@ urls = { "task-downvote": "/tasks/%s/downvote" "task-watch": "/tasks/%s/watch" "task-unwatch": "/tasks/%s/unwatch" + "task-filters": "/tasks/filters_data" # Issues "issues": "/issues" diff --git a/app/coffee/modules/resources/issues.coffee b/app/coffee/modules/resources/issues.coffee index 1568b035..60ea24b6 100644 --- a/app/coffee/modules/resources/issues.coffee +++ b/app/coffee/modules/resources/issues.coffee @@ -30,8 +30,6 @@ generateHash = taiga.generateHash resourceProvider = ($repo, $http, $urls, $storage, $q) -> service = {} hashSuffix = "issues-queryparams" - filtersHashSuffix = "issues-filters" - myFiltersHashSuffix = "issues-my-filters" service.get = (projectId, issueId) -> params = service.getQueryParams(projectId) @@ -95,53 +93,6 @@ resourceProvider = ($repo, $http, $urls, $storage, $q) -> hash = generateHash([projectId, ns]) return $storage.get(hash) or {} - service.storeFilters = (projectSlug, params) -> - ns = "#{projectSlug}:#{filtersHashSuffix}" - hash = generateHash([projectSlug, ns]) - $storage.set(hash, params) - - service.getFilters = (projectSlug) -> - ns = "#{projectSlug}:#{filtersHashSuffix}" - hash = generateHash([projectSlug, ns]) - return $storage.get(hash) or {} - - service.storeMyFilters = (projectId, myFilters) -> - deferred = $q.defer() - url = $urls.resolve("user-storage") - ns = "#{projectId}:#{myFiltersHashSuffix}" - hash = generateHash([projectId, ns]) - if _.isEmpty(myFilters) - promise = $http.delete("#{url}/#{hash}", {key: hash, value:myFilters}) - promise.then -> - deferred.resolve() - promise.then null, -> - deferred.reject() - else - promise = $http.put("#{url}/#{hash}", {key: hash, value:myFilters}) - promise.then (data) -> - deferred.resolve() - promise.then null, (data) -> - innerPromise = $http.post("#{url}", {key: hash, value:myFilters}) - innerPromise.then -> - deferred.resolve() - innerPromise.then null, -> - deferred.reject() - return deferred.promise - - service.getMyFilters = (projectId) -> - deferred = $q.defer() - url = $urls.resolve("user-storage") - ns = "#{projectId}:#{myFiltersHashSuffix}" - hash = generateHash([projectId, ns]) - - promise = $http.get("#{url}/#{hash}") - promise.then (data) -> - deferred.resolve(data.data.value) - promise.then null, (data) -> - deferred.resolve({}) - - return deferred.promise - return (instance) -> instance.issues = service diff --git a/app/coffee/modules/resources/kanban.coffee b/app/coffee/modules/resources/kanban.coffee index a79bee06..48bd2074 100644 --- a/app/coffee/modules/resources/kanban.coffee +++ b/app/coffee/modules/resources/kanban.coffee @@ -32,16 +32,6 @@ resourceProvider = ($storage) -> hashSuffixStatusViewModes = "kanban-statusviewmodels" hashSuffixStatusColumnModes = "kanban-statuscolumnmodels" - service.storeStatusViewModes = (projectId, params) -> - ns = "#{projectId}:#{hashSuffixStatusViewModes}" - hash = generateHash([projectId, ns]) - $storage.set(hash, params) - - service.getStatusViewModes = (projectId) -> - ns = "#{projectId}:#{hashSuffixStatusViewModes}" - hash = generateHash([projectId, ns]) - return $storage.get(hash) or {} - service.storeStatusColumnModes = (projectId, params) -> ns = "#{projectId}:#{hashSuffixStatusColumnModes}" hash = generateHash([projectId, ns]) diff --git a/app/coffee/modules/resources/tasks.coffee b/app/coffee/modules/resources/tasks.coffee index 6a838a5d..ba27fec7 100644 --- a/app/coffee/modules/resources/tasks.coffee +++ b/app/coffee/modules/resources/tasks.coffee @@ -38,17 +38,23 @@ resourceProvider = ($repo, $http, $urls, $storage) -> params.project = projectId return $repo.queryOne("tasks", taskId, params) - service.getByRef = (projectId, ref) -> + service.getByRef = (projectId, ref, extraParams) -> params = service.getQueryParams(projectId) params.project = projectId params.ref = ref + + params = _.extend({}, params, extraParams) + 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} + service.filtersData = (params) -> + return $repo.queryOneRaw("task-filters", null, params) + + service.list = (projectId, sprintId=null, userStoryId=null, params) -> + params = _.merge(params, {project: projectId}) params.milestone = sprintId if sprintId params.user_story = userStoryId if userStoryId service.storeQueryParams(projectId, params) diff --git a/app/coffee/modules/resources/userstories.coffee b/app/coffee/modules/resources/userstories.coffee index 935f6d3a..f4c5cca4 100644 --- a/app/coffee/modules/resources/userstories.coffee +++ b/app/coffee/modules/resources/userstories.coffee @@ -26,7 +26,7 @@ taiga = @.taiga generateHash = taiga.generateHash -resourceProvider = ($repo, $http, $urls, $storage) -> +resourceProvider = ($repo, $http, $urls, $storage, $q) -> service = {} hashSuffix = "userstories-queryparams" @@ -35,10 +35,12 @@ resourceProvider = ($repo, $http, $urls, $storage) -> params.project = projectId return $repo.queryOne("userstories", usId, params) - service.getByRef = (projectId, ref) -> + service.getByRef = (projectId, ref, extraParams = {}) -> params = service.getQueryParams(projectId) params.project = projectId params.ref = ref + params = _.extend({}, params, extraParams) + return $repo.queryOne("userstories", "by_ref", params) service.listInAllProjects = (filters) -> @@ -96,9 +98,9 @@ resourceProvider = ($repo, $http, $urls, $storage) -> params = {project_id: projectId, bulk_stories: data} return $http.post(url, params) - service.bulkUpdateSprintOrder = (projectId, data) -> - url = $urls.resolve("bulk-update-us-sprint-order") - params = {project_id: projectId, bulk_stories: data} + service.bulkUpdateMilestone = (projectId, milestoneId, data) -> + url = $urls.resolve("bulk-update-us-milestone") + params = {project_id: projectId, milestone_id: milestoneId, bulk_stories: data} return $http.post(url, params) service.bulkUpdateKanbanOrder = (projectId, data) -> @@ -133,4 +135,4 @@ resourceProvider = ($repo, $http, $urls, $storage) -> instance.userstories = service module = angular.module("taigaResources") -module.factory("$tgUserstoriesResourcesProvider", ["$tgRepo", "$tgHttp", "$tgUrls", "$tgStorage", resourceProvider]) +module.factory("$tgUserstoriesResourcesProvider", ["$tgRepo", "$tgHttp", "$tgUrls", "$tgStorage", "$q", resourceProvider]) diff --git a/app/coffee/modules/taskboard/lightboxes.coffee b/app/coffee/modules/taskboard/lightboxes.coffee index 756d84e8..ad052026 100644 --- a/app/coffee/modules/taskboard/lightboxes.coffee +++ b/app/coffee/modules/taskboard/lightboxes.coffee @@ -108,6 +108,11 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer if not form.validate() return + params = { + include_attachments: true, + include_tasks: true + } + if $scope.isNew promise = $repo.create("tasks", $scope.task) broadcastEvent = "taskform:new:success" @@ -116,20 +121,22 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer broadcastEvent = "taskform:edit:success" promise.then (data) -> - createAttachments(data) deleteAttachments(data) + .then () => createAttachments(data) + .then () => + currentLoading.finish() + lightboxService.close($el) - return data + $rs.tasks.getByRef(data.project, data.ref, params).then (task) -> + $rootscope.$broadcast(broadcastEvent, task) currentLoading = $loading() .target(submitButton) .start() - # FIXME: error handling? promise.then (data) -> currentLoading.finish() lightboxService.close($el) - $rootscope.$broadcast(broadcastEvent, data) $el.on "submit", "form", submit @@ -139,7 +146,7 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer return {link: link} -CreateBulkTasksDirective = ($repo, $rs, $rootscope, $loading, lightboxService) -> +CreateBulkTasksDirective = ($repo, $rs, $rootscope, $loading, lightboxService, $model) -> link = ($scope, $el, attrs) -> $scope.form = {data: "", usId: null} @@ -161,6 +168,7 @@ CreateBulkTasksDirective = ($repo, $rs, $rootscope, $loading, lightboxService) - promise = $rs.tasks.bulkCreate(projectId, sprintId, usId, data) promise.then (result) -> + result = _.map(result, (x) => $model.make_model('userstories', x)) currentLoading.finish() $rootscope.$broadcast("taskform:bulk:success", result) lightboxService.close($el) @@ -205,5 +213,6 @@ module.directive("tgLbCreateBulkTasks", [ "$rootScope", "$tgLoading", "lightboxService", + "$tgModel", CreateBulkTasksDirective ]) diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee index 15b3b753..b66d6957 100644 --- a/app/coffee/modules/taskboard/main.coffee +++ b/app/coffee/modules/taskboard/main.coffee @@ -38,13 +38,14 @@ module = angular.module("taigaTaskboard") ## Taskboard Controller ############################################################################# -class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) +class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.FiltersMixin) @.$inject = [ "$scope", "$rootScope", "$tgRepo", "$tgConfirm", "$tgResources", + "tgResources" "$routeParams", "$q", "tgAppMetaService", @@ -53,12 +54,20 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) "$tgEvents" "$tgAnalytics", "$translate", - "tgErrorHandlingService" + "tgErrorHandlingService", + "tgTaskboardTasks", + "$tgStorage", + "tgFilterRemoteStorageService" ] - constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appMetaService, @location, @navUrls, - @events, @analytics, @translate, @errorHandlingService) -> + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @rs2, @params, @q, @appMetaService, @location, @navUrls, + @events, @analytics, @translate, @errorHandlingService, @taskboardTasksService, @storage, @filterRemoteStorageService) -> bindMethods(@) + @taskboardTasksService.reset() + @scope.userstories = [] + @.openFilter = false + + return if @.applyStoredFilters(@params.pslug, "tasks-filters") @scope.sectionName = @translate.instant("TASKBOARD.SECTION_NAME") @.initializeEventHandlers() @@ -70,6 +79,150 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) # On Error promise.then null, @.onInitialDataError.bind(@) + taiga.defineImmutableProperty @.scope, "usTasks", () => + return @taskboardTasksService.usTasks + + setZoom: (zoomLevel, zoom) -> + if @.zoomLevel != zoomLevel + @taskboardTasksService.resetFolds() + + @.zoomLevel = zoomLevel + @.zoom = zoom + + if @.zoomLevel == '0' + @rootscope.$broadcast("sprint:zoom0") + + changeQ: (q) -> + @.replaceFilter("q", q) + @.loadTasks() + @.generateFilters() + + removeFilter: (filter) -> + @.unselectFilter(filter.dataType, filter.id) + @.loadTasks() + @.generateFilters() + + addFilter: (newFilter) -> + @.selectFilter(newFilter.category.dataType, newFilter.filter.id) + @.loadTasks() + @.generateFilters() + + selectCustomFilter: (customFilter) -> + @.replaceAllFilters(customFilter.filter) + @.loadTasks() + @.generateFilters() + + removeCustomFilter: (customFilter) -> + @filterRemoteStorageService.getFilters(@scope.projectId, 'tasks-custom-filters').then (userFilters) => + delete userFilters[customFilter.id] + + @filterRemoteStorageService.storeFilters(@scope.projectId, userFilters, 'tasks-custom-filters').then(@.generateFilters) + + saveCustomFilter: (name) -> + filters = {} + urlfilters = @location.search() + filters.tags = urlfilters.tags + filters.status = urlfilters.status + filters.assigned_to = urlfilters.assigned_to + filters.owner = urlfilters.owner + + @filterRemoteStorageService.getFilters(@scope.projectId, 'tasks-custom-filters').then (userFilters) => + userFilters[name] = filters + + @filterRemoteStorageService.storeFilters(@scope.projectId, userFilters, 'tasks-custom-filters').then(@.generateFilters) + + generateFilters: -> + @.storeFilters(@params.pslug, @location.search(), "tasks-filters") + + urlfilters = @location.search() + + loadFilters = {} + loadFilters.project = @scope.projectId + loadFilters.milestone = @scope.sprintId + loadFilters.tags = urlfilters.tags + loadFilters.status = urlfilters.status + loadFilters.assigned_to = urlfilters.assigned_to + loadFilters.owner = urlfilters.owner + loadFilters.q = urlfilters.q + + return @q.all([ + @rs.tasks.filtersData(loadFilters), + @filterRemoteStorageService.getFilters(@scope.projectId, 'tasks-custom-filters') + ]).then (result) => + data = result[0] + customFiltersRaw = result[1] + + statuses = _.map data.statuses, (it) -> + it.id = it.id.toString() + + return it + tags = _.map data.tags, (it) -> + it.id = it.name + + return it + assignedTo = _.map data.assigned_to, (it) -> + if it.id + it.id = it.id.toString() + else + it.id = "null" + + it.name = it.full_name || "Unassigned" + + return it + owner = _.map data.owners, (it) -> + it.id = it.id.toString() + it.name = it.full_name + + return it + + @.selectedFilters = [] + + if loadFilters.status + selected = @.formatSelectedFilters("status", statuses, loadFilters.status) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.tags + selected = @.formatSelectedFilters("tags", tags, loadFilters.tags) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.assigned_to + selected = @.formatSelectedFilters("assigned_to", assignedTo, loadFilters.assigned_to) + @.selectedFilters = @.selectedFilters.concat(selected) + + if loadFilters.owner + selected = @.formatSelectedFilters("owner", owner, loadFilters.owner) + @.selectedFilters = @.selectedFilters.concat(selected) + + @.filterQ = loadFilters.q + + @.filters = [ + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.STATUS"), + dataType: "status", + content: statuses + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.TAGS"), + dataType: "tags", + content: tags, + hideEmpty: true + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.ASSIGNED_TO"), + dataType: "assigned_to", + content: assignedTo + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.CREATED_BY"), + dataType: "owner", + content: owner + } + ]; + + @.customFilters = [] + _.forOwn customFiltersRaw, (value, key) => + @.customFilters.push({id: key, name: key, filter: value}) + _setMeta: -> prettyDate = @translate.instant("BACKLOG.SPRINTS.DATE") @@ -92,24 +245,33 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) @appMetaService.setAll(title, description) initializeEventHandlers: -> - # TODO: Reload entire taskboard after create/edit tasks seems - # a big overhead. It should be optimized in near future. - @scope.$on "taskform:bulk:success", => - @.loadTaskboard() + @scope.$on "taskform:bulk:success", (event, tasks) => + @.refreshTagsColors().then () => + @taskboardTasksService.add(tasks) + @analytics.trackEvent("task", "create", "bulk create task on taskboard", 1) - @scope.$on "taskform:new:success", => - @.loadTaskboard() + @scope.$on "taskform:new:success", (event, task) => + @.refreshTagsColors().then () => + @taskboardTasksService.add(task) + @analytics.trackEvent("task", "create", "create task on taskboard", 1) - @scope.$on("taskform:edit:success", => @.loadTaskboard()) - @scope.$on("taskboard:task:move", @.taskMove) + @scope.$on "taskform:edit:success", (event, task) => + @.refreshTagsColors().then () => + @taskboardTasksService.replaceModel(task) - @scope.$on "assigned-to:added", (ctx, userId, task) => - task.assigned_to = userId - promise = @repo.save(task) - promise.then null, -> - console.log "FAIL" # TODO + @scope.$on("taskboard:task:move", @.taskMove) + @scope.$on("assigned-to:added", @.onAssignedToChanged) + + onAssignedToChanged: (ctx, userid, taskModel) -> + taskModel.assigned_to = userid + + @taskboardTasksService.replaceModel(taskModel) + + promise = @repo.save(taskModel) + promise.then null, -> + console.log "FAIL" # TODO initializeSubscription: -> routingKey = "changes.project.#{@scope.projectId}.tasks" @@ -130,7 +292,6 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) @scope.project = project # Not used at this momment @scope.pointsList = _.sortBy(project.points, "order") - # @scope.roleList = _.sortBy(project.roles, "order") @scope.pointsById = groupBy(project.points, (e) -> e.id) @scope.roleById = groupBy(project.roles, (e) -> e.id) @scope.taskStatusList = _.sortBy(project.task_statuses, "order") @@ -170,34 +331,22 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) return @rs.sprints.get(@scope.projectId, @scope.sprintId).then (sprint) => @scope.sprint = sprint @scope.userstories = _.sortBy(sprint.user_stories, "sprint_order") + + @taskboardTasksService.setUserstories(@scope.userstories) + return sprint loadTasks: -> - return @rs.tasks.list(@scope.projectId, @scope.sprintId).then (tasks) => - @scope.tasks = _.sortBy(tasks, 'taskboard_order') - @scope.usTasks = {} + params = { + include_attachments: true, + include_tasks: true + } - # Iterate over all userstories and - # null userstory for unassigned tasks - for us in _.union(@scope.userstories, [{id:null}]) - @scope.usTasks[us.id] = {} - for status in @scope.taskStatusList - @scope.usTasks[us.id][status.id] = [] + params = _.merge params, @location.search() - for task in @scope.tasks - if @scope.usTasks[task.user_story]? and @scope.usTasks[task.user_story][task.status]? - @scope.usTasks[task.user_story][task.status].push(task) - - if tasks.length == 0 - - if @scope.userstories.length > 0 - usId = @scope.userstories[0].id - else - usId = null - - @scope.usTasks[usId][@scope.taskStatusList[0].id].push({isPlaceholder: true}) - - return tasks + return @rs.tasks.list(@scope.projectId, @scope.sprintId, null, params).then (tasks) => + @taskboardTasksService.init(@scope.project, @scope.usersById) + @taskboardTasksService.set(tasks) loadTaskboard: -> return @q.all([ @@ -219,59 +368,69 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin) return data return promise.then(=> @.loadProject()) - .then(=> @.loadTaskboard()) - .then(=> @.setRolePoints()) + .then => + @.generateFilters() - refreshTasksOrder: (tasks) -> - items = @.resortTasks(tasks) - data = @.prepareBulkUpdateData(items) + return @.loadTaskboard().then(=> @.setRolePoints()) - return @rs.tasks.bulkUpdateTaskTaskboardOrder(@scope.project.id, data) + showPlaceHolder: (statusId, usId) -> + if !@taskboardTasksService.tasksRaw.length + if @scope.taskStatusList[0].id == statusId && + (!@scope.userstories.length || @scope.userstories[0].id == usId) + return true - resortTasks: (tasks) -> - items = [] + return false - for item, index in tasks - item["taskboard_order"] = index - if item.isModified() - items.push(item) + editTask: (id) -> + task = @.taskboardTasksService.getTask(id) - return items + task = task.set('loading', true) + @taskboardTasksService.replace(task) - prepareBulkUpdateData: (uses) -> - return _.map(uses, (x) -> {"task_id": x.id, "order": x["taskboard_order"]}) + @rs.tasks.getByRef(task.getIn(['model', 'project']), task.getIn(['model', 'ref'])).then (editingTask) => + @rs2.attachments.list("task", task.get('id'), task.getIn(['model', 'project'])).then (attachments) => + @rootscope.$broadcast("taskform:edit", editingTask, attachments.toJS()) + task = task.set('loading', false) + @taskboardTasksService.replace(task) - taskMove: (ctx, task, usId, statusId, order) -> - # Remove task from old position - r = @scope.usTasks[task.user_story][task.status].indexOf(task) - @scope.usTasks[task.user_story][task.status].splice(r, 1) + taskMove: (ctx, task, oldStatusId, usId, statusId, order) -> + task = @taskboardTasksService.getTaskModel(task.get('id')) - # Add task to new position - tasks = @scope.usTasks[usId][statusId] - tasks.splice(order, 0, task) + moveUpdateData = @taskboardTasksService.move(task.id, usId, statusId, order) - task.user_story = usId - task.status = statusId - task.taskboard_order = order + params = { + status__is_archived: false, + include_attachments: true, + include_tasks: true + } - promise = @repo.save(task) + options = { + headers: { + "set-orders": JSON.stringify(moveUpdateData.set_orders) + } + } - @rootscope.$broadcast("sprint:task:moved", task) + promise = @repo.save(task, true, params, options, true).then (result) => + headers = result[1] + + if headers && headers['taiga-info-order-updated'] + order = JSON.parse(headers['taiga-info-order-updated']) + @taskboardTasksService.assignOrders(order) - promise.then => - @.refreshTasksOrder(tasks) @.loadSprintStats() - promise.then null, => - console.log "FAIL TASK SAVE" - ## Template actions addNewTask: (type, us) -> switch type when "standard" then @rootscope.$broadcast("taskform:new", @scope.sprintId, us?.id) when "bulk" then @rootscope.$broadcast("taskform:bulk", @scope.sprintId, us?.id) - editTaskAssignedTo: (task) -> + toggleFold: (id) -> + @taskboardTasksService.toggleFold(id) + + changeTaskAssignedTo: (id) -> + task = @taskboardTasksService.getTaskModel(id) + @rootscope.$broadcast("assigned-to:add", task) setRolePoints: () -> @@ -331,43 +490,6 @@ TaskboardDirective = ($rootscope) -> module.directive("tgTaskboard", ["$rootScope", TaskboardDirective]) - -############################################################################# -## Taskboard Task Directive -############################################################################# - -TaskboardTaskDirective = ($rootscope, $loading, $rs, $rs2) -> - link = ($scope, $el, $attrs, $model) -> - $scope.$watch "task", (task) -> - if task.is_blocked and not $el.hasClass("blocked") - $el.addClass("blocked") - else if not task.is_blocked and $el.hasClass("blocked") - $el.removeClass("blocked") - - $el.find(".edit-task").on "click", (event) -> - if $el.find('.edit-task').hasClass('noclick') - return - - $scope.$apply -> - target = $(event.target) - - currentLoading = $loading() - .target(target) - .timeout(200) - .start() - - task = $scope.task - - $rs.tasks.getByRef(task.project, task.ref).then (editingTask) => - $rs2.attachments.list("task", editingTask.id, editingTask.project).then (attachments) => - $rootscope.$broadcast("taskform:edit", editingTask, attachments.toJS()) - currentLoading.finish() - - return {link:link} - - -module.directive("tgTaskboardTask", ["$rootScope", "$tgLoading", "$tgResources", "tgResources", TaskboardTaskDirective]) - ############################################################################# ## Taskboard Squish Column Directive ############################################################################# @@ -377,14 +499,18 @@ TaskboardSquishColumnDirective = (rs) -> maxColumnWidth = 300 link = ($scope, $el, $attrs) -> + $scope.$on "sprint:zoom0", () => + recalculateTaskboardWidth() + $scope.$on "sprint:task:moved", () => recalculateTaskboardWidth() - bindOnce $scope, "usTasks", (project) -> - $scope.statusesFolded = rs.tasks.getStatusColumnModes($scope.project.id) - $scope.usFolded = rs.tasks.getUsRowModes($scope.project.id, $scope.sprintId) + $scope.$watch "usTasks", () -> + if $scope.project + $scope.statusesFolded = rs.tasks.getStatusColumnModes($scope.project.id) + $scope.usFolded = rs.tasks.getUsRowModes($scope.project.id, $scope.sprintId) - recalculateTaskboardWidth() + recalculateTaskboardWidth() $scope.foldStatus = (status) -> $scope.statusesFolded[status.id] = !!!$scope.statusesFolded[status.id] @@ -403,7 +529,10 @@ TaskboardSquishColumnDirective = (rs) -> recalculateTaskboardWidth() getCeilWidth = (usId, statusId) => - tasks = $scope.usTasks[usId][statusId].length + if usId + tasks = $scope.usTasks.getIn([usId.toString(), statusId.toString()]).size + else + tasks = $scope.usTasks.getIn(['null', statusId.toString()]).size if $scope.statusesFolded[statusId] if tasks and $scope.usFolded[usId] @@ -422,7 +551,10 @@ TaskboardSquishColumnDirective = (rs) -> if width column.css('max-width', width) else - column.css("max-width", maxColumnWidth) + if $scope.ctrl.zoomLevel == '0' + column.css("max-width", 148) + else + column.css("max-width", maxColumnWidth) refreshTaskboardTableWidth = () => columnWidths = [] @@ -458,67 +590,3 @@ TaskboardSquishColumnDirective = (rs) -> return {link: link} module.directive("tgTaskboardSquishColumn", ["$tgResources", TaskboardSquishColumnDirective]) - -############################################################################# -## Taskboard User Directive -############################################################################# - -TaskboardUserDirective = ($log, $translate, avatarService) -> - clickable = false - - link = ($scope, $el, $attrs) -> - username_label = $el.parent().find("a.task-assigned") - username_label.addClass("not-clickable") - - $scope.$watch 'task.assigned_to', (assigned_to) -> - user = $scope.usersById[assigned_to] - - avatar = avatarService.getAvatar(user) - - if user is undefined - _.assign($scope, { - name: $translate.instant("COMMON.ASSIGNED_TO.NOT_ASSIGNED"), - avatar: avatar, - clickable: clickable - }) - else - _.assign($scope, { - name: user.full_name_display, - avatar: avatar, - clickable: clickable - }) - - username_label.text($scope.name) - - - bindOnce $scope, "project", (project) -> - if project.my_permissions.indexOf("modify_task") > -1 - clickable = true - $el.find(".avatar-assigned-to").on "click", (event) => - if $el.find('a').hasClass('noclick') - return - - $ctrl = $el.controller() - $ctrl.editTaskAssignedTo($scope.task) - - username_label.removeClass("not-clickable") - username_label.on "click", (event) -> - if $el.find('a').hasClass('noclick') - return - - $ctrl = $el.controller() - $ctrl.editTaskAssignedTo($scope.task) - - - return { - link: link, - templateUrl: "taskboard/taskboard-user.html", - scope: { - "usersById": "=users", - "project": "=", - "task": "=", - } - } - - -module.directive("tgTaskboardUserAvatar", ["$log", "$translate", "tgAvatarService", TaskboardUserDirective]) diff --git a/app/coffee/modules/taskboard/sortable.coffee b/app/coffee/modules/taskboard/sortable.coffee index a5698cc8..b0c7bc4e 100644 --- a/app/coffee/modules/taskboard/sortable.coffee +++ b/app/coffee/modules/taskboard/sortable.coffee @@ -37,11 +37,14 @@ module = angular.module("taigaBacklog") ## Sortable Directive ############################################################################# -TaskboardSortableDirective = ($repo, $rs, $rootscope) -> +TaskboardSortableDirective = ($repo, $rs, $rootscope, $translate) -> link = ($scope, $el, $attrs) -> - bindOnce $scope, "tasks", (xx) -> - # If the user has not enough permissions we don't enable the sortable - if not ($scope.project.my_permissions.indexOf("modify_us") > -1) + unwatch = $scope.$watch "usTasks", (usTasks) -> + return if !usTasks || !usTasks.size + + unwatch() + + if not ($scope.project.my_permissions.indexOf("modify_task") > -1) return oldParentScope = null @@ -49,6 +52,10 @@ TaskboardSortableDirective = ($repo, $rs, $rootscope) -> itemEl = null tdom = $el + filterError = -> + text = $translate.instant("BACKLOG.SORTABLE_FILTER_ERROR") + $tgConfirm.notify("error", text) + deleteElement = (itemEl) -> # Completelly remove item and its scope from dom itemEl.scope().$destroy() @@ -63,12 +70,22 @@ TaskboardSortableDirective = ($repo, $rs, $rootscope) -> copy: false, mirrorContainer: $el[0], accepts: (el, target) -> return !$(target).hasClass('taskboard-userstory-box') - moves: (item) -> return $(item).hasClass('taskboard-task') + moves: (item) -> + return $(item).is('tg-card') }) drake.on 'drag', (item) -> oldParentScope = $(item).parent().scope() + if $el.hasClass("active-filters") + filterError() + + setTimeout (() -> + drake.cancel(true) + ), 0 + + return false + drake.on 'dragend', (item) -> parentEl = $(item).parent() itemEl = $(item) @@ -85,7 +102,7 @@ TaskboardSortableDirective = ($repo, $rs, $rootscope) -> deleteElement(itemEl) $scope.$apply -> - $rootscope.$broadcast("taskboard:task:move", itemTask, newUsId, newStatusId, itemIndex) + $rootscope.$broadcast("taskboard:task:move", itemTask, itemTask.getIn(['model', 'status']), newUsId, newStatusId, itemIndex) scroll = autoScroll([$('.taskboard-table-body')[0]], { @@ -107,5 +124,6 @@ module.directive("tgTaskboardSortable", [ "$tgRepo", "$tgResources", "$rootScope", + "$translate", TaskboardSortableDirective ]) diff --git a/app/coffee/modules/taskboard/taskboard-tasks.coffee b/app/coffee/modules/taskboard/taskboard-tasks.coffee new file mode 100644 index 00000000..cc8f087d --- /dev/null +++ b/app/coffee/modules/taskboard/taskboard-tasks.coffee @@ -0,0 +1,173 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: home.service.coffee +### + +groupBy = @.taiga.groupBy + +class TaskboardTasksService extends taiga.Service + @.$inject = [] + constructor: () -> + @.reset() + + reset: () -> + @.tasksRaw = [] + @.foldStatusChanged = {} + @.usTasks = Immutable.Map() + + init: (project, usersById) -> + @.project = project + @.usersById = usersById + + resetFolds: () -> + @.foldStatusChanged = {} + @.refresh() + + toggleFold: (taskId) -> + @.foldStatusChanged[taskId] = !@.foldStatusChanged[taskId] + @.refresh() + + add: (task) -> + @.tasksRaw = @.tasksRaw.concat(task) + @.refresh() + + set: (tasks) -> + @.tasksRaw = tasks + @.refreshRawOrder() + @.refresh() + + setUserstories: (userstories) -> + @.userstories = userstories + + refreshRawOrder: () -> + @.order = {} + + @.order[task.id] = task.taskboard_order for task in @.tasksRaw + + assignOrders: (order) -> + order = _.invert(order) + @.order = _.assign(@.order, order) + + @.refresh() + + getTask: (id) -> + findedTask = null + + @.usTasks.forEach (us) -> + us.forEach (status) -> + findedTask = status.find (task) -> return task.get('id') == id + + return false if findedTask + + return false if findedTask + + return findedTask + + replace: (task) -> + @.usTasks = @.usTasks.map (us) -> + return us.map (status) -> + findedIndex = status.findIndex (usItem) -> + return usItem.get('id') == us.get('id') + + if findedIndex != -1 + status = status.set(findedIndex, task) + + return status + + getTaskModel: (id) -> + return _.find @.tasksRaw, (task) -> return task.id == id + + replaceModel: (task) -> + @.tasksRaw = _.map @.tasksRaw, (it) -> + if task.id == it.id + return task + else + return it + + @.refresh() + + move: (id, usId, statusId, index) -> + task = @.getTaskModel(id) + + taskByUsStatus = _.filter @.tasksRaw, (task) => + return task.status == statusId && task.user_story == usId + + taskByUsStatus = _.sortBy taskByUsStatus, (it) => @.order[it.id] + + taksWithoutMoved = _.filter taskByUsStatus, (it) => it.id != id + beforeDestination = _.slice(taksWithoutMoved, 0, index) + afterDestination = _.slice(taksWithoutMoved, index) + + setOrders = {} + + previous = beforeDestination[beforeDestination.length - 1] + + previousWithTheSameOrder = _.filter beforeDestination, (it) => + @.order[it.id] == @.order[previous.id] + + if previousWithTheSameOrder.length > 1 + for it in previousWithTheSameOrder + setOrders[it.id] = @.order[it.id] + + if !previous + @.order[task.id] = 0 + else if previous + @.order[task.id] = @.order[previous.id] + 1 + + for it, key in afterDestination + @.order[it.id] = @.order[task.id] + key + 1 + + task.status = statusId + task.user_story = usId + task.taskboard_order = @.order[task.id] + + @.refresh() + + return {"task_id": task.id, "order": @.order[task.id], "set_orders": setOrders} + + refresh: -> + @.tasksRaw = _.sortBy @.tasksRaw, (it) => @.order[it.id] + + tasks = @.tasksRaw + taskStatusList = _.sortBy(@.project.task_statuses, "order") + + usTasks = {} + + # Iterate over all userstories and + # null userstory for unassigned tasks + for us in _.union(@.userstories, [{id:null}]) + usTasks[us.id] = {} + for status in taskStatusList + usTasks[us.id][status.id] = [] + + for taskModel in tasks + if usTasks[taskModel.user_story]? and usTasks[taskModel.user_story][taskModel.status]? + task = {} + task.foldStatusChanged = @.foldStatusChanged[taskModel.id] + task.model = taskModel.getAttrs() + task.images = _.filter taskModel.attachments, (it) -> return !!it.thumbnail_card_url + task.id = taskModel.id + task.assigned_to = @.usersById[taskModel.assigned_to] + task.colorized_tags = _.map task.model.tags, (tag) => + color = @.project.tags_colors[tag] + return {name: tag, color: color} + + usTasks[taskModel.user_story][taskModel.status].push(task) + + @.usTasks = Immutable.fromJS(usTasks) + +angular.module("taigaKanban").service("tgTaskboardTasks", TaskboardTasksService) diff --git a/app/coffee/utils.coffee b/app/coffee/utils.coffee index fc6d1757..d69a91c3 100644 --- a/app/coffee/utils.coffee +++ b/app/coffee/utils.coffee @@ -38,7 +38,7 @@ bindMethods = (object) => methods = [] _.forIn object, (value, key) => - if key not in dependencies + if key not in dependencies && _.isFunction(value) methods.push(key) _.bindAll(object, methods) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 41abba09..dd3166f4 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -45,6 +45,10 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "This value seems to be invalid.", "TYPE_EMAIL": "This value should be a valid email.", @@ -196,9 +200,25 @@ "TITLE": "filters", "INPUT_PLACEHOLDER": "Subject or reference", "TITLE_ACTION_FILTER_BUTTON": "search", - "BREADCRUMB_TITLE": "back to categories", - "BREADCRUMB_FILTERS": "Filters", - "BREADCRUMB_STATUS": "status" + "TITLE": "Filters", + "INPUT_SEARCH_PLACEHOLDER": "Subject or ref", + "TITLE_ACTION_SEARCH": "Search", + "ACTION_SAVE_CUSTOM_FILTER": "save as custom filter", + "PLACEHOLDER_FILTER_NAME": "Write the filter name and press enter", + "CATEGORIES": { + "TYPE": "Type", + "STATUS": "Status", + "SEVERITY": "Severity", + "PRIORITIES": "Priorities", + "TAGS": "Tags", + "ASSIGNED_TO": "Assigned to", + "CREATED_BY": "Created by", + "CUSTOM_FILTERS": "Custom filters" + }, + "CONFIRM_DELETE": { + "TITLE": "Delete custom filter", + "MESSAGE": "the custom filter '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "First Level Heading", @@ -1169,9 +1189,7 @@ "TITLE": "Filters", "REMOVE": "Remove Filters", "HIDE": "Hide Filters", - "SHOW": "Show Filters", - "FILTER_CATEGORY_STATUS": "Status", - "FILTER_CATEGORY_TAGS": "Tags" + "SHOW": "Show Filters" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1278,7 +1296,6 @@ "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ NEW ISSUE", "ACTION_PROMOTE_TO_US": "Promote to User Story", - "PLACEHOLDER_FILTER_NAME": "Write the filter name and press enter", "PROMOTED": "This issue has been promoted to US:", "EXTERNAL_REFERENCE": "This issue has been created from", "GO_TO_EXTERNAL_REFERENCE": "Go to origin", @@ -1296,28 +1313,6 @@ "TITLE": "Promote this issue to a new user story", "MESSAGE": "Are you sure you want to create a new US from this Issue?" }, - "FILTERS": { - "TITLE": "Filters", - "INPUT_SEARCH_PLACEHOLDER": "Subject or ref", - "TITLE_ACTION_SEARCH": "Search", - "ACTION_SAVE_CUSTOM_FILTER": "save as custom filter", - "BREADCRUMB": "Filters", - "TITLE_BREADCRUMB": "Filters", - "CATEGORIES": { - "TYPE": "Type", - "STATUS": "Status", - "SEVERITY": "Severity", - "PRIORITIES": "Priorities", - "TAGS": "Tags", - "ASSIGNED_TO": "Assigned to", - "CREATED_BY": "Created by", - "CUSTOM_FILTERS": "Custom filters" - }, - "CONFIRM_DELETE": { - "TITLE": "Delete custom filter", - "MESSAGE": "the custom filter '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Type", diff --git a/app/modules/components/board-zoom/board-zoom.directive.coffee b/app/modules/components/board-zoom/board-zoom.directive.coffee new file mode 100644 index 00000000..cba9fdcf --- /dev/null +++ b/app/modules/components/board-zoom/board-zoom.directive.coffee @@ -0,0 +1,29 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: board-zoom.directive.coffee +### + +BoardZoomDirective = () -> + return { + scope: { + levels: "=", + value: "=" + }, + templateUrl: 'components/board-zoom/board-zoom.html' + } + +angular.module('taigaComponents').directive("tgBoardZoom", [BoardZoomDirective]) diff --git a/app/modules/components/board-zoom/board-zoom.jade b/app/modules/components/board-zoom/board-zoom.jade new file mode 100644 index 00000000..e6067dbb --- /dev/null +++ b/app/modules/components/board-zoom/board-zoom.jade @@ -0,0 +1,9 @@ +input.range-slider( + type="range", + min="0", + max="{{levels - 1}}", + step="1" + ng-model="value" + ng-model-options="{ debounce: 200 }" + tg-bind-scope +) diff --git a/app/modules/components/board-zoom/board-zoom.scss b/app/modules/components/board-zoom/board-zoom.scss new file mode 100644 index 00000000..5e5d7eb2 --- /dev/null +++ b/app/modules/components/board-zoom/board-zoom.scss @@ -0,0 +1,108 @@ +$track-color: $whitish; +$thumb-color: $grayer; +$thumb-shadow: rgba($thumb-color, .3); + +$thumb-radius: 50%; +$thumb-height: 14px; +$thumb-width: 14px; +$thumb-border-width: 0; +$thumb-border-color: transparent; + +$track-width: 200px; +$track-height: 3px; +$track-border-width: 0; +$track-border-color: transparent; + +$track-radius: 1px; +$contrast: 2; + +@mixin track() { + width: $track-width; + height: $track-height; + cursor: pointer; + transition: all .2s ease; +} + +@mixin thumb() { + border: $thumb-border-width solid $thumb-border-color; + height: $thumb-height; + width: $thumb-width; + border-radius: $thumb-radius; + background: $thumb-color; + cursor: pointer; + box-shadow: 0 0 0 2px $thumb-shadow; + transition: box-shadow .2s; +} + +.range-slider { + -webkit-appearance: none; + margin: $thumb-height / 2 0; + width: $track-width; + + &:focus { + &::-webkit-slider-runnable-track { + background: lighten($track-color, $contrast); + } + &::-webkit-slider-thumb { + box-shadow: 0 0 0 4px $thumb-shadow; + } + &::-moz-range-thumb { + box-shadow: 0 0 0 4px $thumb-shadow; + } + &::-ms-fill-lower { + background: $track-color; + } + &::-ms-fill-upper { + background: lighten($track-color, $contrast); + } + } + + &::-webkit-slider-runnable-track { + @include track(); + background: $track-color; + border: $track-border-width solid $track-border-color; + border-radius: $track-radius; + } + + &::-webkit-slider-thumb { + @include thumb(); + -webkit-appearance: none; + margin-top: ((-$track-border-width * 2 + $track-height) / 2) - ($thumb-height / 2); + } + + &::-moz-range-track { + @include track(); + background: $track-color; + border: $track-border-width solid $track-border-color; + border-radius: $track-radius; + } + + &::-moz-range-thumb { + @include thumb(); + } + + &::-ms-track { + @include track(); + background: transparent; + border-color: transparent; + border-width: $thumb-width 0; + color: transparent; + } + + &::-ms-fill-lower { + background: darken($track-color, $contrast); + border: $track-border-width solid $track-border-color; + border-radius: $track-radius * 2; + } + + &::-ms-fill-upper { + background: $track-color; + border: $track-border-width solid $track-border-color; + border-radius: $track-radius * 2; + } + + &::-ms-thumb { + @include thumb(); + } + +} diff --git a/app/modules/components/card-slideshow/card-slideshow.controller.coffee b/app/modules/components/card-slideshow/card-slideshow.controller.coffee new file mode 100644 index 00000000..552f65bb --- /dev/null +++ b/app/modules/components/card-slideshow/card-slideshow.controller.coffee @@ -0,0 +1,38 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: card-slideshow.controller.coffee +### + +class CardSlideshowController + @.$inject = [] + + constructor: () -> + @.index = 0 + + next: () -> + @.index++ + + if @.index >= @.images.size + @.index = 0 + + previous: () -> + @.index-- + + if @.index < 0 + @.index = @.images.size - 1 + +angular.module('taigaComponents').controller('CardSlideshow', CardSlideshowController) diff --git a/app/modules/components/card-slideshow/card-slideshow.directive.coffee b/app/modules/components/card-slideshow/card-slideshow.directive.coffee new file mode 100644 index 00000000..bbce104b --- /dev/null +++ b/app/modules/components/card-slideshow/card-slideshow.directive.coffee @@ -0,0 +1,33 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: card.directive.coffee +### + +module = angular.module("taigaComponents") + +cardSlideshowDirective = () -> + return { + controller: "CardSlideshow", + templateUrl: "components/card-slideshow/card-slideshow.html", + bindToController: true, + controllerAs: "vm", + scope: { + images: "=" + } + } + +module.directive('tgCardSlideshow', cardSlideshowDirective) diff --git a/app/modules/components/card-slideshow/card-slideshow.jade b/app/modules/components/card-slideshow/card-slideshow.jade new file mode 100644 index 00000000..0f42c061 --- /dev/null +++ b/app/modules/components/card-slideshow/card-slideshow.jade @@ -0,0 +1,18 @@ +.card-slideshow(ng-if="vm.images.size") + tg-svg.slideshow-icon.slideshow-left( + ng-click="vm.previous()" + ng-if="vm.images.size > 1" + svg-icon="icon-arrow-left" + ) + tg-svg.slideshow-icon.slideshow-right( + ng-click="vm.next()" + ng-if="vm.images.size > 1" + svg-icon="icon-arrow-right" + ) + + .card-slideshow-wrapper( + ng-if="$index == vm.index" + tg-repeat="image in vm.images track by image.get('id')" + ) + tg-preload-image(preload-src="{{image.get('thumbnail_card_url')}}") + img(ng-src="{{image.get('thumbnail_card_url')}}") diff --git a/app/modules/components/card/card-templates/card-completion.jade b/app/modules/components/card/card-templates/card-completion.jade new file mode 100644 index 00000000..1f2fa662 --- /dev/null +++ b/app/modules/components/card/card-templates/card-completion.jade @@ -0,0 +1,4 @@ +.card-completion(ng-if="vm.visible('extra_info') && vm.item.getIn(['model', 'tasks']).size") + .card-completion-bar + .card-completion-percentage(ng-style="{width: vm.closedTasksPercent() + '%'}" ) + span.card-tooltip tasks {{vm.getClosedTasks().size}}/{{vm.item.getIn(['model', 'tasks']).size}} diff --git a/app/modules/components/card/card-templates/card-data.jade b/app/modules/components/card/card-templates/card-data.jade new file mode 100644 index 00000000..e918b9fc --- /dev/null +++ b/app/modules/components/card/card-templates/card-data.jade @@ -0,0 +1,21 @@ +.card-data( + ng-if="vm.visible('extra_info')" + ng-class="{'empty-tasks': !vm.item.getIn(['model', 'tasks']).size}" +) + span.card-estimation( + ng-if="vm.item.getIn(['model', 'total_points']) === null", + translate="US.NOT_ESTIMATED" + ) + span.card-estimation( + ng-if="vm.item.getIn(['model', 'total_points'])" + ) {{"COMMON.FIELDS.POINTS" | translate}} {{vm.item.getIn(['model', 'total_points'])}} + .card-statistics + .statistic.card-votes(ng-class="{'active': vm.item.getIn(['model', 'is_voter'])}") + tg-svg(svg-icon="icon-upvote") + span {{vm.item.getIn(['model', 'total_voters'])}} + .statistic.card-watchers + tg-svg(svg-icon="icon-watch") + span {{vm.item.getIn(['model', 'watchers']).size}} + .statistic.card-attachments(ng-if="vm.item.getIn(['model', 'attachments']).size") + tg-svg(svg-icon="icon-attachment") + span {{vm.item.getIn(['model', 'attachments']).size}} diff --git a/app/modules/components/card/card-templates/card-owner.jade b/app/modules/components/card/card-templates/card-owner.jade new file mode 100644 index 00000000..682e0c4a --- /dev/null +++ b/app/modules/components/card/card-templates/card-owner.jade @@ -0,0 +1,43 @@ +.card-owner + .card-owner-info(ng-if="vm.item.get('assigned_to')") + .card-owner-avatar + img( + ng-class="{'is-iocaine': vm.item.getIn(['model', 'is_iocaine'])}" + tg-avatar="vm.item.get('assigned_to')" + ) + tg-svg( + ng-if="vm.item.getIn(['model', 'is_iocaine'])" + svg-icon="icon-iocaine" + svg-title="COMMON.IOCAINE_TEXT" + ) + span.card-owner-name(ng-if="vm.visible('owner')") {{vm.item.getIn(['assigned_to', 'full_name'])}} + div(ng-if="!vm.visible('owner')") + include card-title + + .card-owner-info(ng-if="!vm.item.get('assigned_to')") + img(ng-src="/#{v}/images/unnamed.png") + span.card-owner-name( + ng-if="vm.visible('owner')", + translate="COMMON.ASSIGNED_TO.NOT_ASSIGNED" + ) + div(ng-if="!vm.visible('owner')") + include card-title + + .card-owner-actions( + ng-if="vm.visible('owner')" + tg-check-permission="{{vm.getPermissionsKey()}}" + ) + a.e2e-assign.card-owner-assign( + ng-click="vm.onClickAssignedTo({id: vm.item.get('id')})" + href="" + ) + tg-svg(svg-icon="icon-add-user") + span(translate="COMMON.CARD.ASSIGN_TO") + + a.e2e-edit.card-edit( + href="" + ng-click="vm.onClickEdit({id: vm.item.get('id')})" + tg-loading="vm.item.get('loading')" + ) + tg-svg(svg-icon="icon-edit") + span(translate="COMMON.CARD.EDIT") diff --git a/app/modules/components/card/card-templates/card-tags.jade b/app/modules/components/card/card-templates/card-tags.jade new file mode 100644 index 00000000..a5161331 --- /dev/null +++ b/app/modules/components/card/card-templates/card-tags.jade @@ -0,0 +1,7 @@ +.card-tags(ng-if="vm.visible('tags')") + span.card-tag( + tg-repeat="tag in vm.item.get('colorized_tags') track by tag.get('name')" + style="background-color: {{tag.get('color')}}" + title="{{tag.get('name')}}" + ng-if="tag.get('color')" + ) diff --git a/app/modules/components/card/card-templates/card-tasks.jade b/app/modules/components/card/card-templates/card-tasks.jade new file mode 100644 index 00000000..d029bc1e --- /dev/null +++ b/app/modules/components/card/card-templates/card-tasks.jade @@ -0,0 +1,7 @@ +ul.card-tasks(ng-if="vm.isRelatedTasksVisible()") + li.card-task(tg-repeat="task in vm.item.getIn(['model', 'tasks'])") + a( + href="#" + tg-nav="project-tasks-detail:project=vm.project.slug,ref=task.get('ref')", + ng-class="{'closed-task': task.get('is_closed'), 'blocked-task': task.get('is_blocked')}" + ) {{"#" + task.get('ref')}} {{task.get('subject')}} diff --git a/app/modules/components/card/card-templates/card-title.jade b/app/modules/components/card/card-templates/card-title.jade new file mode 100644 index 00000000..ad5434d8 --- /dev/null +++ b/app/modules/components/card/card-templates/card-title.jade @@ -0,0 +1,9 @@ +h2.card-title + a( + href="" + tg-nav="{{vm.getNavKey()}}:project=vm.project.slug,ref=vm.item.getIn(['model', 'ref'])", + tg-nav-get-params="{\"kanban-status\": {{vm.item.getIn(['model', 'status'])}}}" + title="#{{ ::vm.item.getIn(['model', 'ref']) }} {{ vm.item.getIn(['model', 'subject'])}}" + ) + span(ng-if="vm.visible('ref')") {{::"#" + vm.item.getIn(['model', 'ref'])}} + span.e2e-title(ng-if="vm.visible('subject')") {{vm.item.getIn(['model', 'subject'])}} diff --git a/app/modules/components/card/card-templates/card-unfold.jade b/app/modules/components/card/card-templates/card-unfold.jade new file mode 100644 index 00000000..5b734660 --- /dev/null +++ b/app/modules/components/card/card-templates/card-unfold.jade @@ -0,0 +1,6 @@ +.card-unfold.ng-animate-disabled( + ng-click="vm.toggleFold()" + ng-if="vm.visible('unfold') && (vm.item.getIn(['model', 'tasks']).size || vm.item.get('images').size)" + role="button" +) + tg-svg(svg-icon="icon-view-more") diff --git a/app/modules/components/card/card.controller.coffee b/app/modules/components/card/card.controller.coffee new file mode 100644 index 00000000..25dce94c --- /dev/null +++ b/app/modules/components/card/card.controller.coffee @@ -0,0 +1,82 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: card.controller.coffee +### + +class CardController + @.$inject = [] + + visible: (name) -> + return @.zoom.indexOf(name) != -1 + + toggleFold: () -> + @.onToggleFold({id: @.item.get('id')}) + + getClosedTasks: () -> + return @.item.getIn(['model', 'tasks']).filter (task) -> return task.get('is_closed'); + + closedTasksPercent: () -> + return @.getClosedTasks().size * 100 / @.item.getIn(['model', 'tasks']).size + + getPermissionsKey: () -> + if @.type == 'task' + return 'modify_task' + else + return 'modify_us' + + _setVisibility: () -> + visibility = { + related: @.visible('related_tasks'), + slides: @.visible('attachments') + } + + if!_.isUndefined(@.item.get('foldStatusChanged')) + if @.visible('related_tasks') && @.visible('attachments') + visibility.related = !@.item.get('foldStatusChanged') + visibility.slides = !@.item.get('foldStatusChanged') + else if @.visible('attachments') + visibility.related = @.item.get('foldStatusChanged') + visibility.slides = @.item.get('foldStatusChanged') + else if !@.visible('related_tasks') && !@.visible('attachments') + visibility.related = @.item.get('foldStatusChanged') + visibility.slides = @.item.get('foldStatusChanged') + + if !@.item.getIn(['model', 'tasks']) || !@.item.getIn(['model', 'tasks']).size + visibility.related = false + + if !@.item.get('images') || !@.item.get('images').size + visibility.slides = false + + return visibility + + isRelatedTasksVisible: () -> + visibility = @._setVisibility() + + return visibility.related + + isSlideshowVisible: () -> + visibility = @._setVisibility() + + return visibility.slides + + getNavKey: () -> + if @.type == 'task' + return 'project-tasks-detail' + else + return 'project-userstories-detail' + +angular.module('taigaComponents').controller('Card', CardController) diff --git a/app/modules/components/card/card.controller.spec.coffee b/app/modules/components/card/card.controller.spec.coffee new file mode 100644 index 00000000..3e2ed30c --- /dev/null +++ b/app/modules/components/card/card.controller.spec.coffee @@ -0,0 +1,142 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: card.controller.spec.coffee +### + +describe "Card", -> + $provide = null + $controller = null + mocks = {} + + _inject = -> + inject (_$controller_) -> + $controller = _$controller_ + + _setup = -> + _inject() + + beforeEach -> + module "taigaComponents" + + _setup() + + it "toggle fold callback", () -> + ctrl = $controller("Card") + + ctrl.item = Immutable.fromJS({id: 2}) + ctrl.onToggleFold = sinon.spy() + + ctrl.toggleFold() + + expect(ctrl.onToggleFold).to.have.been.calledWith({id: 2}) + + it "get closed tasks", () -> + ctrl = $controller("Card") + + ctrl.item = Immutable.fromJS({ + id: 2, + model: { + tasks: [ + {is_closed: true}, + {is_closed: false}, + {is_closed: true} + ] + } + }) + + tasks = ctrl.getClosedTasks() + expect(tasks.size).to.be.equal(2) + + it "get closed percent", () -> + ctrl = $controller("Card") + + ctrl.item = Immutable.fromJS({ + id: 2, + model: { + tasks: [ + {is_closed: true}, + {is_closed: false}, + {is_closed: false}, + {is_closed: true} + ] + } + }) + + percent = ctrl.closedTasksPercent() + expect(percent).to.be.equal(50) + + describe "check if related task and slides visibility", () -> + it "no content", () -> + ctrl = $controller("Card") + + ctrl.item = Immutable.fromJS({ + id: 2, + images: [], + model: { + tasks: [] + } + }) + + ctrl.visible = () => return true + + visibility = ctrl._setVisibility() + + expect(visibility).to.be.eql({ + related: false, + slides: false + }) + + it "with content", () -> + ctrl = $controller("Card") + + ctrl.item = Immutable.fromJS({ + id: 2, + images: [3,4], + model: { + tasks: [1,2] + } + }) + + ctrl.visible = () => return true + + visibility = ctrl._setVisibility() + + expect(visibility).to.be.eql({ + related: true, + slides: true + }) + + it "fold", () -> + ctrl = $controller("Card") + + ctrl.item = Immutable.fromJS({ + foldStatusChanged: true, + id: 2, + images: [3,4], + model: { + tasks: [1,2] + } + }) + + ctrl.visible = () => return true + + visibility = ctrl._setVisibility() + + expect(visibility).to.be.eql({ + related: false, + slides: false + }) diff --git a/app/modules/components/card/card.directive.coffee b/app/modules/components/card/card.directive.coffee new file mode 100644 index 00000000..4ad69505 --- /dev/null +++ b/app/modules/components/card/card.directive.coffee @@ -0,0 +1,43 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: card.directive.coffee +### + +module = angular.module("taigaComponents") + +cardDirective = () -> + return { + link: (scope) -> + + controller: "Card", + controllerAs: "vm", + bindToController: true, + templateUrl: "components/card/card.html", + scope: { + onToggleFold: "&", + onClickAssignedTo: "&", + onClickEdit: "&", + project: "=", + item: "=", + zoom: "=", + zoomLevel: "=", + archived: "=", + type: "@" + } + } + +module.directive('tgCard', cardDirective) diff --git a/app/modules/components/card/card.jade b/app/modules/components/card/card.jade new file mode 100644 index 00000000..556c3c82 --- /dev/null +++ b/app/modules/components/card/card.jade @@ -0,0 +1,16 @@ +.card-inner( + class="{{'zoom-' + vm.zoomLevel}}" + ng-class="{'card-blocked': vm.item.getIn(['model', 'is_blocked']), 'archived': vm.archived}" +) + include card-templates/card-tags + include card-templates/card-owner + div(ng-if="vm.visible('owner')") + include card-templates/card-title + include card-templates/card-data + include card-templates/card-completion + include card-templates/card-tasks + tg-card-slideshow( + ng-if="vm.isSlideshowVisible()" + images="vm.item.get('images')" + ) + include card-templates/card-unfold diff --git a/app/modules/components/card/card.scss b/app/modules/components/card/card.scss new file mode 100644 index 00000000..b31f11a3 --- /dev/null +++ b/app/modules/components/card/card.scss @@ -0,0 +1,326 @@ +.card { + box-shadow: 2px 2px 4px darken($whitish, 10%); + cursor: move; + display: block; + margin: 0 .6rem .6rem; + overflow: hidden; + transition: box-shadow .2s ease-in; + &:hover { + box-shadow: 3px 3px 6px darken($whitish, 10%); + } +} + +.card-inner { + background: $white; + border-radius: .25rem; + &.zoom-0, + &.zoom-1 { + .card-title { + flex: 1; + margin: 0; + padding: .25rem; + } + } + &.zoom-1 { + .card-owner-info { + align-items: flex-start; + } + } + &.card-blocked { + background: $red-light; + .statistic, + .card-title a, + .card-owner-name, + .card-estimation { + color: $white; + } + .card-owner-actions { + background: rgba($red-light, .9); + } + svg { + fill: $white; + } + .statistic { + &.active { + color: $white; + } + } + .card-unfold { + &:hover { + background: rgba($red-light, .9); + } + } + &.zoom-0, + &.zoom-1 { + .card-title { + color: $white; + } + } + } +} + +.card-tags { + display: flex; + .card-tag { + display: block; + flex: 1; + height: .5rem; + + } +} + +.card-owner { + position: relative; + .card-owner-info { + align-items: center; + display: flex; + } + .card-owner-avatar { + line-height: 0; + position: relative; + } + .icon-iocaine { + @include svg-size(1.2rem); + background: rgba($blackish, .8); + border-radius: 4px 0 0; + bottom: .25rem; + fill: $whitish; + padding: .25rem; + position: absolute; + right: .5rem; + } + .is-iocaine { + filter: hue-rotate(265deg) saturate(3); + } + &:hover { + .card-owner-actions { + opacity: 1; + } + } + img { + flex-shrink: 0; + height: 2.5rem; + margin-right: .5rem; + width: 2.5rem; + } + .card-owner-name { + color: $gray-light; + } +} + +.card-owner-actions { + background: rgba($white, .9); + display: flex; + justify-content: space-between; + left: 0; + opacity: 0; + position: absolute; + top: 0; + transition: all .2s; + width: 100%; + &:hover { + color: $primary-light; + svg { + fill: currentColor; + } + } + .icon { + @include svg-size(1.2rem); + display: inline-block; + margin-right: .25rem; + padding: 0; + } + a { + align-items: center; + cursor: pointer; + display: flex; + padding: .6rem 1rem; + } +} + +.card-title { + @include font-size(normal); + line-height: 1.25; + margin-bottom: .25rem; + padding: 1rem 1rem 0; + span { + padding-right: .25rem; + } +} + +.card-data { + color: $gray-light; + display: flex; + font-size: 14px; + justify-content: space-between; + padding: 0 1rem .5rem; +} + +.card-statistics { + @include font-size(small); + color: lighten($gray-light, 25%); + display: flex; + margin-left: auto; + .statistic { + align-content: center; + display: flex; + margin-left: .75rem; + &.active { + color: $primary-light; + svg { + fill: currentColor; + } + } + } + .icon { + @include svg-size(.75rem); + fill: lighten($gray-light, 25%); + margin-right: .2rem; + } +} + +.card-completion { + margin: 0 1rem .5rem; + position: relative; + .card-completion-bar { + background: $whitish; + height: .4rem; + width: 100%; + } + .card-completion-percentage { + background: $primary-light; + cursor: pointer; + height: .4rem; + left: 0; + position: absolute; + top: 0; + &:hover { + + .card-tooltip { + opacity: 1; + } + } + } + .card-tooltip { + background: $blackish; + border-radius: 5px; + color: $white; + font-size: 14px; + left: calc(25% - 50px); + opacity: 0; + padding: .25rem 1rem; + position: absolute; + text-align: center; + top: -2.25rem; + transition: opacity .2s; + width: 100px; + &::after { + background: $black; + content: ''; + height: 10px; + left: 50%; + position: absolute; + top: 70%; + transform: rotate(45deg); + width: 10px; + } + } +} + +.card-unfold { + align-items: center; + cursor: pointer; + display: flex; + justify-content: center; + margin: 0; + padding: .25rem; + &:hover { + background: linear-gradient(to bottom, $white, darken($white, 1%)); + } + svg { + @include svg-size($width: 2rem, $height: .3rem); + fill: $whitish; + } +} + +.card-tasks { + border-top: 1px solid $whitish; + margin: 0; + margin-top: .5rem; + padding: 0; +} + +.card-task { + @include font-size(xsmall); + border-bottom: 1px solid $whitish; + list-style: none; + a { + color: $gray-light; + display: block; + overflow: hidden; + padding: .5rem .75rem; + text-overflow: ellipsis; + transition: color .2s; + white-space: nowrap; + &.blocked-task { + color: $red-light; + } + &.closed-task { + color: $gray-light; + text-decoration: line-through; + } + &:hover { + color: $primary; + } + } +} + +.card-slideshow { + position: relative; + &:hover { + .slideshow-left, + .slideshow-right { + background: rgba($white, .2); + padding: .25rem; + transition: background .2s; + } + } + .slideshow-icon { + cursor: pointer; + position: absolute; + top: 35%; + &:hover { + background: rgba($primary-light, .5); + transition: background .2s; + } + } + svg { + @include svg-size(1.2rem); + transition: fill .2s; + } + .slideshow-left, + .slideshow-right { + background: transparent; + padding: .25rem; + } + .slideshow-left { + left: 0; + } + .slideshow-right { + right: 0; + } + img { + width: 100%; + } +} + +.card-slideshow-wrapper { + align-items: center; + display: flex; + height: 120px; + justify-content: center; + overflow: hidden; + .loading-spinner { + min-height: 3rem; + min-width: 3rem; + } +} diff --git a/app/modules/components/filter/filter-remote.service.coffee b/app/modules/components/filter/filter-remote.service.coffee new file mode 100644 index 00000000..95435432 --- /dev/null +++ b/app/modules/components/filter/filter-remote.service.coffee @@ -0,0 +1,68 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: filter-utils.service.coffee +### + +generateHash = taiga.generateHash + +class FilterRemoteStorageService extends taiga.Service + @.$inject = [ + "$q", + "$tgUrls", + "$tgHttp" + ] + + constructor: (@q, @urls, @http) -> + + storeFilters: (projectId, myFilters, filtersHashSuffix) -> + deferred = @q.defer() + url = @urls.resolve("user-storage") + ns = "#{projectId}:#{filtersHashSuffix}" + hash = generateHash([projectId, ns]) + if _.isEmpty(myFilters) + promise = @http.delete("#{url}/#{hash}", {key: hash, value:myFilters}) + promise.then -> + deferred.resolve() + promise.then null, -> + deferred.reject() + else + promise = @http.put("#{url}/#{hash}", {key: hash, value:myFilters}) + promise.then (data) -> + deferred.resolve() + promise.then null, (data) => + innerPromise = @http.post("#{url}", {key: hash, value:myFilters}) + innerPromise.then -> + deferred.resolve() + innerPromise.then null, -> + deferred.reject() + return deferred.promise + + getFilters: (projectId, filtersHashSuffix) -> + deferred = @q.defer() + url = @urls.resolve("user-storage") + ns = "#{projectId}:#{filtersHashSuffix}" + hash = generateHash([projectId, ns]) + + promise = @http.get("#{url}/#{hash}") + promise.then (data) -> + deferred.resolve(data.data.value) + promise.then null, (data) -> + deferred.resolve({}) + + return deferred.promise + +angular.module("taigaComponents").service("tgFilterRemoteStorageService", FilterRemoteStorageService) diff --git a/app/modules/components/filter/filter-slide-down.directive.coffee b/app/modules/components/filter/filter-slide-down.directive.coffee new file mode 100644 index 00000000..d0f4dbaf --- /dev/null +++ b/app/modules/components/filter/filter-slide-down.directive.coffee @@ -0,0 +1,45 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: filter.-slide-down.controller.coffee +### + +FilterSlideDownDirective = () -> + link = (scope, el, attrs, ctrl) -> + filter = $('tg-filter') + + scope.$watch attrs.ngIf, (value) -> + if value + filter.find('.filter-list').hide() + + wrapperHeight = filter.height() + contentHeight = 0 + + filter.children().each () -> + contentHeight += $(this).outerHeight(true) + + $(el.context.nextSibling) + .css({ + "max-height": wrapperHeight - contentHeight, + "display": "block" + }) + + return { + priority: 900, + link: link + } + +angular.module('taigaComponents').directive("tgFilterSlideDown", [FilterSlideDownDirective]) diff --git a/app/modules/components/filter/filter.controller.coffee b/app/modules/components/filter/filter.controller.coffee new file mode 100644 index 00000000..0830f63d --- /dev/null +++ b/app/modules/components/filter/filter.controller.coffee @@ -0,0 +1,70 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: filter.controller.coffee +### + +class FilterController + @.$inject = [] + + constructor: () -> + @.opened = null + @.customFilterForm = false + @.customFilterName = '' + + toggleFilterCategory: (filterName) -> + if @.opened == filterName + @.opened = null + else + @.opened = filterName + + isOpen: (filterName) -> + return @.opened == filterName + + saveCustomFilter: () -> + @.onSaveCustomFilter({name: @.customFilterName}) + @.customFilterForm = false + @.opened = 'custom-filter' + @.customFilterName = '' + + changeQ: () -> + @.onChangeQ({q: @.q}) + + unselectFilter: (filter) -> + @.onRemoveFilter({filter: filter}) + + unselectFilter: (filter) -> + @.onRemoveFilter({filter: filter}) + + selectFilter: (filterCategory, filter) -> + filter = { + category: filterCategory + filter: filter + } + + @.onAddFilter({filter: filter}) + + removeCustomFilter: (filter) -> + @.onRemoveCustomFilter({filter: filter}) + + selectCustomFilter: (filter) -> + @.onSelectCustomFilter({filter: filter}) + + isFilterSelected: (filterCategory, filter) -> + return !!_.find @.selectedFilters, (it) -> + return filter.id == it.id && filterCategory.dataType == it.dataType + +angular.module('taigaComponents').controller('Filter', FilterController) diff --git a/app/modules/components/filter/filter.controller.spec.coffee b/app/modules/components/filter/filter.controller.spec.coffee new file mode 100644 index 00000000..c7166a4b --- /dev/null +++ b/app/modules/components/filter/filter.controller.spec.coffee @@ -0,0 +1,87 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: filter.controller.spec.coffee +### + +describe "Filter", -> + $provide = null + $controller = null + mocks = {} + + _inject = -> + inject (_$controller_) -> + $controller = _$controller_ + + _setup = -> + _inject() + + beforeEach -> + module "taigaComponents" + + _setup() + + it "toggle filter category", () -> + ctrl = $controller("Filter") + + ctrl.toggleFilterCategory('filter1') + + expect(ctrl.opened).to.be.equal('filter1') + + ctrl.toggleFilterCategory('filter1') + + expect(ctrl.opened).to.be.null + + it "is filter open", () -> + ctrl = $controller("Filter") + ctrl.opened = 'filter1' + + isOpen = ctrl.isOpen('filter1') + + expect(isOpen).to.be.true; + + it "save custom filter", () -> + ctrl = $controller("Filter") + ctrl.customFilterName = "custom-name" + ctrl.customFilterForm = true + ctrl.onSaveCustomFilter = sinon.spy() + + ctrl.saveCustomFilter() + + expect(ctrl.onSaveCustomFilter).to.have.been.calledWith({name: "custom-name"}) + expect(ctrl.customFilterForm).to.be.false + expect(ctrl.opened).to.be.equal('custom-filter') + expect(ctrl.customFilterName).to.be.equal('') + + it "is filter selected", () -> + ctrl = $controller("Filter") + ctrl.selectedFilters = [ + {id: 1, dataType: "1"}, + {id: 2, dataType: "2"}, + {id: 3, dataType: "3"} + ] + + filterCategory = {dataType: "x"} + filter = {id: 1} + isFilterSelected = ctrl.isFilterSelected(filterCategory, filter) + + expect(isFilterSelected).to.be.false + + filterCategory = {dataType: "1"} + filter = {id: 1} + isFilterSelected = ctrl.isFilterSelected(filterCategory, filter) + + expect(isFilterSelected).to.be.true diff --git a/app/modules/components/filter/filter.directive.coffee b/app/modules/components/filter/filter.directive.coffee new file mode 100644 index 00000000..92dca69f --- /dev/null +++ b/app/modules/components/filter/filter.directive.coffee @@ -0,0 +1,44 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: filter.directive.coffee +### + +FilterDirective = () -> + link = (scope, el, attrs, ctrl) -> + + return { + scope: { + onChangeQ: "&", + onAddFilter: "&", + onSelectCustomFilter: "&", + onRemoveFilter: "&", + onRemoveCustomFilter: "&", + onSaveCustomFilter: "&", + customFilters: "<", + q: "<", + filters: "<" + customFilters: "<" + selectedFilters: "<" + }, + bindToController: true, + controller: "Filter", + controllerAs: "vm", + templateUrl: 'components/filter/filter.html', + link: link + } + +angular.module('taigaComponents').directive("tgFilter", [FilterDirective]) diff --git a/app/modules/components/filter/filter.jade b/app/modules/components/filter/filter.jade new file mode 100644 index 00000000..05760586 --- /dev/null +++ b/app/modules/components/filter/filter.jade @@ -0,0 +1,110 @@ +h1 + span.title(translate="COMMON.FILTERS.TITLE") + +form + fieldset + input.e2e-filter-q( + type="text", + placeholder="{{'COMMON.FILTERS.INPUT_PLACEHOLDER' | translate}}", + ng-model="vm.q" + ng-model-options="{ debounce: 200 }" + ng-change="vm.changeQ()" + ) + tg-svg.search-action( + svg-icon="icon-search", + title="{{'COMMON.FILTERS.TITLE_ACTION_SEARCH' | translate}}" + ) + +.filters-step-cat + .filters-applied + .single-filter.ng-animate-disabled(ng-repeat="it in vm.selectedFilters track by it.key") + span.name(ng-attr-style="{{it.color ? 'border-left: 3px solid ' + it.color: ''}}") {{it.name}} + a.remove-filter.e2e-remove-filter( + ng-click="vm.unselectFilter(it)" + href="" + ) + tg-svg(svg-icon="icon-close") + + a.button.button-gray.save-filters.ng-animate-disabled.e2e-open-custom-filter-form( + ng-click="vm.customFilterForm = true" + ng-if="vm.selectedFilters.length && !vm.customFilterForm" + href="", + title="{{'COMMON.SAVE' | translate}}", + translate="COMMON.FILTERS.ACTION_SAVE_CUSTOM_FILTER" + ) + + form( + ng-if="vm.customFilterForm" + ng-submit="vm.saveCustomFilter()" + ) + input.my-filter-name.e2e-filter-name-input( + tg-autofocus + ng-model="vm.customFilterName" + type="text" + placeholder="{{'COMMON.FILTERS.PLACEHOLDER_FILTER_NAME' | translate}}" + ) + + .filters-cats + ul + li( + ng-class="{selected: vm.isOpen(filter.dataType)}" + ng-repeat="filter in vm.filters track by filter.dataType" + ) + a.filters-cat-single.e2e-category( + ng-class="{selected: vm.isOpen(filter.dataType)}" + ng-click="vm.toggleFilterCategory(filter.dataType)" + href="" + title="{{::filter.title}}" + ) + span.title {{::filter.title}} + tg-svg.ng-animate-disabled( + ng-if="!vm.isOpen(filter.dataType)" + svg-icon="icon-arrow-right" + ) + tg-svg.ng-animate-disabled( + ng-if="vm.isOpen(filter.dataType)" + svg-icon="icon-arrow-down" + ) + + .filter-list( + ng-if="vm.isOpen(filter.dataType)", + tg-filter-slide-down + ) + .single-filter.ng-animate-disabled( + ng-repeat="it in filter.content" + ng-if="!vm.isFilterSelected(filter, it) && !(it.count == 0 && filter.hideEmpty)" + ng-click="vm.selectFilter(filter, it)" + ) + span.name(ng-attr-style="{{it.color ? 'border-left: 3px solid ' + it.color: ''}}") {{it.name}} + span.number.e2e-filter-count(ng-if="it.count > 0") {{it.count}} + + li.custom-filters.e2e-custom-filters(ng-class="{selected: vm.isOpen('custom-filter')}") + a.filters-cat-single( + ng-class="{selected: vm.isOpen('custom-filter')}" + ng-click="vm.toggleFilterCategory('custom-filter')" + href="" + title="{{'COMMON.FILTERS.CATEGORIES.CUSTOM_FILTERS' | translate}}" + ) + span.title(translate="COMMON.FILTERS.CATEGORIES.CUSTOM_FILTERS") + tg-svg.ng-animate-disabled( + ng-if="!vm.isOpen('custom-filter')" + svg-icon="icon-arrow-right" + ) + tg-svg.ng-animate-disabled( + ng-if="vm.isOpen('custom-filter')" + svg-icon="icon-arrow-down" + ) + .filter-list( + ng-if="vm.isOpen('custom-filter')", + tg-filter-slide-down + ) + .single-filter.ng-animate-disabled.e2e-custom-filter( + ng-repeat="it in vm.customFilters" + ng-click="vm.selectCustomFilter(it)" + ) + span.name {{it.name}} + a.remove-filter.e2e-remove-custom-filter( + ng-click="vm.removeCustomFilter(it)" + href="" + ) + tg-svg(svg-icon="icon-trash") diff --git a/app/modules/components/filter/filter.scss b/app/modules/components/filter/filter.scss new file mode 100644 index 00000000..ad2e2a7f --- /dev/null +++ b/app/modules/components/filter/filter.scss @@ -0,0 +1,150 @@ +tg-filter { + background-color: $whitish; + display: block; + left: 0; + min-height: 100%; + padding: 1rem 0; + position: absolute; + top: 0; + width: 260px; + z-index: 1; + .filters-applied { + padding: 0 1rem 1rem; + } + h1, + form { + padding: 0 1rem; + } + input { + background: $grayer; + color: $white; + @include placeholder { + color: $gray-light; + } + } + .search-action { + position: absolute; + right: .7rem; + top: .7rem; + } + &.ng-hide-add { + transform: translateX(0); + transition-duration: .5s; + } + &.ng-hide-add-active { + transform: translateX(-260px); + } + &.ng-hide-remove { + transform: translateX(-260px); + transition-duration: .5s; + } + &.ng-hide-remove-active { + transform: translateX(0); + } +} + +.filter-list { + display: none; + overflow-y: auto; + padding: 1rem; +} + +.filters-step-cat { + margin-top: 2rem; +} + +.filters-cats { + ul { + margin-bottom: 0; + } + li { + border-bottom: 1px solid $gray-light; + text-transform: uppercase; + &.selected { + border-bottom: 0; + } + } + .custom-filters { + .title { + color: $primary; + } + } + .filters-cat-single { + align-items: center; + color: $grayer; + display: flex; + justify-content: space-between; + padding: .5rem .5rem .5rem 1.5rem; + transition: color .2s ease-in; + &:hover, + &.selected { + background-color: rgba(darken($whitish, 20%), 1); + color: $grayer; + transition: background-color .2s ease-in; + .icon { + opacity: 1; + transition: opacity .2s ease-in; + } + } + } + .icon-arrow-down { + fill: currentColor; + float: right; + height: .9rem; + opacity: 0; + transition: opacity .2s ease-in; + width: .9rem; + } +} + +.single-filter { + @include font-type(text); + @include clearfix; + align-items: center; + background: darken($whitish, 10%); // Fallback + cursor: pointer; + display: flex; + justify-content: space-between; + margin-bottom: .5rem; + opacity: .5; + padding-right: .5rem; + position: relative; + &:hover { + color: $grayer; + opacity: 1; + transition: opacity .2s linear; + } + &.selected, + &.active { + color: $grayer; + opacity: 1; + transition: opacity .2s linear; + } + .name, + .number { + padding: 8px 10px; + } + .name { + @include ellipsis(100%); + display: block; + width: 100%; + } + .number { + background: darken($whitish, 20%); // Fallback + position: absolute; + right: 0; + top: 0; + } + .remove-filter { + display: block; + svg { + fill: $gray; + transition: fill .2s linear; + } + &:hover { + svg { + fill: $red; + } + } + } +} diff --git a/app/modules/components/joy-ride/joy-ride.service.coffee b/app/modules/components/joy-ride/joy-ride.service.coffee index 396ec30d..bf22b54b 100644 --- a/app/modules/components/joy-ride/joy-ride.service.coffee +++ b/app/modules/components/joy-ride/joy-ride.service.coffee @@ -138,7 +138,7 @@ class JoyRideService extends taiga.Service if @checkPermissionsService.check('add_us') steps.push({ - element: '.icon-plus', + element: '.add-action', position: 'bottom', joyride: { title: @translate.instant('JOYRIDE.KANBAN.STEP3.TITLE') diff --git a/app/modules/components/kanban-board-zoom/kanban-board-zoom.directive.coffee b/app/modules/components/kanban-board-zoom/kanban-board-zoom.directive.coffee new file mode 100644 index 00000000..26e60a0f --- /dev/null +++ b/app/modules/components/kanban-board-zoom/kanban-board-zoom.directive.coffee @@ -0,0 +1,69 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: kanban-board-zoom.directive.coffee +### + +KanbanBoardZoomDirective = (storage, projectService) -> + link = (scope, el, attrs, ctrl) -> + scope.zoomIndex = storage.get("kanban_zoom") or 2 + scope.levels = 5 + + zooms = [ + ["ref"], + ["subject"], + ["owner", "tags", "extra_info", "unfold"], + ["attachments"], + ["related_tasks"] + ] + + getZoomView = (zoomIndex = 0) -> + if storage.get("kanban_zoom") != zoomIndex + storage.set("kanban_zoom", zoomIndex) + + return _.reduce zooms, (result, value, key) -> + if key <= zoomIndex + result = result.concat(value) + + return result + + scope.$watch 'zoomIndex', (zoomLevel) -> + zoom = getZoomView(zoomLevel) + scope.onZoomChange({zoomLevel: zoomLevel, zoom: zoom}) + + unwatch = scope.$watch () -> + return projectService.project + , (project) -> + if project + if project.get('my_permissions').indexOf("view_tasks") == -1 + scope.levels = 4 + unwatch() + + return { + scope: { + onZoomChange: "&" + }, + template: """ + + """, + link: link + } + +angular.module('taigaComponents').directive("tgKanbanBoardZoom", ["$tgStorage", "tgProjectService", KanbanBoardZoomDirective]) diff --git a/app/modules/components/taskboard-zoom/taskboard-zoom.directive.coffee b/app/modules/components/taskboard-zoom/taskboard-zoom.directive.coffee new file mode 100644 index 00000000..7db01d43 --- /dev/null +++ b/app/modules/components/taskboard-zoom/taskboard-zoom.directive.coffee @@ -0,0 +1,62 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: taskboard-zoom.directive.coffee +### + +TaskboardZoomDirective = (storage) -> + link = (scope, el, attrs, ctrl) -> + scope.zoomIndex = storage.get("taskboard_zoom") or 2 + + scope.levels = 4 + + zooms = [ + ["ref"], + ["subject"], + ["owner", "tags", "extra_info", "unfold"], + ["attachments"], + ["related_tasks"] + ] + + getZoomView = (zoomIndex = 0) -> + if storage.get("taskboard_zoom") != zoomIndex + storage.set("taskboard_zoom", zoomIndex) + + return _.reduce zooms, (result, value, key) -> + if key <= zoomIndex + result = result.concat(value) + + return result + + scope.$watch 'zoomIndex', (zoomLevel) -> + zoom = getZoomView(zoomLevel) + scope.onZoomChange({zoomLevel: zoomLevel, zoom: zoom}) + + return { + scope: { + onZoomChange: "&" + }, + template: """ + + """, + link: link + } + +angular.module('taigaComponents').directive("tgTaskboardZoom", ["$tgStorage", TaskboardZoomDirective]) diff --git a/app/partials/backlog/backlog.jade b/app/partials/backlog/backlog.jade index def38f1d..75ca752c 100644 --- a/app/partials/backlog/backlog.jade +++ b/app/partials/backlog/backlog.jade @@ -3,8 +3,21 @@ doctype html div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", ng-init="section='backlog'") tg-project-menu - sidebar.menu-secondary.extrabar.filters-bar(tg-backlog-filters) - include ../includes/modules/backlog-filters + + sidebar.backlog-filter + tg-filter( + q="ctrl.filterQ" + filters="ctrl.filters" + custom-filters="ctrl.customFilters" + selected-filters="ctrl.selectedFilters" + customFilters="ctl.customFilters" + on-save-custom-filter="ctrl.saveCustomFilter(name)" + on-add-filter="ctrl.addFilter(filter)" + on-select-custom-filter="ctrl.selectCustomFilter(filter)" + on-remove-custom-filter="ctrl.removeCustomFilter(filter)" + on-remove-filter="ctrl.removeFilter(filter)" + on-change-q="ctrl.changeQ(q)" + ) section.main.backlog include ../includes/components/mainTitle @@ -39,13 +52,20 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", ) tg-svg(svg-icon="icon-move") span.text(translate="BACKLOG.MOVE_US_TO_LATEST_SPRINT") - a.trans-button( - ng-if="userstories.length" + a.trans-button.e2e-open-filter( + ng-if="!ctrl.activeFilters" href="" title="{{'BACKLOG.FILTERS.TOGGLE' | translate}}" id="show-filters-button" translate="BACKLOG.FILTERS.SHOW" ) + a.trans-button.active.e2e-open-filter( + ng-if="ctrl.activeFilters" + href="" + title="{{'BACKLOG.FILTERS.HIDE' | translate}}" + id="show-filters-button" + translate="BACKLOG.FILTERS.HIDE" + ) a.trans-button( ng-if="userstories.length" href="" diff --git a/app/partials/backlog/filter-selected.jade b/app/partials/backlog/filter-selected.jade deleted file mode 100644 index 29e0a12e..00000000 --- a/app/partials/backlog/filter-selected.jade +++ /dev/null @@ -1,9 +0,0 @@ -<% _.each(filters, function(f) { %> -.single-filter.selected( - data-type!="<%- f.type %>" - data-id!="<%- f.id %>" -) - span.name(style!="<%- f.style %>") <%- f.name %> - a.remove-filter(href="") - tg-svg(svg-icon="icon-close") -<% }) %> diff --git a/app/partials/backlog/filters.jade b/app/partials/backlog/filters.jade deleted file mode 100644 index 1d504108..00000000 --- a/app/partials/backlog/filters.jade +++ /dev/null @@ -1,17 +0,0 @@ -<% _.each(filters, function(f) { %> -<% if (f.selected) { %> -a.single-filter.active(data-type!="<%- f.type %>", data-id!="<%- f.id %>") - span.name(style!="<%- f.style %>") - | <%- f.name %> - <% if (f.count){ %> - span.number <%- f.count %> - <% } %> -<% } else { %> -a.single-filter(data-type!="<%- f.type %>", data-id!="<%- f.id %>") - span.name(style!="<%- f.style %>") - | <%- f.name %> - <% if (f.count){ %> - span.number <%- f.count %> - <% } %> -<% } %> -<% }) %> \ No newline at end of file diff --git a/app/partials/includes/components/taskboard-task.jade b/app/partials/includes/components/taskboard-task.jade deleted file mode 100644 index 07808869..00000000 --- a/app/partials/includes/components/taskboard-task.jade +++ /dev/null @@ -1,18 +0,0 @@ -div.taskboard-tagline(tg-colorize-tags="task.tags", tg-colorize-tags-type="taskboard") -div.taskboard-task-inner - div.taskboard-user-avatar(tg-taskboard-user-avatar, users="usersById", task="task", project="project") - tg-svg.iocaine( - ng-if="task.is_iocaine" - svg-icon="icon-iocaine", - svg-title="{{'COMMON.IOCAINE_TEXT' | translate}}" - ) - p.taskboard-text - a.task-assigned(href="", title="{{'TASKBOARD.TITLE_ACTION_ASSIGN' | translate}}") - span.task-num(tg-bo-ref="task.ref") - a.task-name(href="", title="#{{ ::task.ref }} {{ ::task.subject }}", ng-bind="task.subject", - tg-nav="project-tasks-detail:project=project.slug,ref=task.ref") - tg-svg.edit-task( - tg-check-permission="modify_task" - svg-icon="icon-edit", - svg-title-translate="TASKBOARD.TITLE_ACTION_EDIT" - ) diff --git a/app/partials/includes/modules/backlog-filters.jade b/app/partials/includes/modules/backlog-filters.jade deleted file mode 100644 index 6198bfaa..00000000 --- a/app/partials/includes/modules/backlog-filters.jade +++ /dev/null @@ -1,36 +0,0 @@ -section.filters - div.filters-inner - h1 - span.title(translate="COMMON.FILTERS.TITLE") - - form - fieldset - input(type="text", placeholder="{{'COMMON.FILTERS.INPUT_PLACEHOLDER' | translate}}", ng-model="filtersQ") - tg-svg.search-action( - svg-icon="icon-search", - title="{{'COMMON.FILTERS.TITLE_ACTION_FILTER_BUTTON' | translate}}" - ) - - div.filters-step-cat - div.filters-applied - h2.hidden.breadcrumb - a.back( - href="" - title="{{'COMMON.FILTERS.BREADCRUMB_TITLE' | translate}}" - translate="BACKLOG.FILTERS.TITLE" - ) - tg-svg(svg-icon="icon-arrow-right") - a.subfilter(href="") - span.title(translate="COMMON.FILTERS.BREADCRUMB_STATUS") - div.filters-cats - ul - li - a(href="", title="{{'BACKLOG.FILTERS.FILTER_CATEGORY_STATUS' | translate}}", data-type="status") - span.title(translate="BACKLOG.FILTERS.FILTER_CATEGORY_STATUS") - tg-svg(svg-icon="icon-arrow-right") - li - a(href="", title="{{'BACKLOG.FILTERS.FILTER_CATEGORY_TAGS' | translate}}", data-type="tags") - span.title(translate="BACKLOG.FILTERS.FILTER_CATEGORY_TAGS") - tg-svg(svg-icon="icon-arrow-right") - - div.filter-list.hidden diff --git a/app/partials/includes/modules/issues-filters.jade b/app/partials/includes/modules/issues-filters.jade deleted file mode 100644 index 1dd12fa8..00000000 --- a/app/partials/includes/modules/issues-filters.jade +++ /dev/null @@ -1,84 +0,0 @@ -section.filters - div.filters-inner - h1 - span.title(translate="ISSUES.FILTERS.TITLE") - form - fieldset - input(type="text", placeholder="{{'ISSUES.FILTERS.INPUT_SEARCH_PLACEHOLDER' | translate}}", - ng-model="filtersQ") - tg-svg.search-action(svg-icon="icon-search", title="{{'ISSUES.FILTERS.TITLE_ACTION_SEARCH' | translate}}") - div.filters-step-cat - div.filters-applied - a.hide.button.button-gray.save-filters(href="", title="{{'COMMON.SAVE' | translate}}", ng-class="{hide: filters.length}", translate="ISSUES.FILTERS.ACTION_SAVE_CUSTOM_FILTER") - h2.hidden.breadcrumb - a.back(href="", title="{{'ISSUES.FILTERS.TITLE_BREADCRUMB' | translate}}", translate="ISSUES.FILTERS.BREADCRUMB") - tg-svg(svg-icon="icon-arrow-right") - a.subfilter(href="", title="cat-name") - span.title(translate="COMMON.FILTERS.BREADCRUMB_STATUS") - div.filters-cats - ul - li - a.filters-cat-single( - href="" - title="{{ 'ISSUES.FILTERS.CATEGORIES.TYPE' | translate}}" - data-type="types" - ) - span.title(translate="ISSUES.FILTERS.CATEGORIES.TYPE") - tg-svg(svg-icon="icon-arrow-right") - li - a.filters-cat-single( - href="" - title="{{ 'ISSUES.FILTERS.CATEGORIES.STATUS' | translate}}" - data-type="status" - ) - span.title(translate="ISSUES.FILTERS.CATEGORIES.STATUS") - tg-svg(svg-icon="icon-arrow-right") - li - a.filters-cat-single( - href="" - title="{{ 'ISSUES.FILTERS.CATEGORIES.SEVERITY' | translate}}" - data-type="severities" - ) - span.title(translate="ISSUES.FILTERS.CATEGORIES.SEVERITY") - tg-svg(svg-icon="icon-arrow-right") - li - a.filters-cat-single( - href="" - title="{{ 'ISSUES.FILTERS.CATEGORIES.PRIORITIES' | translate}}" - data-type="priorities" - ) - span.title(translate="ISSUES.FILTERS.CATEGORIES.PRIORITIES") - tg-svg(svg-icon="icon-arrow-right") - li - a.filters-cat-single( - href="" - title="{{ 'ISSUES.FILTERS.CATEGORIES.TAGS' | translate}}" - data-type="tags" - ) - span.title(translate="ISSUES.FILTERS.CATEGORIES.TAGS") - tg-svg(svg-icon="icon-arrow-right") - li - a.filters-cat-single(href="" - title="{{ 'ISSUES.FILTERS.CATEGORIES.ASSIGNED_TO' | translate}}" - data-type="assignedTo" - ) - span.title(translate="ISSUES.FILTERS.CATEGORIES.ASSIGNED_TO") - tg-svg(svg-icon="icon-arrow-right") - li - a.filters-cat-single( - href="" - title="{{ 'ISSUES.FILTERS.CATEGORIES.CREATED_BY' | translate}}" - data-type="createdBy" - ) - span.title(translate="ISSUES.FILTERS.CATEGORIES.CREATED_BY") - tg-svg(svg-icon="icon-arrow-right") - li.custom-filters(ng-if="filters.myFilters.length") - a.filters-cat-single( - href="" - title="{{ 'ISSUES.FILTERS.CATEGORIES.CUSTOM_FILTERS' | translate}}" - data-type="myFilters" - ) - span.title(translate="ISSUES.FILTERS.CATEGORIES.CUSTOM_FILTERS") - tg-svg(svg-icon="icon-arrow-right") - - div.filter-list.hidden diff --git a/app/partials/includes/modules/issues-table.jade b/app/partials/includes/modules/issues-table.jade index 06b0a9f2..186629c8 100644 --- a/app/partials/includes/modules/issues-table.jade +++ b/app/partials/includes/modules/issues-table.jade @@ -1,13 +1,21 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}") div.row.title - div.level-field(data-fieldname="type", translate="ISSUES.TABLE.COLUMNS.TYPE") - div.level-field(data-fieldname="severity", translate="ISSUES.TABLE.COLUMNS.SEVERITY") - div.level-field(data-fieldname="priority", translate="ISSUES.TABLE.COLUMNS.PRIORITY") - div.votes(data-fieldname="total_voters", translate="ISSUES.TABLE.COLUMNS.VOTES") - div.subject(data-fieldname="subject", translate="ISSUES.TABLE.COLUMNS.SUBJECT") - div.issue-field(data-fieldname="status", translate="ISSUES.TABLE.COLUMNS.STATUS") - div.created-field(data-fieldname="created_date", translate="ISSUES.TABLE.COLUMNS.CREATED") - div.assigned-field(data-fieldname="assigned_to", translate="ISSUES.TABLE.COLUMNS.ASSIGNED_TO") + div.level-field(data-fieldname="type") + | {{"ISSUES.TABLE.COLUMNS.TYPE" | translate}} + div.level-field(data-fieldname="severity") + | {{"ISSUES.TABLE.COLUMNS.SEVERITY" | translate}} + div.level-field(data-fieldname="priority") + | {{"ISSUES.TABLE.COLUMNS.PRIORITY" | translate}} + div.votes(data-fieldname="total_voters") + | {{"ISSUES.TABLE.COLUMNS.VOTES" | translate}} + div.subject(data-fieldname="subject") + | {{"ISSUES.TABLE.COLUMNS.SUBJECT" | translate}} + div.issue-field(data-fieldname="status") + | {{"ISSUES.TABLE.COLUMNS.STATUS" | translate}} + div.created-field(data-fieldname="created_date") + | {{"ISSUES.TABLE.COLUMNS.CREATED" | translate}} + div.assigned-field(data-fieldname="assigned_to") + | {{"ISSUES.TABLE.COLUMNS.ASSIGNED_TO" | translate}} div.row.table-main( ng-repeat="issue in issues track by issue.id" diff --git a/app/partials/includes/modules/kanban-table.jade b/app/partials/includes/modules/kanban-table.jade index c904c56d..12c396c1 100644 --- a/app/partials/includes/modules/kanban-table.jade +++ b/app/partials/includes/modules/kanban-table.jade @@ -1,8 +1,13 @@ -div.kanban-table(tg-kanban-squish-column, tg-kanban-sortable) +div.kanban-table( + tg-kanban-squish-column, + tg-kanban-sortable, + ng-class="{'zoom-0': ctrl.zoomLevel == 0}" +) div.kanban-table-header div.kanban-table-inner h2.task-colum-name(ng-repeat="s in usStatusList track by s.id", - ng-style="{'border-top-color':s.color}", tg-bo-title="s.name", + ng-style="{'border-top-color':s.color}", + tg-bo-title="s.name", ng-class='{vfold:folds[s.id]}', tg-class-permission="{'readonly': '!modify_task'}") span(tg-bo-bind="s.name") @@ -21,21 +26,6 @@ div.kanban-table(tg-kanban-squish-column, tg-kanban-sortable) ng-class='{hidden:!folds[s.id]}' ) tg-svg(svg-icon="icon-unfold-column") - a.option( - href="" - title="{{'KANBAN.TITLE_ACTION_FOLD_CARDS' | translate}}" - ng-class="{hidden:statusViewModes[s.id] == 'minimized'}" - ng-click="ctrl.updateStatusViewMode(s.id, 'minimized')" - ) - tg-svg.fold-action(svg-icon="icon-fold-row") - a.option( - href="" - title="{{'KANBAN.TITLE_ACTION_UNFOLD_CARDS' | translate}}" - ng-class="{hidden:statusViewModes[s.id] == 'maximized'}" - ng-click="ctrl.updateStatusViewMode(s.id, 'maximized')" - ) - tg-svg.fold-action(svg-icon="icon-unfold-row") - a.option( href="" title="{{'KANBAN.TITLE_ACTION_ADD_US' | translate}}" @@ -65,18 +55,29 @@ div.kanban-table(tg-kanban-squish-column, tg-kanban-sortable) div.kanban-table-body div.kanban-table-inner div.kanban-uses-box.task-column(ng-class='{vfold:folds[s.id]}', - ng-repeat="s in usStatusList track by s.id", + ng-repeat="s in ::usStatusList track by s.id", tg-kanban-wip-limit="s", tg-kanban-column-height-fixer, tg-bind-scope ) - div.kanban-task( - ng-repeat="us in usByStatus[s.id] track by us.id", - tg-kanban-userstory, - ng-model="us", - tg-bind-scope, - tg-class-permission="{'readonly': '!modify_task'}" - ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id), 'card-placeholder': us.isPlaceholder}" - placeholder="{{us.isPlaceholder}}" + .card-placeholder( + ng-if="ctrl.showPlaceHolder(s.id)" + ng-include="'common/components/kanban-placeholder.html'" ) + + tg-card.card.ng-animate-disabled( + tg-repeat="us in usByStatus.get(s.id.toString()) track by us.getIn(['model', 'id'])", + ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id)}" + tg-class-permission="{'readonly': '!modify_task'}" + tg-bind-scope, + on-toggle-fold="ctrl.toggleFold(id)" + on-click-edit="ctrl.editUs(id)" + on-click-assigned-to="ctrl.changeUsAssignedTo(id)" + project="project" + item="us" + zoom="ctrl.zoom" + zoom-level="ctrl.zoomLevel" + archived="ctrl.isUsInArchivedHiddenStatus(us.get('id'))" + ) + div.kanban-column-intro(ng-if="s.is_archived", tg-kanban-archived-status-intro="s") diff --git a/app/partials/includes/modules/taskboard-table.jade b/app/partials/includes/modules/taskboard-table.jade index 16eff4f9..3ad5325b 100644 --- a/app/partials/includes/modules/taskboard-table.jade +++ b/app/partials/includes/modules/taskboard-table.jade @@ -1,8 +1,18 @@ -div.taskboard-table(tg-taskboard-squish-column, tg-taskboard-sortable) +div.taskboard-table( + tg-taskboard-squish-column, + tg-taskboard-sortable, + ng-class="{'zoom-0': ctrl.zoomLevel == 0}" +) div.taskboard-table-header div.taskboard-table-inner h2.task-colum-name(translate="TASKBOARD.TABLE.COLUMN") - h2.task-colum-name(ng-repeat="s in taskStatusList track by s.id", ng-style="{'border-top-color':s.color}", ng-class="{'column-fold':statusesFolded[s.id]}", class="squish-status-{{s.id}}", tg-bo-title="s.name") + h2.task-colum-name( + ng-repeat="s in ::taskStatusList track by s.id" + ng-style="{'border-top-color':s.color}" + ng-class="{'column-fold':statusesFolded[s.id]}" + class="squish-status-{{s.id}}" + tg-bo-title="s.name" + ) span(tg-bo-bind="s.name") tg-svg.hfold.fold-action( @@ -49,20 +59,31 @@ div.taskboard-table(tg-taskboard-squish-column, tg-taskboard-sortable) span(translate="TASKBOARD.TABLE.FIELD_POINTS") include ../components/addnewtask - div.taskboard-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id", class="squish-status-{{st.id}}", ng-class="{'column-fold':statusesFolded[st.id]}", tg-bind-scope) - div.taskboard-task( - ng-repeat="task in usTasks[us.id][st.id] track by task.id" - tg-bind-scope - tg-class-permission="{'readonly': '!modify_task'}" - ng-class="{'card-placeholder': task.isPlaceholder}" + + div.taskboard-tasks-box.task-column( + ng-repeat="st in ::taskStatusList track by st.id", + class="squish-status-{{st.id}}", + ng-class="{'column-fold':statusesFolded[st.id]}", + tg-bind-scope + ) + .card-placeholder( + ng-if="ctrl.showPlaceHolder(st.id, us.id)" + ng-include="'common/components/taskboard-placeholder.html'" + ) + tg-card.card.ng-animate-disabled( + tg-repeat="task in usTasks.getIn([us.id.toString(), st.id.toString()]) track by task.get('id')" + ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id)}" + tg-class-permission="{'readonly': '!modify_task'}" + tg-bind-scope, + on-toggle-fold="ctrl.toggleFold(id)" + on-click-edit="ctrl.editTask(id)" + on-click-assigned-to="ctrl.changeTaskAssignedTo(id)" + project="project" + item="task" + zoom="ctrl.zoom" + zoom-level="ctrl.zoomLevel" + type="task" ) - div(ng-if="!task.isPlaceholder", tg-taskboard-task) - include ../components/taskboard-task - - div(ng-if="task.isPlaceholder") - - var card = 'task' - include ../../common/components/taskboard-placeholder - div.task-row(ng-init="us = null", ng-class="{'row-fold':usFolded[null]}") div.taskboard-userstory-box.task-column a.vfold( @@ -82,15 +103,29 @@ div.taskboard-table(tg-taskboard-squish-column, tg-taskboard-sortable) h3.us-title span(translate="TASKBOARD.TABLE.ROW_UNASSIGED_TASKS_TITLE") include ../components/addnewtask.jade - div.taskboard-tasks-box.task-column(ng-repeat="st in taskStatusList track by st.id", class="squish-status-{{st.id}}", ng-class="{'column-fold':statusesFolded[st.id]}", tg-bind-scope) - div.taskboard-task( - ng-repeat="task in usTasks[null][st.id] track by task.id" - tg-bind-scope - tg-class-permission="{'readonly': '!modify_task'}" - ng-class="{'card-placeholder': task.isPlaceholder}" - ) - div(ng-if="!task.isPlaceholder", tg-taskboard-task) - include ../components/taskboard-task - div(ng-if="task.isPlaceholder") - include ../../common/components/taskboard-placeholder + div.taskboard-tasks-box.task-column( + ng-repeat="st in ::taskStatusList track by st.id", + class="squish-status-{{st.id}}", + ng-class="{'column-fold':statusesFolded[st.id]}", + tg-bind-scope + ) + .card-placeholder( + ng-if="ctrl.showPlaceHolder(st.id, us.id)" + ng-include="'common/components/taskboard-placeholder.html'" + ) + + tg-card.card.ng-animate-disabled( + tg-bind-scope, + tg-repeat="task in usTasks.getIn(['null', st.id.toString()]) track by task.get('id')" + ng-class="{'kanban-task-maximized': ctrl.isMaximized(s.id), 'kanban-task-minimized': ctrl.isMinimized(s.id)}" + tg-class-permission="{'readonly': '!modify_task'}" + on-toggle-fold="ctrl.toggleFold(id)" + on-click-edit="ctrl.editTask(id)" + on-click-assigned-to="ctrl.changeTaskAssignedTo(id)" + project="project" + item="task" + zoom="ctrl.zoom" + zoom-level="ctrl.zoomLevel" + type="task" + ) diff --git a/app/partials/issue/issues-filters-selected.jade b/app/partials/issue/issues-filters-selected.jade deleted file mode 100644 index 29e0a12e..00000000 --- a/app/partials/issue/issues-filters-selected.jade +++ /dev/null @@ -1,9 +0,0 @@ -<% _.each(filters, function(f) { %> -.single-filter.selected( - data-type!="<%- f.type %>" - data-id!="<%- f.id %>" -) - span.name(style!="<%- f.style %>") <%- f.name %> - a.remove-filter(href="") - tg-svg(svg-icon="icon-close") -<% }) %> diff --git a/app/partials/issue/issues-filters.jade b/app/partials/issue/issues-filters.jade deleted file mode 100644 index 5c26bdd5..00000000 --- a/app/partials/issue/issues-filters.jade +++ /dev/null @@ -1,21 +0,0 @@ -<% _.each(filters, function(f) { %> -<% if (!f.selected) { %> -.single-filter( - data-type!="<%- f.type %>" - data-id!="<%- f.id %>" -) - span.name(style!="<%- f.style %>") <%- f.name %> - <% if (f.count){ %> - span.number <%- f.count %> - <% } %> - <% if (f.type == "myFilters"){ %> - a.remove-filter(href="") - tg-svg(svg-icon="icon-trash") - <% } %> -<% } %> -<% }) %> -span(class="new") -input.hidden.my-filter-name( - type="text" - placeholder="{{'ISSUES.PLACEHOLDER_FILTER_NAME' | translate}}" -) diff --git a/app/partials/issue/issues.jade b/app/partials/issue/issues.jade index 2e6adb2b..97df1b10 100644 --- a/app/partials/issue/issues.jade +++ b/app/partials/issue/issues.jade @@ -6,8 +6,20 @@ div.wrapper.issues.lightbox-generic-form( ng-init="section='issues'" ) tg-project-menu - sidebar.menu-secondary.extrabar.filters-bar(tg-issues-filters) - include ../includes/modules/issues-filters + sidebar.filters-bar + tg-filter( + q="ctrl.filterQ" + filters="ctrl.filters" + custom-filters="ctrl.customFilters" + selected-filters="ctrl.selectedFilters" + customFilters="ctl.customFilters" + on-save-custom-filter="ctrl.saveCustomFilter(name)" + on-add-filter="ctrl.addFilter(filter)" + on-select-custom-filter="ctrl.selectCustomFilter(filter)" + on-remove-custom-filter="ctrl.removeCustomFilter(filter)" + on-remove-filter="ctrl.removeFilter(filter)" + on-change-q="ctrl.changeQ(q)" + ) section.main.issues-page header diff --git a/app/partials/kanban/kanban-task.jade b/app/partials/kanban/kanban-task.jade deleted file mode 100644 index 46fed783..00000000 --- a/app/partials/kanban/kanban-task.jade +++ /dev/null @@ -1,33 +0,0 @@ -div.kanban-tagline( - tg-colorize-tags="us.tags" - tg-colorize-tags-type="kanban" - ng-hide="us.isArchived" -) -div.kanban-task-inner(ng-class="{'task-archived': us.isArchived}") - div.avatar-wrapper(tg-kanban-user-avatar="us.assigned_to", ng-model="us", ng-hide="us.isArchived") - div.task-text(ng-hide="us.isArchived") - a.task-assigned(href="", title="{{'US.ASSIGN' | translate}}") - span.task-num(tg-bo-ref="us.ref") - a.task-name(href="", title="#{{ ::us.ref }} {{ us.subject }}", ng-bind="us.subject", - tg-nav="project-userstories-detail:project=project.slug,ref=us.ref", - tg-nav-get-params="{\"kanban-status\": {{us.status}}}") - - p.task-points(href="", title="{{'US.TOTAL_US_POINTS' | translate}}") - span(ng-if="us.total_points !== null", ng-bind="us.total_points") - span.points-text(ng-if="us.total_points !== null", translate="COMMON.FIELDS.POINTS") - span(ng-if="us.total_points === null", translate="US.NOT_ESTIMATED") - - div.task-archived-text(ng-show="us.isArchived") - p(translate="KANBAN.ARCHIVED") - p - span.task-num(tg-bo-ref="us.ref") - span.task-name(ng-bind="us.subject") - p(translate="KANBAN.UNDO_ARCHIVED") - - a.edit-us( - href="", - title="{{'COMMON.EDIT' | translate}}", - tg-check-permission="modify_us", - ng-hide="us.isArchived" - ) - tg-svg(svg-icon="icon-edit") diff --git a/app/partials/kanban/kanban.jade b/app/partials/kanban/kanban.jade index 99c05aa1..885fb31e 100644 --- a/app/partials/kanban/kanban.jade +++ b/app/partials/kanban/kanban.jade @@ -5,7 +5,35 @@ div.wrapper(tg-kanban, ng-controller="KanbanController as ctrl" tg-project-menu section.main.kanban - include ../includes/components/mainTitle + tg-filter( + ng-show="ctrl.openFilter" + q="ctrl.filterQ" + filters="ctrl.filters" + custom-filters="ctrl.customFilters" + selected-filters="ctrl.selectedFilters" + customFilters="ctl.customFilters" + on-save-custom-filter="ctrl.saveCustomFilter(name)" + on-add-filter="ctrl.addFilter(filter)" + on-select-custom-filter="ctrl.selectCustomFilter(filter)" + on-remove-custom-filter="ctrl.removeCustomFilter(filter)" + on-remove-filter="ctrl.removeFilter(filter)" + on-change-q="ctrl.changeQ(q)" + ) + + .kanban-header + include ../includes/components/mainTitle + .taskboard-actions + tg-kanban-board-zoom( + ng-if="usByStatus.size", + on-zoom-change="ctrl.setZoom(zoomLevel, zoom)" + ) + + button.button-filter.e2e-open-filter( + ng-class="{'button-filters-applied': !!ctrl.selectedFilters.length}" + ng-click="ctrl.openFilter = !ctrl.openFilter" + ) + tg-svg(svg-icon="icon-filters") + include ../includes/modules/kanban-table div.lightbox.lightbox-generic-form.lb-create-edit-userstory(tg-lb-create-edit-userstory) diff --git a/app/partials/taskboard/taskboard.jade b/app/partials/taskboard/taskboard.jade index 8a12aaf1..81274531 100644 --- a/app/partials/taskboard/taskboard.jade +++ b/app/partials/taskboard/taskboard.jade @@ -4,11 +4,38 @@ div.wrapper(tg-taskboard, ng-controller="TaskboardController as ctrl", ng-init="section='backlog'") tg-project-menu section.main.taskboard - .taskboard-inner + tg-filter( + ng-show="ctrl.openFilter" + q="ctrl.filterQ" + filters="ctrl.filters" + custom-filters="ctrl.customFilters" + selected-filters="ctrl.selectedFilters" + customFilters="ctl.customFilters" + on-save-custom-filter="ctrl.saveCustomFilter(name)" + on-add-filter="ctrl.addFilter(filter)" + on-select-custom-filter="ctrl.selectCustomFilter(filter)" + on-remove-custom-filter="ctrl.removeCustomFilter(filter)" + on-remove-filter="ctrl.removeFilter(filter)" + on-change-q="ctrl.changeQ(q)" + ) + .taskboard-header h1 span(tg-bo-bind="project.name", class="project-name-short") span.green(tg-bo-bind="sprint.name") span.date(tg-date-range="sprint.estimated_start,sprint.estimated_finish") + .taskboard-actions + tg-taskboard-zoom( + ng-if="usTasks.size", + on-zoom-change="ctrl.setZoom(zoomLevel, zoom)" + ) + button.button-filter.e2e-open-filter( + ng-class="{'button-filters-applied': !!ctrl.selectedFilters.length}" + ng-click="ctrl.openFilter = !ctrl.openFilter" + ) + tg-svg(svg-icon="icon-filters") + + .taskboard-inner + include ../includes/components/sprint-summary div.graphics-container diff --git a/app/styles/components/buttons.scss b/app/styles/components/buttons.scss index b1f34cfd..2a6d9e27 100755 --- a/app/styles/components/buttons.scss +++ b/app/styles/components/buttons.scss @@ -155,3 +155,14 @@ a.button-gray { display: inline-block; margin-top: .5rem; } + +.button-filter { + @extend %button; + background: $whitish; + margin-left: 1rem; + padding: .4rem .5rem; + &:hover { + background: $gray-light; + fill: $whitish; + } +} diff --git a/app/styles/components/filter.scss b/app/styles/components/filter.scss deleted file mode 100644 index c681c70d..00000000 --- a/app/styles/components/filter.scss +++ /dev/null @@ -1,50 +0,0 @@ -.single-filter { - @include font-type(text); - @include clearfix; - align-items: center; - background: darken($whitish, 10%); // Fallback - display: flex; - justify-content: space-between; - margin-bottom: .5rem; - opacity: .5; - padding-right: .5rem; - position: relative; - &:hover { - color: $grayer; - opacity: 1; - transition: opacity .2s linear; - } - &.selected, - &.active { - color: $grayer; - opacity: 1; - transition: opacity .2s linear; - } - .name, - .number { - padding: 8px 10px; - } - .name { - @include ellipsis(100%); - display: block; - width: 100%; - } - .number { - background: darken($whitish, 20%); // Fallback - position: absolute; - right: 0; - top: 0; - } - .remove-filter { - display: block; - svg { - fill: $gray; - transition: fill .2s linear; - } - &:hover { - svg { - fill: $red; - } - } - } -} diff --git a/app/styles/components/kanban-task.scss b/app/styles/components/kanban-task.scss deleted file mode 100644 index 12a0de21..00000000 --- a/app/styles/components/kanban-task.scss +++ /dev/null @@ -1,224 +0,0 @@ -.kanban-task { - background: $card; - border: 1px solid $card-hover; - box-shadow: none; - cursor: move; - margin: .2rem; - position: relative; - &:last-child { - margin-bottom: 0; - } - &:hover { - .edit-us { - display: block; - fill: $card-dark; - opacity: 1; - transition: color .3s linear, opacity .3s linear; - } - } - &.gu-mirror { - box-shadow: 1px 1px 15px rgba($black, .4); - opacity: 1; - transition: box-shadow .3s linear; - } - &.blocked { - background: $red; - border: 1px solid darken($red, 10%); - color: $white; - a, - span { - color: $white; - } - } - &.card-placeholder { - background: darken($whitish, 2%); - border: 3px dashed darken($whitish, 8%); - cursor: default; - } - .kanban-tagline { - border-color: $card-hover; - display: flex; - height: .6rem; - } - .kanban-tag { - border-top: .3rem solid $card-hover; - flex-basis: 0; - flex-grow: 1; - height: .6rem; - z-index: 90; - } - .kanban-task-inner { - display: flex; - padding: .5rem; - } - .avatar-wrapper { - flex-basis: 55px; - flex-grow: 0; - flex-shrink: 0; - width: 55px; - img { - width: 100%; - } - } - .avatar { - a { - @include font-size(small); - text-align: center; - } - img { - margin: 0 auto; - &:hover { - border: 2px solid $primary; - transition: border .3s linear; - } - } - } - .task-text { - @include font-size(small); - flex-grow: 1; - padding: 0 .5rem 0 .8rem; - } - .task-assigned { - color: $card-dark; - display: block; - } - .task-num { - color: $grayer; - margin-right: .3rem; - } - .task-name { - @include font-type(bold); - } - .loading { - bottom: .5rem; - position: absolute; - } - .edit-us { - display: block; - opacity: 0; - position: absolute; - svg { - @include svg-size(1.1rem); - fill: $card-hover; - } - &:hover { - cursor: pointer; - svg { - fill: darken($card-hover, 15%); - transition: color .3s linear; - } - } - } -} - - -.kanban-task-maximized { - .task-archived { - background: darken($whitish, 5%); - padding: .5rem; - text-align: left; - transition: background .3s linear; - &:hover { - background: darken($whitish, 8%); - transition: background .3s linear; - } - .task-archived-text { - flex: 1; - } - span { - color: $gray-light; - } - p { - @include font-size(small); - color: $gray-light; - margin: 0; - &:last-child { - color: $gray; - margin: .5rem 0; - text-align: center; - } - } - } - .task-name { - word-wrap: break-word; - } - .loading, - .edit-us { - bottom: .2rem; - right: .5rem; - } - .task-points { - @include font-size(small); - color: darken($card-hover, 15%); - margin: 0; - span { - display: inline-block; - &:first-child { - padding-right: .2rem; - } - } - .points-text { - text-transform: lowercase; - } - } - .kanban-tag { - border-top: .3rem solid; - } -} - -.kanban-task-minimized { - .kanban-task-inner { - padding: 0 .3rem; - } - .task-archived { - @include font-size(small); - background: darken($whitish, 5%); - padding: .3rem; - text-align: left; - .task-archived-text { - flex: 1; - } - span { - color: $gray-light; - } - .task-name { - display: inline-block; - max-width: 70%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - p { - color: $gray-light; - margin: 0; - &:last-child { - display: none; - } - } - } - .task-num { - vertical-align: top; - } - .task-name { - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 135px; - } - .task-points { - display: none; - } - .icon-edit { - bottom: .2rem; - right: 1rem; - top: 1.4rem; - } - .kanban-tag { - border-top: .2rem solid; - } - .edit-us { - bottom: .2rem; - right: .5rem; - } -} diff --git a/app/styles/components/taskboard-task.scss b/app/styles/components/taskboard-task.scss deleted file mode 100644 index 7a9e6516..00000000 --- a/app/styles/components/taskboard-task.scss +++ /dev/null @@ -1,140 +0,0 @@ -.taskboard-task { - background: $card; - border: 1px solid $card-hover; - box-shadow: none; - cursor: move; - margin: .2rem; - position: relative; - &:hover { - .icon-edit { - display: block; - fill: $card-dark; - opacity: 1; - transition: color .3s linear, opacity .3s linear; - } - } - &.gu-mirror { - box-shadow: 1px 1px 15px rgba($black, .4); - transition: box-shadow .3s linear; - } - .blocked { - background: $red; - border: 1px solid darken($red, 10%); - color: $white; - svg, - span { - color: $white; - fill: $white; - } - &:hover { - .icon-edit { - fill: currentColor; - } - } - } - &.card-placeholder { - background: darken($whitish, 2%); - border: 3px dashed darken($whitish, 8%); - cursor: default; - } - .taskboard-tagline { - border-color: $card-hover; - display: flex; - height: .6rem; - } - .taskboard-tag { - border-top: .3rem solid $card-hover; - flex-basis: 0; - flex-grow: 1; - height: .6rem; - z-index: 90; - } - .taskboard-task-inner { - display: flex; - padding: .5rem; - } - .taskboard-user-avatar { - flex-basis: 50px; - flex-grow: 1; - max-width: 55px; - a { - @include font-size(small); - display: block; - text-align: center; - } - img { - margin: 0 auto; - &:hover { - border: 2px solid $primary; - transition: border .3s linear; - } - } - } - .iocaine { - left: .2rem; - position: absolute; - top: 1rem; - img { - filter: hue-rotate(150deg) saturate(200%); - } - } - .icon-iocaine { - background: $black; - border-radius: 5px; - fill: $white; - height: 1.75rem; - padding: .25rem; - width: 1.75rem; - } - .task-assigned { - @include font-size(small); - color: $card-dark; - display: block; - &:hover { - color: $primary; - } - } - .task-num { - color: $grayer; - margin-right: .5em; - } - .task-name { - @include font-type(bold); - } - .taskboard-text { - @include font-size(small); - flex-basis: 50px; - flex-grow: 10; - padding: 0 .5rem 0 1rem; - word-wrap: break-word; - } - .icon { - transition: color .3s linear, opacity .3s linear; - } - .loading { - bottom: .5rem; - position: absolute; - } - .edit-task { - bottom: .5rem; - position: absolute; - top: auto; - } - .icon-edit { - @include svg-size(1.1rem); - cursor: pointer; - fill: $card-hover; - opacity: 0; - &:hover { - fill: $card-dark; - } - } - .icon-edit, - .loading { - right: 1rem; - } -} - -.task-drag { - @include box-shadow(); -} diff --git a/app/styles/core/base.scss b/app/styles/core/base.scss index b42e403f..7b4af1d2 100644 --- a/app/styles/core/base.scss +++ b/app/styles/core/base.scss @@ -56,21 +56,6 @@ body { min-width: 0; padding: 1rem; width: 320px; - &.filters-bar { - flex: 0 0 auto; - padding: 0; - transition: all .2s linear; - width: 0; - &.active { - padding: 2em 1em; - transition: all .2s linear; - width: 260px; - .filters-inner { - opacity: 1; - transition: all .4s ease-in; - } - } - } .search-in { margin-top: .5rem; } diff --git a/app/styles/dependencies/helpers.scss b/app/styles/dependencies/helpers.scss index ed6cb729..4ea9378f 100644 --- a/app/styles/dependencies/helpers.scss +++ b/app/styles/dependencies/helpers.scss @@ -1,2 +1,2 @@ $navbar: 40px; -$main-height: calc(100vh - 40px); +$main-height: calc(100vh - #{$navbar}); diff --git a/app/styles/layout/backlog.scss b/app/styles/layout/backlog.scss index ec8c3afb..310e5467 100644 --- a/app/styles/layout/backlog.scss +++ b/app/styles/layout/backlog.scss @@ -1,3 +1,24 @@ +.backlog-filter { + align-items: stretch; + display: flex; + opacity: 0; + overflow: hidden; + position: relative; + transition: all .2s linear; + width: 0; + tg-filter { + transform: translateX(-260px); + transition: all .2s linear; + } + &.active { + opacity: 1; + transition: all .2s linear; + width: 260px; + tg-filter { + transform: translateX(0); + } + } +} .backlog-menu { background: $mass-white; color: $blackish; diff --git a/app/styles/layout/issues.scss b/app/styles/layout/issues.scss index 1c122c2f..52e9deff 100644 --- a/app/styles/layout/issues.scss +++ b/app/styles/layout/issues.scss @@ -1,10 +1,6 @@ .issues { .filters-bar { - flex: 0 0 auto; + position: relative; width: 260px; } - .filters-inner { - opacity: 1; - padding: 1rem; - } } diff --git a/app/styles/layout/kanban.scss b/app/styles/layout/kanban.scss index ce1cb08d..11a7ea77 100644 --- a/app/styles/layout/kanban.scss +++ b/app/styles/layout/kanban.scss @@ -4,6 +4,7 @@ height: $main-height; max-height: $main-height; max-width: calc(100vw - 50px); + position: relative; header { min-height: 70px; } @@ -14,3 +15,12 @@ display: none; } } + +.kanban-header { + align-items: center; + display: flex; + justify-content: space-between; + .options { + display: flex; + } +} diff --git a/app/styles/layout/taskboard.scss b/app/styles/layout/taskboard.scss index 7c64a5d5..e213b56f 100644 --- a/app/styles/layout/taskboard.scss +++ b/app/styles/layout/taskboard.scss @@ -1,6 +1,7 @@ .taskboard { height: $main-height; overflow: hidden; + position: relative; h1, .graphics-container, .summary { @@ -11,6 +12,12 @@ } } +.taskboard-header { + align-items: center; + display: flex; + justify-content: space-between; +} + .taskboard-inner { display: flex; flex-direction: column; diff --git a/app/styles/modules/backlog/taskboard-table.scss b/app/styles/modules/backlog/taskboard-table.scss index 54509fbb..77467948 100644 --- a/app/styles/modules/backlog/taskboard-table.scss +++ b/app/styles/modules/backlog/taskboard-table.scss @@ -3,27 +3,29 @@ $column-width: 300px; $column-flex: 1; $column-shrink: 0; -$column-margin: 0 10px 0 0; +$column-margin: 0 5px 0 0; +$column-padding: .5rem 1rem; @mixin fold { - .taskboard-task { - background: none; - border: 0; - margin: 0; - min-height: 0; - .taskboard-task-inner { - padding: .1rem; - } - .taskboard-tagline, - .taskboard-text { + .card { + align-self: flex-start; + margin-top: .5rem; + tg-card-slideshow, + .card-unfold, + .card-tag, + .card-title, + .card-owner-actions, + .card-data, + .card-statistics, + .card-owner-name { display: none; } - .avatar { - height: 35px; - width: 35px; - } - .icon { - display: none; + .card-owner { + img { + height: 1.3rem; + margin-right: 0; + width: 1.3rem; + } } } &.task-column, @@ -44,25 +46,20 @@ $column-margin: 0 10px 0 0; .taskboard-table { display: flex; flex-direction: column; - height: 100%; overflow: hidden; width: 100%; - .taskboard-task { - &.readonly { - cursor: auto; - } - &.gu-mirror { - opacity: 1; - .avatar-task-link { - display: none; - } + &.zoom-0 { + .task-colum-name span { + padding-right: 1rem; } } } .taskboard-table-header { - margin-bottom: .5rem; - min-height: 40px; + flex-basis: 38px; + flex-grow: 0; + flex-shrink: 0; + min-height: 38px; position: relative; width: 100%; .taskboard-table-inner { @@ -83,7 +80,7 @@ $column-margin: 0 10px 0 0; justify-content: space-between; margin: $column-margin; max-width: $column-width; - padding: .5rem 1rem; + padding: $column-padding; position: relative; text-transform: uppercase; width: $column-width; @@ -102,6 +99,9 @@ $column-margin: 0 10px 0 0; margin: 0; } } + span { + @include ellipsis(65%); + } } tg-svg { display: block; @@ -128,7 +128,8 @@ $column-margin: 0 10px 0 0; } .taskboard-table-body { - height: 100%; + flex: 1; + margin-bottom: 5rem; overflow: auto; width: 100%; .task-column { @@ -147,14 +148,10 @@ $column-margin: 0 10px 0 0; } .column-fold { @include fold; - .taskboard-task { - max-width: 40px; - width: 40px; - } } .task-row { display: flex; - margin-bottom: .5rem; + margin-bottom: .25rem; min-height: 10rem; width: 100%; &.blocked { @@ -167,6 +164,7 @@ $column-margin: 0 10px 0 0; .points-value, .points-value:hover { color: $white; + fill: $white; transition: color .3s linear; } .taskboard-tasks-box { @@ -185,18 +183,26 @@ $column-margin: 0 10px 0 0; } } } - .taskboard-userstory-box { padding: .5rem .5rem .5rem 1.5rem; } - .avatar-task-link { - display: none; + +} + +.taskboard-userstory-box { + position: relative; + .us-title { + @include font-size(normal); + @include font-type(text); + margin-bottom: 0; + margin-right: 3rem; } - .avatar-assigned-to { - display: block; - } - .icon { - transition: fill .2s linear; + .points-value { + @include font-size(small); + color: $gray-light; + span { + margin-right: .1rem; + } } tg-svg { cursor: pointer; @@ -219,20 +225,3 @@ $column-margin: 0 10px 0 0; } } } - -.taskboard-userstory-box { - position: relative; - .us-title { - @include font-size(normal); - @include font-type(text); - margin-bottom: 0; - margin-right: 3rem; - } - .points-value { - @include font-size(small); - color: $gray-light; - span { - margin-right: .1rem; - } - } -} diff --git a/app/styles/modules/filters/filters.scss b/app/styles/modules/filters/filters.scss deleted file mode 100644 index bc938262..00000000 --- a/app/styles/modules/filters/filters.scss +++ /dev/null @@ -1,114 +0,0 @@ -.filters { - h1 { - vertical-align: baseline; - .icon { - margin: 0; - } - a { - vertical-align: baseline; - } - } - .breadcrumb { - @include font-size(large); - margin-top: 1rem; - .icon-arrow-right { - @include svg-size(.7rem); - margin: 0 .25rem; - vertical-align: middle; - } - .back { - color: $gray-light; - } - } - input { - background: $grayer; - color: $white; - @include placeholder { - color: $gray-light; - } - } - .search-action { - position: absolute; - right: .7rem; - top: .7rem; - } -} - -.filters-inner { - opacity: 0; - transition: all .1s ease-in; - .loading { - margin: 0; - padding: 8px; - text-align: center; - width: 100%; - .loading-spinner { - @include loading-spinner; - max-height: 1rem; - max-width: 1rem; - } - } -} - -.filters-applied { - margin-top: .5rem; -} - -.filters-step-cat { - .save-filters { - color: $white; - display: block; - text-align: center; - } - .my-filter-name { - background: $grayer; - color: $whitish; - width: 100%; - @include placeholder { - color: $gray-light; - } - } -} - -.filter-list { - .single-filter { - cursor: pointer; - } -} - -.filters-cats { - margin-top: 2rem; - li { - border-bottom: 1px solid $gray-light; - text-transform: uppercase; - } - .custom-filters { - .title { - color: $primary; - } - } - a { - align-items: center; - color: $grayer; - display: flex; - justify-content: space-between; - padding: .5rem 0 .5rem .5rem; - transition: color .2s ease-in; - &:hover { - color: $primary; - transition: color .2s ease-in; - .icon { - opacity: 1; - transition: opacity .2s ease-in; - } - } - } - .icon { - fill: currentColor; - float: right; - height: .9rem; - opacity: 0; - transition: opacity .2s ease-in; - width: .9rem; - } -} diff --git a/app/styles/modules/kanban/kanban-table.scss b/app/styles/modules/kanban/kanban-table.scss index 8c1930eb..2dd442ce 100644 --- a/app/styles/modules/kanban/kanban-table.scss +++ b/app/styles/modules/kanban/kanban-table.scss @@ -1,10 +1,11 @@ //Table basic shared vars -$column-width: 300px; +$column-width: 296px; $column-folded-width: 30px; $column-flex: 0; $column-shrink: 0; -$column-margin: 0 10px 0 0; +$column-margin: 0 5px 0 0; +$column-padding: .5rem 1rem; .kanban-table { display: flex; @@ -12,7 +13,19 @@ $column-margin: 0 10px 0 0; height: 100%; overflow: hidden; width: 100%; + &.zoom-0 { + .task-column, + .task-colum-name { + max-width: $column-width / 2; + } + .task-colum-name span { + padding-right: 1rem; + } + } .vfold { + tg-card { + display: none; + } &.task-colum-name { align-items: center; display: flex; @@ -36,9 +49,6 @@ $column-margin: 0 10px 0 0; min-width: $column-folded-width; width: $column-folded-width; } - .kanban-task { - display: none; - } .kanban-column-intro { display: none; } @@ -46,11 +56,11 @@ $column-margin: 0 10px 0 0; .readonly { cursor: auto; } + } .kanban-table-header { - margin-bottom: .5rem; - min-height: 40px; + min-height: 38px; position: relative; width: 100%; .kanban-table-inner { @@ -58,6 +68,9 @@ $column-margin: 0 10px 0 0; overflow: hidden; position: absolute; } + .options { + display: flex; + } .task-colum-name { @include font-size(medium); align-items: center; @@ -71,7 +84,7 @@ $column-margin: 0 10px 0 0; justify-content: space-between; margin: $column-margin; max-width: $column-width; - padding: .5rem .5rem .5rem 1rem; + padding: $column-padding; position: relative; text-transform: uppercase; &:last-child { @@ -110,6 +123,7 @@ $column-margin: 0 10px 0 0; max-width: $column-width; overflow-y: auto; widows: $column-width; + width: $column-width; &:last-child { margin-right: 0; } diff --git a/app/styles/shame/shame.scss b/app/styles/shame/shame.scss index bdd3b817..5a238d13 100644 --- a/app/styles/shame/shame.scss +++ b/app/styles/shame/shame.scss @@ -28,14 +28,8 @@ a[ng-click] svg { } // chrome url break -.kanban-task { - .task-name { +tg-card { + .card-title span:last-child { word-break: break-word; } } - -.taskboard-task { - .task-name { - word-break: break-word; - } -} \ No newline at end of file diff --git a/app/svg/sprite.svg b/app/svg/sprite.svg index 0a5d3ec6..ff8ef151 100644 --- a/app/svg/sprite.svg +++ b/app/svg/sprite.svg @@ -429,5 +429,14 @@ fill="#fff" d="M511.998 107.939c-222.856 0-404.061 181.204-404.061 404.061s181.205 404.061 404.061 404.061c222.856 0 404.061-181.203 404.061-404.061s-181.205-404.061-404.061-404.061zM511.998 158.447c88.671 0 169.621 32.484 231.616 86.222l-498.947 498.948c-53.74-61.998-86.223-142.945-86.223-231.617 0-195.561 157.992-353.553 353.553-353.553zM779.328 280.383c53.74 61.998 86.223 142.945 86.223 231.617 0 195.561-157.992 353.553-353.553 353.553-88.671 0-169.617-32.484-231.616-86.222l498.947-498.948z"> + + Add user + + + + View more + + diff --git a/e2e/helpers/filters-helper.js b/e2e/helpers/filters-helper.js new file mode 100644 index 00000000..38cac018 --- /dev/null +++ b/e2e/helpers/filters-helper.js @@ -0,0 +1,81 @@ +var utils = require('../utils'); + +var helper = module.exports; + +helper.getFilter = function() { + return $('tg-filter'); +}; + +helper.open = async function() { + let isPresent = await $('.e2e-open-filter').isPresent(); + + if(isPresent) { + $('.e2e-open-filter').click(); + } else { + return; + } + + var filter = helper.getFilter(); + + return utils.common.transitionend('.e2e-open-filter') +}; + +helper.byText = function(text) { + return $('.e2e-filter-q').sendKeys(text); +}; + +helper.clearByTextInput = function() { + return utils.common.clear($('.e2e-filter-q')); +}; + +helper.clearFilters = async function() { + let filters = $$('.e2e-remove-filter'); + let filtersSize = await filters.count() + + for(var i = 0; i < filtersSize; i++) { + filters.get(i).click(); + } + + await helper.clearByTextInput(); + let isPresent = await $('.e2e-category.selected').isPresent(); + + if(isPresent) { + $('.e2e-category.selected').click(); + } +}; + +helper.getFiltersCounters = function() { + return $$('.e2e-filter-count'); +}; + +helper.getCustomFilters = function() { + return $$('.e2e-custom-filter'); +}; + +helper.firterByLastCustomFilter = function() { + helper.openCustomFiltersCategory(); + helper.getCustomFilters().last().click(); +}; + +helper.openCustomFiltersCategory = function() { + $('.e2e-custom-filters').click(); +}; + +helper.removeLastCustomFilter = function() { + $$('.e2e-remove-custom-filter').last().click(); +} + +helper.firterByCategoryWithContent = function() { + $$('.e2e-category').first().click(); + + let filter = helper.getFiltersCounters().first().element(by.xpath('..')); + + return filter.click(); +}; + +helper.saveFilter = async function(name) { + $('.e2e-open-custom-filter-form').click(); + + await $('.e2e-filter-name-input').sendKeys(name); + await $('.e2e-filter-name-input').sendKeys(protractor.Key.ENTER); +}; diff --git a/e2e/helpers/issues-helper.js b/e2e/helpers/issues-helper.js index 2308fa26..85022ebe 100644 --- a/e2e/helpers/issues-helper.js +++ b/e2e/helpers/issues-helper.js @@ -90,57 +90,3 @@ helper.parseIssue = async function(elm) { return obj; }; - -helper.getFilterInput = function() { - return $$('sidebar[tg-issues-filters] input').get(0); -}; - -helper.filtersCats = function() { - return $$('.filters-cats li'); -}; - -helper.filtersList = function() { - return $$('.filter-list .single-filter'); -}; - -helper.selectFilter = async function(index) { - helper.filtersList().get(index).click(); -}; - -helper.saveFilter = async function(name) { - $('.filters-step-cat .save-filters').click(); - - await $('.filter-list input').sendKeys(name); - - return browser.actions().sendKeys(protractor.Key.ENTER).perform(); -}; - -helper.backToFilters = function() { - $$('.breadcrumb a').get(0).click(); -}; - -helper.removeFilters = async function() { - let count = await $$('.filters-applied .single-filter.selected').count(); - - while(count) { - $$('.single-filter.selected').get(0).$('.remove-filter').click(); - - count = await $$('.single-filter.selected').count(); - } -}; - -helper.getCustomFilters = function() { - return $$('.filter-list div[data-type="myFilters"]'); -}; - -helper.removeCustomFilters = async function() { - let count = await $$('.filter-list .remove-filter').count(); - - while(count) { - $$('.filter-list .remove-filter').get(0).click(); - - await utils.lightbox.confirm.ok(); - - count = await $$('.filter-list .remove-filter').count(); - } -}; diff --git a/e2e/helpers/kanban-helper.js b/e2e/helpers/kanban-helper.js index a40703fb..a84460ca 100644 --- a/e2e/helpers/kanban-helper.js +++ b/e2e/helpers/kanban-helper.js @@ -15,15 +15,31 @@ helper.getColumns = function() { }; helper.getColumnUssTitles = function(column) { - return helper.getColumns().$$('.task-name').getText(); + return helper.getColumns().$$('.e2e-title').getText(); }; helper.getBoxUss = function(column) { - return helper.getColumns().get(column).$$('.kanban-task'); + return helper.getColumns().get(column).$$('tg-card'); }; -helper.editUs = function(column, us) { - helper.getColumns().get(column).$$('.edit-us').get(us).click(); +helper.getUss = function() { + return $$('tg-card') +}; + +helper.editUs = async function(column, us) { + let editionZone = helper.getColumns().get(column).$$('.card-owner-actions').get(us); + + await browser + .actions() + .mouseMove(editionZone) + .perform(); + + return browser + .actions() + .mouseMove(editionZone) + .mouseMove(editionZone.$('.e2e-edit')) + .click() + .perform(); }; helper.openBulkUsLb = function(column) { @@ -59,5 +75,13 @@ helper.scrollRight = function() { }; helper.watchersLinks = function() { - return $$('.task-assigned'); + return $$('.e2e-assign'); +}; + +helper.zoom = async function(level) { + return browser + .actions() + .mouseMove($('tg-board-zoom'), {y: 14, x: level * 49}) + .click() + .perform(); }; diff --git a/e2e/helpers/taskboard-helper.js b/e2e/helpers/taskboard-helper.js index 084d4f6f..734a2d58 100644 --- a/e2e/helpers/taskboard-helper.js +++ b/e2e/helpers/taskboard-helper.js @@ -13,7 +13,11 @@ helper.getBox = function(row, column) { helper.getBoxTasks = function(row, column) { let box = helper.getBox(row, column); - return box.$$('.taskboard-task'); + return box.$$('tg-card'); +}; + +helper.getTasks = function() { + return $$('tg-card'); }; helper.openNewTaskLb = function(row) { @@ -52,8 +56,20 @@ helper.unFoldColumn = function(row) { icon.click(); }; -helper.editTask = function(row, column, task) { - helper.getBoxTasks(row, column).get(task).$('.edit-task').click(); +helper.editTask = async function(row, column, task) { + let editionZone = helper.getBoxTasks(row, column).$$('.card-owner-actions').get(task); + + await browser + .actions() + .mouseMove(editionZone) + .perform(); + + return browser + .actions() + .mouseMove(editionZone) + .mouseMove(editionZone.$('.e2e-edit')) + .click() + .perform(); }; helper.toggleGraph = function() { @@ -114,5 +130,13 @@ helper.getBulkCreateTask = function() { }; helper.watchersLinks = function() { - return $$('.task-assigned'); + return $$('.e2e-assign'); +}; + +helper.zoom = async function(level) { + return browser + .actions() + .mouseMove($('tg-board-zoom'), {y: 10, x: level * 74}) + .click() + .perform(); }; diff --git a/e2e/shared/filters.js b/e2e/shared/filters.js new file mode 100644 index 00000000..54db32c4 --- /dev/null +++ b/e2e/shared/filters.js @@ -0,0 +1,76 @@ +var filterHelper = require('../helpers/filters-helper'); +var utils = require('../utils'); + +var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); +var expect = chai.expect; + +module.exports = function(name, counter) { + before(async () => { + await filterHelper.open(); + + utils.common.takeScreenshot(name, 'filters'); + }); + + it('filter by ref', async () => { + await filterHelper.byText('xxxxyy123123123'); + + let len = await counter(); + len = await counter(); + + await filterHelper.clearFilters(); + + expect(len).to.be.equal(0); + }); + + it('filter by category', async () => { + let len = await counter(); + + await filterHelper.firterByCategoryWithContent(); + + let newLength = await counter(); + + expect(len).to.be.above(newLength); + + await filterHelper.clearFilters(); + + newLength = await counter(); + + expect(len).to.be.equal(newLength); + }); + + it('save custom filters', async () => { + let len = await counter(); + + filterHelper.openCustomFiltersCategory(); + + let customFiltersSize = await filterHelper.getCustomFilters().count(); + + await filterHelper.firterByCategoryWithContent(); + await filterHelper.saveFilter("custom-filter"); + await filterHelper.clearFilters(); + await filterHelper.firterByLastCustomFilter(); + + let newLength = await counter(); + let newCustomFiltersSize = await filterHelper.getCustomFilters().count(); + + expect(newLength).to.be.below(len); + expect(newCustomFiltersSize).to.be.equal(customFiltersSize + 1); + + await filterHelper.clearFilters(); + }); + + it('remove custom filters', async () => { + filterHelper.openCustomFiltersCategory(); + + let customFiltersSize = await filterHelper.getCustomFilters().count(); + + filterHelper.removeLastCustomFilter(); + + let newCustomFiltersSize = await filterHelper.getCustomFilters().count(); + + expect(newCustomFiltersSize).to.be.equal(customFiltersSize - 1); + }); +}; diff --git a/e2e/suites/backlog.e2e.js b/e2e/suites/backlog.e2e.js index b88b5db9..dce0a0a5 100644 --- a/e2e/suites/backlog.e2e.js +++ b/e2e/suites/backlog.e2e.js @@ -5,6 +5,8 @@ var commonHelper = require('../helpers').common; var chai = require('chai'); var chaiAsPromised = require('chai-as-promised'); +var sharedFilters = require('../shared/filters'); + chai.use(chaiAsPromised); var expect = chai.expect; @@ -243,7 +245,7 @@ describe('backlog', function() { expect(elementRef1).to.be.equal(draggedRefs[1]); }); - it.only('drag multiple us to milestone', async function() { + it('drag multiple us to milestone', async function() { let sprint = backlogHelper.sprints().get(0); let initUssSprintCount = await backlogHelper.getSprintUsertories(sprint).count(); @@ -453,143 +455,9 @@ describe('backlog', function() { }); }); - describe('filters', function() { - it('show filters', async function() { - let transition = utils.common.transitionend('.menu-secondary.filters-bar', 'opacity'); - - $('#show-filters-button').click(); - - await transition(); - - utils.common.takeScreenshot('backlog', 'backlog-filters'); - }); - - it('filter by subject', async function() { - let usCount = await backlogHelper.userStories().count(); - let filterQ = element(by.model('filtersQ')); - - let htmlChanges = await utils.common.outerHtmlChanges('.backlog-table-body'); - - await filterQ.sendKeys('add'); - - await htmlChanges(); - - let newUsCount = await backlogHelper.userStories().count(); - - expect(newUsCount).to.be.below(usCount); - - htmlChanges = await utils.common.outerHtmlChanges('.backlog-table-body'); - - // clear status - await filterQ.clear(); - - await htmlChanges(); - }); - - it('filter by ref', async function() { - let userstories = backlogHelper.userStories(); - let filterQ = element(by.model('filtersQ')); - let htmlChanges = await utils.common.outerHtmlChanges('.backlog-table-body'); - - let ref = await backlogHelper.getTestingFilterRef(); - - ref = ref.replace('#', ''); - - await filterQ.sendKeys(ref); - await htmlChanges(); - - let newUsCount = await userstories.count(); - expect(newUsCount).to.be.equal(1); - - htmlChanges = await utils.common.outerHtmlChanges('.backlog-table-body'); - - // clear status - await filterQ.clear(); - - await htmlChanges(); - }); - - it('filter by status', async function() { - let usCount = await backlogHelper.userStories().count(); - - let htmlChanges = await utils.common.outerHtmlChanges('.backlog-table-body'); - - $$('.filters-cats a').first().click(); - $$('.filter-list a').first().click(); - - await htmlChanges(); - - let newUsCount = await backlogHelper.userStories().count(); - - expect(newUsCount).to.be.below(usCount); - - //remove status - htmlChanges = await utils.common.outerHtmlChanges('.backlog-table-body'); - - $$('.filters-applied a').first().click(); - - await htmlChanges(); - - newUsCount = await backlogHelper.userStories().count(); - - expect(newUsCount).to.be.equal(usCount); - - backlogHelper.goBackFilters(); - }); - - it('filter by tags', async function() { - let usCount = await backlogHelper.userStories().count(); - let htmlChanges = await utils.common.outerHtmlChanges('.backlog-table-body'); - - $$('.filters-cats a').get(1).click(); - await browser.waitForAngular(); - - $$('.filter-list a').first().click(); - - await htmlChanges(); - - let newUsCount = await backlogHelper.userStories().count(); - - expect(newUsCount).to.be.below(usCount); - - //remove tags - htmlChanges = await utils.common.outerHtmlChanges('.backlog-table-body'); - - $$('.filters-applied a').first().click(); - - await htmlChanges(); - - newUsCount = await backlogHelper.userStories().count(); - - expect(newUsCount).to.be.equal(usCount); - }); - - it('trying drag with filters open', async function() { - let dragableElements = backlogHelper.userStories(); - let dragElement = dragableElements.get(5); - - await utils.common.drag(dragElement, dragableElements.get(0)); - - let waitErrorOpen = await utils.notifications.error.open(); - - expect(waitErrorOpen).to.be.true; - - await utils.notifications.error.close(); - }); - - it('hide filters', async function() { - let menu = $('.menu-secondary.filters-bar'); - let transition = utils.common.transitionend('.menu-secondary.filters-bar', 'width'); - - $('#show-filters-button').click(); - - await transition(); - - let waitWidth = await menu.getCssValue('width'); - - expect(waitWidth).to.be.equal('0px'); - }); - }); + describe('backlog filters', sharedFilters.bind(this, 'backlog', () => { + return backlogHelper.userStories().count(); + })); describe('closed sprints', function() { async function createEmptyMilestone() { diff --git a/e2e/suites/issues/issues.e2e.js b/e2e/suites/issues/issues.e2e.js index 809d2003..785badac 100644 --- a/e2e/suites/issues/issues.e2e.js +++ b/e2e/suites/issues/issues.e2e.js @@ -1,6 +1,7 @@ var utils = require('../../utils'); var issuesHelper = require('../../helpers').issues; var commonHelper = require('../../helpers').common; +var sharedFilters = require('../../shared/filters'); var chai = require('chai'); var chaiAsPromised = require('chai-as-promised'); @@ -126,195 +127,7 @@ describe('issues list', function() { expect(issueUserName).to.be.equal(newUserName); }); - describe('filters', function() { - it('by ref', async function() { - let table = issuesHelper.getTable(); - let issues = issuesHelper.getIssues(); - let issue = issues.get(0); - issue = await issuesHelper.parseIssue(issue); - let filterInput = issuesHelper.getFilterInput(); - - let htmlChanges = await utils.common.outerHtmlChanges(table); - await filterInput.sendKeys(issue.ref); - await htmlChanges(); - - let newIssuesCount = await issues.count(); - - expect(newIssuesCount).to.be.equal(1); - - htmlChanges = await utils.common.outerHtmlChanges(table); - await utils.common.clear(filterInput); - await htmlChanges(); - }); - - it('by subject', async function() { - let table = issuesHelper.getTable(); - let issues = issuesHelper.getIssues(); - let issue = issues.get(0); - issue = await issuesHelper.parseIssue(issue); - let filterInput = issuesHelper.getFilterInput(); - - let oldIssuesCount = await $$('.row.table-main').count(); - - let htmlChanges = await utils.common.outerHtmlChanges(table); - await filterInput.sendKeys(issue.subject); - await htmlChanges(); - - let newIssuesCount = await issues.count(); - - expect(newIssuesCount).not.to.be.equal(oldIssuesCount); - expect(newIssuesCount).to.be.above(0); - - htmlChanges = await utils.common.outerHtmlChanges(table); - await utils.common.clear(filterInput); - await htmlChanges(); - }); - - it('by type', async function() { - let table = issuesHelper.getTable(); - - let htmlChanges = await utils.common.outerHtmlChanges(table); - issuesHelper.filtersCats().get(0).$('a').click(); - issuesHelper.selectFilter(0); - - await htmlChanges(); - - issuesHelper.backToFilters(); - - await issuesHelper.removeFilters(); - }); - - it('by status', async function() { - let table = issuesHelper.getTable(); - - let htmlChanges = await utils.common.outerHtmlChanges(table); - issuesHelper.filtersCats().get(1).$('a').click(); - issuesHelper.selectFilter(0); - await htmlChanges(); - - issuesHelper.backToFilters(); - - await issuesHelper.removeFilters(); - }); - - it('by severity', async function() { - let table = issuesHelper.getTable(); - - let htmlChanges = await utils.common.outerHtmlChanges(table); - issuesHelper.filtersCats().get(2).$('a').click(); - issuesHelper.selectFilter(0); - await htmlChanges(); - - issuesHelper.backToFilters(); - - await issuesHelper.removeFilters(); - }); - - it('by priorities', async function() { - let table = issuesHelper.getTable(); - - let htmlChanges = await utils.common.outerHtmlChanges(table); - issuesHelper.filtersCats().get(3).$('a').click(); - issuesHelper.selectFilter(0); - await htmlChanges(); - - issuesHelper.backToFilters(); - - await issuesHelper.removeFilters(); - }); - - it('by tags', async function() { - let table = issuesHelper.getTable(); - - let htmlChanges = await utils.common.outerHtmlChanges(table); - issuesHelper.filtersCats().get(4).$('a').click(); - issuesHelper.selectFilter(1); - await htmlChanges(); - - issuesHelper.backToFilters(); - - await issuesHelper.removeFilters(); - }); - - it('by assigned to', async function() { - let table = issuesHelper.getTable(); - - let htmlChanges = await utils.common.outerHtmlChanges(table); - issuesHelper.filtersCats().get(5).$('a').click(); - issuesHelper.selectFilter(0); - await htmlChanges(); - - issuesHelper.backToFilters(); - - await issuesHelper.removeFilters(); - }); - - it('by created by', async function() { - let table = issuesHelper.getTable(); - - let htmlChanges = await utils.common.outerHtmlChanges(table); - issuesHelper.filtersCats().get(6).$('a').click(); - issuesHelper.selectFilter(0); - await htmlChanges(); - - issuesHelper.backToFilters(); - - await issuesHelper.removeFilters(); - }); - - it('empty', async function() { - let table = issuesHelper.getTable(); - let htmlChanges = await utils.common.outerHtmlChanges(table); - - let filterInput = issuesHelper.getFilterInput(); - - await filterInput.sendKeys(new Date().getTime()); - - await htmlChanges(); - - let newIssuesCount = await issuesHelper.getIssues().count(); - - expect(newIssuesCount).to.be.equal(0); - - await utils.common.takeScreenshot('issues', 'empty-issues'); - await utils.common.clear(filterInput); - }); - - it('save custom filter', async function() { - issuesHelper.filtersCats().get(1).$('a').click(); - issuesHelper.selectFilter(0); - - await browser.waitForAngular(); - - await issuesHelper.saveFilter('custom'); - - let customFilters = await issuesHelper.getCustomFilters().count(); - - expect(customFilters).to.be.equal(1); - - await issuesHelper.removeFilters(); - issuesHelper.backToFilters(); - }); - - it('apply custom filter', async function() { - let table = issuesHelper.getTable(); - let htmlChanges = await utils.common.outerHtmlChanges(table); - - issuesHelper.filtersCats().get(7).$('a').click(); - - issuesHelper.selectFilter(0); - - await htmlChanges(); - - await issuesHelper.removeFilters(); - }); - - it('remove custom filter', async function() { - await issuesHelper.removeCustomFilters(); - - let customFilterCount = await issuesHelper.getCustomFilters().count(); - - expect(customFilterCount).to.be.equal(0); - }); - }); + describe('issues filters', sharedFilters.bind(this, 'issues', () => { + return issuesHelper.getIssues().count(); + })); }); diff --git a/e2e/suites/kanban.e2e.js b/e2e/suites/kanban.e2e.js index 3f4fd80d..d92c80cc 100644 --- a/e2e/suites/kanban.e2e.js +++ b/e2e/suites/kanban.e2e.js @@ -2,6 +2,7 @@ var utils = require('../utils'); var kanbanHelper = require('../helpers').kanban; var backlogHelper = require('../helpers').backlog; var commonHelper = require('../helpers').common; +var filterHelper = require('../helpers/filters-helper'); var chai = require('chai'); var chaiAsPromised = require('chai-as-promised'); @@ -18,6 +19,24 @@ describe('kanban', function() { utils.common.takeScreenshot('kanban', 'kanban'); }); + it('zoom', async function() { + kanbanHelper.zoom(1); + await browser.sleep(1000); + utils.common.takeScreenshot('kanban', 'zoom1'); + + kanbanHelper.zoom(2); + await browser.sleep(1000); + utils.common.takeScreenshot('kanban', 'zoom2'); + + kanbanHelper.zoom(3); + await browser.sleep(1000); + utils.common.takeScreenshot('kanban', 'zoom3'); + + kanbanHelper.zoom(4); + await browser.sleep(1000); + utils.common.takeScreenshot('kanban', 'zoom4'); + }); + describe('create us', function() { let createUSLightbox = null; let formFields = {}; @@ -148,7 +167,6 @@ describe('kanban', function() { await utils.lightbox.close(createUSLightbox.el); let ussTitles = await kanbanHelper.getColumnUssTitles(0); - let findSubject = ussTitles.indexOf(formFields.subject) !== -1; expect(findSubject).to.be.true; @@ -297,8 +315,12 @@ describe('kanban', function() { await lightbox.waitClose(); - let usAssignedTo = await kanbanHelper.getBoxUss(0).get(0).$('.task-assigned').getText(); + let usAssignedTo = await kanbanHelper.getBoxUss(0).get(0).$('.card-owner-name').getText(); expect(assgnedToName).to.be.equal(usAssignedTo); }); + + describe('kanban filters', sharedFilters.bind(this, 'kanban', () => { + return kanbanHelper.getUss().count(); + })); }); diff --git a/e2e/suites/tasks/taskboard.e2e.js b/e2e/suites/tasks/taskboard.e2e.js index 3113b4a0..8dd56f08 100644 --- a/e2e/suites/tasks/taskboard.e2e.js +++ b/e2e/suites/tasks/taskboard.e2e.js @@ -2,6 +2,8 @@ var utils = require('../../utils'); var backlogHelper = require('../../helpers').backlog; var taskboardHelper = require('../../helpers').taskboard; var commonHelper = require('../../helpers').common; +var filterHelper = require('../../helpers/filters-helper'); +var sharedFilters = require('../../shared/filters'); var chai = require('chai'); var chaiAsPromised = require('chai-as-promised'); @@ -21,6 +23,24 @@ describe('taskboard', function() { utils.common.takeScreenshot('taskboard', 'taskboard'); }); + it('zoom', async function() { + taskboardHelper.zoom(1); + await browser.sleep(1000); + utils.common.takeScreenshot('taskboard', 'zoom1'); + + taskboardHelper.zoom(2); + await browser.sleep(1000); + utils.common.takeScreenshot('taskboard', 'zoom2'); + + taskboardHelper.zoom(3); + await browser.sleep(1000); + utils.common.takeScreenshot('taskboard', 'zoom3'); + + taskboardHelper.zoom(4); + await browser.sleep(1000); + utils.common.takeScreenshot('taskboard', 'zoom4'); + }); + describe('create task', function() { let createTaskLightbox = null; let formFields = {}; @@ -65,7 +85,7 @@ describe('taskboard', function() { let tasks = taskboardHelper.getBoxTasks(0, 0); - let tasksSubject = await $$('.task-name').getText(); + let tasksSubject = await $$('.e2e-title').getText(); let findSubject = tasksSubject.indexOf(formFields.subject) !== -1; @@ -111,7 +131,7 @@ describe('taskboard', function() { let tasks = taskboardHelper.getBoxTasks(0, 0); - let tasksSubject = await $$('.task-name').getText(); + let tasksSubject = await $$('.e2e-title').getText(); let findSubject = tasksSubject.indexOf(formFields.subject) !== 1; @@ -296,4 +316,8 @@ describe('taskboard', function() { expect(open).to.be.false; }); }); + + describe('taskboard filters', sharedFilters.bind(this, 'taskboard', () => { + return taskboardHelper.getTasks().count(); + })); }); diff --git a/gulpfile.js b/gulpfile.js index 7c5b788f..b69e5c92 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -291,7 +291,11 @@ gulp.task("css-lint-app", function() { return gulp.src(cssFiles) .pipe(gulpif(!isDeploy, cache(csslint("csslintrc.json"), { success: function(csslintFile) { - return csslintFile.csslint.success; + if (csslintFile.csslint) { + return csslintFile.csslint.success; + } else { + return false; + } }, value: function(csslintFile) { return { From 24b8cf2cef9f80178d77925330c67fecdb2576cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Thu, 28 Jul 2016 12:51:09 +0200 Subject: [PATCH 105/315] US#4445: Add milestones to main menu --- .../components/project-menu/project-menu.jade | 16 ++++++- app/styles/modules/common/nav.scss | 45 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/app/modules/components/project-menu/project-menu.jade b/app/modules/components/project-menu/project-menu.jade index ddb4e109..e1bdd2d9 100644 --- a/app/modules/components/project-menu/project-menu.jade +++ b/app/modules/components/project-menu/project-menu.jade @@ -25,7 +25,12 @@ nav.menu( tg-svg(svg-icon="icon-timeline") span.helper(translate="PROJECT.SECTION.TIMELINE") - li#nav-backlog(ng-if="vm.menu.get('backlog')") + li#nav-backlog( + ng-if="vm.menu.get('backlog')" + ng-mouseover="backlogHover = true" + ng-mouseleave="backlogHover = false" + ng-init="backlogHover = false" + ) a( tg-nav="project-backlog:project=vm.project.get('slug')" ng-class="{active: vm.active == 'backlog'}" @@ -33,7 +38,14 @@ nav.menu( tabindex="2" ) tg-svg(svg-icon="icon-scrum") - span.helper(translate="PROJECT.SECTION.BACKLOG") + + span.backlog-sprints-menu(ng-show="backlogHover") + span(translate="PROJECT.SECTION.BACKLOG") + a( + tg-repeat="sprint in vm.project.get('milestones') track by sprint.get('id')" + ng-if="!sprint.get('closed')" + tg-nav="project-taskboard:project=vm.project.get('slug'),sprint=sprint.get('slug')" + ) {{::sprint.get('name')}} li#nav-kanban(ng-if="vm.menu.get('kanban')") a( diff --git a/app/styles/modules/common/nav.scss b/app/styles/modules/common/nav.scss index 0da555f0..6aca2ffe 100644 --- a/app/styles/modules/common/nav.scss +++ b/app/styles/modules/common/nav.scss @@ -27,6 +27,9 @@ tg-project-menu { padding: 1.1rem .8rem; position: relative; } + li { + position: relative; + } a:hover { background: rgba($black, .2); transition: color .3s linear; @@ -100,3 +103,45 @@ tg-project-menu { opacity: 1; } } + +.backlog-sprints-menu { + @include font-size(small); + animation: slideLeft 200ms ease-in-out both; + background: linear-gradient(to right, rgba($black, 1) 0%, rgba($black, .8) 100%); + color: $white; + display: block; + left: 50px; + opacity: 1; + padding: .4rem 1rem; + position: absolute; + top: 1rem; + transition: all .2s; + white-space: nowrap; + z-index: 99; + a { + color: $white; + padding: .6rem .8rem; + text-align: left; + text-transform: none; + &:nth-child(2) { + padding: 1rem .8rem .6rem; + } + &:last-child { + padding: .6rem .8rem .4rem; + } + &:hover { + background: none; + } + } + &::after { + background: rgba($blackish, 1); + content: ''; + height: $label-arrow-wh; + left: calc(-#{$label-arrow-wh}/2); + position: absolute; + top: calc(1rem - #{$label-arrow-wh}/2); + transform: rotate(45deg); + width: $label-arrow-wh; + z-index: 98; + } +} From 4e4f0c1e3121455163d65ac1e25fe7b871f5f51d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 3 Aug 2016 14:53:50 +0200 Subject: [PATCH 106/315] Update changelog --- CHANGELOG.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e970fec..e39fdd43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,15 @@ - Upvote and downvote issues from the issues list. - Show points per role in statsection of the taskboard panel. - Show a funny randon animals/color for users with no avatar (like project logos). +- Filters: + - Refactor the filter module. + - Add filters in the kanban panel. + - Add filter in the sprint taskboard panel. +- Cards UI improvements: + - Add zoom levels. + - Show information according the zoom level. + - Show voters, watchers, taks and attachments. + - Improve performance. - Comments: - Add a new permissions to allow add comments instead of use the existent modify permission for this purpose. - Ability to edit comments, view edition history and redesign comments module UI. @@ -23,10 +32,6 @@ - Add Wiki history - Third party integrations: - Included gogs as builtin integration. -- Filters refactor -- Cards ui refactor with zoom -- Kanban filters -- Taskboard filters ### Misc - Lots of small and not so small bugfixes. From 18ba60e8cbba61e45481e18e87076dc2eafc961d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 3 Aug 2016 14:56:00 +0200 Subject: [PATCH 107/315] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e39fdd43..5169a577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Upvote and downvote issues from the issues list. - Show points per role in statsection of the taskboard panel. - Show a funny randon animals/color for users with no avatar (like project logos). +- Show Open Sprints in the left navigation menu (backlog submenu). - Filters: - Refactor the filter module. - Add filters in the kanban panel. From 74da21ab122489dec4d9c2e87cdf384cd20484a8 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 9 Aug 2016 13:25:26 +0200 Subject: [PATCH 108/315] update ngInfiniteScroll --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index a1977319..7cd4a904 100644 --- a/bower.json +++ b/bower.json @@ -69,7 +69,7 @@ "angular-translate-loader-partial": "~2.10.0", "angular-translate-loader-static-files": "~2.10.0", "angular-translate-interpolation-messageformat": "~2.10.0", - "ngInfiniteScroll": "1.2.2", + "ngInfiniteScroll": "1.3.0", "immutable": "~3.8.1", "bluebird": "~3.3.5", "intro.js": "~2.1.0", From 9f6d3f0d3779d51970ba09259ff530769fce73e3 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 9 Aug 2016 13:55:37 +0200 Subject: [PATCH 109/315] fix avatar binding --- app/modules/profile/profile-favs/items/ticket.jade | 2 +- app/partials/common/lightbox/lightbox-change-owner.jade | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/profile/profile-favs/items/ticket.jade b/app/modules/profile/profile-favs/items/ticket.jade index 170109d2..b5539492 100644 --- a/app/modules/profile/profile-favs/items/ticket.jade +++ b/app/modules/profile/profile-favs/items/ticket.jade @@ -6,7 +6,7 @@ title="{{ ::vm.item.getIn(['assigned_to_extra_info', 'full_name_display']) }}" ) img( - tg-avatar="{{ ::vm.item.get('assigned_to_extra_info') }}", + tg-avatar="vm.item.get('assigned_to_extra_info')", alt="{{ ::vm.item.getIn(['assigned_to_extra_info', 'full_name_display']) }}" ) diff --git a/app/partials/common/lightbox/lightbox-change-owner.jade b/app/partials/common/lightbox/lightbox-change-owner.jade index dc4f912d..b6c966b6 100644 --- a/app/partials/common/lightbox/lightbox-change-owner.jade +++ b/app/partials/common/lightbox/lightbox-change-owner.jade @@ -18,7 +18,7 @@ tg-lightbox-close href="#" title="{{'COMMON.ASSIGNED_TO.TITLE' | translate}}" ) - img(tg-avatar="{{vm.selected}}") + img(tg-avatar="vm.selected") a.user-list-name( href="" title="{{vm.selected.full_name_display}}" From 5f96ca2d4bd0e1c47071fe41613df97cbe7c50bc Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 10 Aug 2016 08:59:20 +0200 Subject: [PATCH 110/315] fix save module in safari --- app/coffee/modules/admin/project-profile.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 4be5738b..3ab3ca98 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -223,10 +223,12 @@ ProjectModulesDirective = ($repo, $confirm, $loading, projectService) -> $el.on "change", ".module-activation.module-direct-active input", (event) -> event.preventDefault() - submit() + + $scope.$applyAsync(submit) $el.on "submit", "form", (event) -> event.preventDefault() + submit() $el.on "click", ".save", (event) -> From f4c2afebef37a2696999145a256ee39dae539b95 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 10 Aug 2016 11:31:40 +0200 Subject: [PATCH 111/315] fix xss releated task --- app/partials/task/related-task-row.jade | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/partials/task/related-task-row.jade b/app/partials/task/related-task-row.jade index e51deda1..83fec325 100644 --- a/app/partials/task/related-task-row.jade +++ b/app/partials/task/related-task-row.jade @@ -1,9 +1,9 @@ .task-name a.clickable( - tg-nav="project-tasks-detail:project=project.slug,ref=task.ref" - title!="#<%- task.ref %> <%- task.subject %>") + tg-nav="project-tasks-detail:project=project.slug,ref=task.ref") span #<%- task.ref %> - span <%- task.subject %> + span(ng-non-bindable) <%- task.subject %> + .task-settings <% if(perms.modify_task) { %> a.edit-task( From 4a2ee578b486677a535ff2e05ac44722412f518a Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 10 Aug 2016 12:17:52 +0200 Subject: [PATCH 112/315] fix related task edit xss --- app/coffee/modules/related-tasks.coffee | 2 ++ app/partials/task/related-task-row-edit.jade | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/related-tasks.coffee b/app/coffee/modules/related-tasks.coffee index 7bf26155..459d800b 100644 --- a/app/coffee/modules/related-tasks.coffee +++ b/app/coffee/modules/related-tasks.coffee @@ -55,6 +55,8 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading, $tem renderEdit = (task) -> $el.html($compile(templateEdit({task: task}))($scope)) + $el.find(".task-name input").val(task.subject) + $el.on "keyup", "input", (event) -> if event.keyCode == 13 saveTask($model.$modelValue).then -> diff --git a/app/partials/task/related-task-row-edit.jade b/app/partials/task/related-task-row-edit.jade index 0eee394a..893b7b27 100644 --- a/app/partials/task/related-task-row-edit.jade +++ b/app/partials/task/related-task-row-edit.jade @@ -1,7 +1,6 @@ .task-name input( type='text' - value!='<%- task.subject %>' placeholder="{{'TASK.PLACEHOLDER_SUBJECT' | translate}}" ) .task-settings From b2860e8a5378bf777acd46fc10bb292adc6ab85e Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 11 Aug 2016 10:39:27 +0200 Subject: [PATCH 113/315] load project in the routerProvider resolve --- app/coffee/app.coffee | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 7827d1a7..ad15383f 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -57,6 +57,19 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $translate().then () -> deferred.resolve() + return deferred.promise + ], + projectLoaded: ["$q", "tgProjectService", "$route", ($q, projectService, $route) -> + deferred = $q.defer() + + projectService.setSection($route.current.$$route.section) + + if $route.current.params.pslug + projectService.setProjectBySlug($route.current.params.pslug).then(deferred.resolve) + else + projectService.cleanProject() + deferred.resolve() + return deferred.promise ] }) @@ -659,7 +672,7 @@ i18nInit = (lang, $translate) -> checksley.updateMessages('default', messages) -init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, projectService, loaderService, navigationBarService, errorHandlingService) -> +init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, loaderService, navigationBarService, errorHandlingService) -> $log.debug("Initialize application") $rootscope.$on '$translatePartialLoaderStructureChanged', () -> @@ -722,13 +735,6 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na if !$auth.isAuthenticated() $location.path($navUrls.resolve("login")) - projectService.setSection(next.section) - - if next.params.pslug - projectService.setProjectBySlug(next.params.pslug) - else - projectService.cleanProject() - if next.title or next.description title = $translate.instant(next.title or "") description = $translate.instant(next.description or "") @@ -826,7 +832,6 @@ module.run([ "$tgLocation", "$tgNavUrls", "tgAppMetaService", - "tgProjectService", "tgLoader", "tgNavigationBarService", "tgErrorHandlingService", From ca702689dcb814b60ae650c6c9380e0e4e256259 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 11 Aug 2016 14:02:29 +0200 Subject: [PATCH 114/315] fix delete attachment on creation --- app/coffee/modules/common/lightboxes.coffee | 3 ++- app/coffee/modules/taskboard/lightboxes.coffee | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index 9f4c28c3..0016ce4f 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -292,7 +292,8 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, attachmentsToAdd = attachmentsToAdd.push(attachment) $scope.deleteAttachment = (attachment) -> - attachmentsToDelete = attachmentsToDelete.push(attachment) + if attachment.get("id") + attachmentsToDelete = attachmentsToDelete.push(attachment) $scope.$on "usform:new", (ctx, projectId, status, statusList) -> form.reset() if form diff --git a/app/coffee/modules/taskboard/lightboxes.coffee b/app/coffee/modules/taskboard/lightboxes.coffee index ad052026..caf92987 100644 --- a/app/coffee/modules/taskboard/lightboxes.coffee +++ b/app/coffee/modules/taskboard/lightboxes.coffee @@ -41,7 +41,8 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer attachmentsToAdd = attachmentsToAdd.push(attachment) $scope.deleteAttachment = (attachment) -> - attachmentsToDelete = attachmentsToDelete.push(attachment) + if attachment.get("id") + attachmentsToDelete = attachmentsToDelete.push(attachment) createAttachments = (obj) -> promises = _.map attachmentsToAdd.toJS(), (attachment) -> From 6eb85a0d2b9f18da59d1c52b0b74251bb757c75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 18 Aug 2016 12:15:19 +0200 Subject: [PATCH 115/315] Fix warning message in add member lightbox --- app/locales/taiga/locale-en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index dd3166f4..84bc19e5 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -985,8 +985,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Optional) Add a personalized text to the invitation. Tell something lovely to your new members ;-)", "PLACEHOLDER_TYPE_EMAIL": "Type an Email", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", From 52513ae1e426a155ba5349c52a065274101246ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 18 Aug 2016 13:13:13 +0200 Subject: [PATCH 116/315] Change color of warning message in the add-member lightbox --- app/styles/modules/common/lightbox.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/styles/modules/common/lightbox.scss b/app/styles/modules/common/lightbox.scss index 1d64e244..9cfb907a 100644 --- a/app/styles/modules/common/lightbox.scss +++ b/app/styles/modules/common/lightbox.scss @@ -192,8 +192,8 @@ } .member-limit-warning { @include font-size(small); - background: $red-light; - color: $white; + background: $mass-white; + color: $grayer; margin: 1rem 0; padding: 1rem 2rem; text-align: center; From 89d9472ae80902f57306285842daca4fc1d512f9 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 12 Aug 2016 14:03:30 +0200 Subject: [PATCH 117/315] Fix e2e tests --- app/coffee/app.coffee | 2 +- app/coffee/modules/admin/roles.coffee | 1 + app/coffee/modules/common/components.coffee | 2 ++ app/coffee/modules/common/lightboxes.coffee | 16 ++++++--- app/coffee/modules/team/main.coffee | 2 ++ app/coffee/modules/wiki/main.coffee | 8 ++--- .../components/avatar/avatar.directive.coffee | 4 +-- app/modules/components/filter/filter.scss | 1 - .../discover-home-order-by.controller.coffee | 2 +- .../comments/comment.controller.coffee | 6 +--- .../comments/comment.controller.spec.coffee | 17 ++++------ .../history/comments/comment.directive.coffee | 2 ++ app/modules/history/comments/comment.jade | 22 ++++++------- app/modules/history/comments/comment.scss | 2 +- .../comments/comments.directive.coffee | 2 ++ app/modules/history/comments/comments.jade | 2 ++ app/modules/history/history.controller.coffee | 19 +++++++---- .../history/history.controller.spec.coffee | 10 +++--- app/modules/history/history.jade | 3 ++ app/modules/history/history/history.scss | 2 +- .../admin/memberships-row-avatar.jade | 5 +-- app/partials/backlog/backlog.jade | 4 +-- .../admin/admin-custom-attributes.jade | 2 +- .../team/team-member-current-user.jade | 2 +- app/partials/team/team-members.jade | 2 +- app/partials/wiki/wiki-nav.jade | 2 +- app/styles/components/editor-help.scss | 2 +- .../modules/backlog/taskboard-table.scss | 1 + e2e/helpers/admin-attributes-helper.js | 2 +- e2e/helpers/admin-memberships.js | 4 +-- e2e/helpers/custom-fields-helper.js | 8 ++--- e2e/helpers/detail-helper.js | 13 ++++---- e2e/helpers/filters-helper.js | 2 +- e2e/helpers/kanban-helper.js | 16 ++------- e2e/helpers/taskboard-helper.js | 2 +- e2e/helpers/wiki-helper.js | 23 ++++++++----- e2e/shared/detail.js | 9 ++--- e2e/shared/filters.js | 1 + .../admin/attributes/custom-fields.e2e.js | 33 +++++++++++-------- e2e/suites/admin/attributes/tags.e2e.js | 1 - e2e/suites/admin/members.e2e.js | 7 ++-- e2e/suites/backlog.e2e.js | 12 +++---- e2e/suites/kanban.e2e.js | 23 ++----------- e2e/suites/search.e2e.js | 2 +- e2e/suites/tasks/taskboard.e2e.js | 22 ++++++------- e2e/suites/team.e2e.js | 2 +- e2e/suites/wiki.e2e.js | 29 ++++++++-------- e2e/utils/common.js | 29 +++++++++------- e2e/utils/lightbox.js | 2 +- e2e/utils/nav.js | 4 +-- e2e/utils/notifications.js | 4 +-- 51 files changed, 201 insertions(+), 194 deletions(-) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index ad15383f..e538a865 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -503,7 +503,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven errorHandlingService.error() else if response.status == 401 and $location.url().indexOf('/login') == -1 - nextUrl = encodeURIComponent($location.url()) + nextUrl = $location.url() search = $location.search() if search.force_next diff --git a/app/coffee/modules/admin/roles.coffee b/app/coffee/modules/admin/roles.coffee index 6262fcbc..f1815b99 100644 --- a/app/coffee/modules/admin/roles.coffee +++ b/app/coffee/modules/admin/roles.coffee @@ -99,6 +99,7 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil @scope.roles = roles @scope.role = @scope.roles[0] + return roles loadInitialData: -> diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index e42ad1a4..a358dde4 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -278,6 +278,8 @@ WatchersDirective = ($rootscope, $confirm, $repo, $modelTransform, $template, $c $scope.$watch $attrs.ngModel, (item) -> return if not item? watchers = _.map(item.watchers, (watcherId) -> $scope.usersById[watcherId]) + watchers = _.filter watchers, (it) -> return !!it + renderWatchers(watchers) $scope.$on "$destroy", -> diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index 0016ce4f..f31c9d4a 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -544,7 +544,8 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic visibleUsers = _.map visibleUsers, (user) -> user.avatar = avatarService.getAvatar(user) - selected.avatar = avatarService.getAvatar(selected) if selected + if selected + selected.avatar = avatarService.getAvatar(selected) if selected ctx = { selected: selected @@ -620,7 +621,7 @@ module.directive("tgLbAssignedto", ["lightboxService", "lightboxKeyboardNavigati ## Watchers Lightbox directive ############################################################################# -WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationService, $template, $compile) -> +WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationService, $template, $compile, avatarService) -> link = ($scope, $el, $attrs) -> selectedItem = null usersTemplate = $template.get("common/lightbox/lightbox-assigned-to-users.html", true) @@ -642,9 +643,16 @@ WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationS # Render the specific list of users. render = (users) -> + visibleUsers = _.slice(users, 0, 5) + + visibleUsers = _.map visibleUsers, (user) -> + user.avatar = avatarService.getAvatar(user) + + return user + ctx = { selected: false - users: _.slice(users, 0, 5) + users: visibleUsers showMore: users.length > 5 } @@ -700,7 +708,7 @@ WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationS link:link } -module.directive("tgLbWatchers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", WatchersLightboxDirective]) +module.directive("tgLbWatchers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", "tgAvatarService", WatchersLightboxDirective]) LightboxLeaveProjectWarningDirective = (lightboxService, $template, $compile) -> diff --git a/app/coffee/modules/team/main.coffee b/app/coffee/modules/team/main.coffee index e11a4769..283e69e9 100644 --- a/app/coffee/modules/team/main.coffee +++ b/app/coffee/modules/team/main.coffee @@ -81,6 +81,8 @@ class TeamController extends mixOf(taiga.Controller, taiga.PageMixin) for member in @scope.activeUsers @scope.totals[member.id] = 0 + console.log @scope.activeUsers + # Get current user @scope.currentUser = _.find(@scope.activeUsers, {id: user?.id}) diff --git a/app/coffee/modules/wiki/main.coffee b/app/coffee/modules/wiki/main.coffee index 745d22c0..b192b09a 100644 --- a/app/coffee/modules/wiki/main.coffee +++ b/app/coffee/modules/wiki/main.coffee @@ -242,12 +242,8 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $ cancelEdition = -> return if not $model.$modelValue.id - title = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_TITLE") - message = $translate.instant("COMMON.CONFIRM_CLOSE_EDIT_MODE_MESSAGE") - $confirm.ask(title, null, message).then (askResponse) -> - $model.$modelValue.revert() - switchToReadMode() - askResponse.finish() + $model.$modelValue.revert() + switchToReadMode() getSelectedText = -> if $window.getSelection diff --git a/app/modules/components/avatar/avatar.directive.coffee b/app/modules/components/avatar/avatar.directive.coffee index a8ac3a29..8d8aa8b0 100644 --- a/app/modules/components/avatar/avatar.directive.coffee +++ b/app/modules/components/avatar/avatar.directive.coffee @@ -24,15 +24,13 @@ AvatarDirective = (avatarService) -> else attributeName = 'avatar' - unwatch = scope.$watch attributeName, (user) -> + scope.$watch attributeName, (user) -> avatar = avatarService.getAvatar(user, attributeName) el.attr('src', avatar.url) if avatar.bg el.css('background', avatar.bg) - unwatch() if user - return { link: link scope: { diff --git a/app/modules/components/filter/filter.scss b/app/modules/components/filter/filter.scss index ad2e2a7f..4283e97b 100644 --- a/app/modules/components/filter/filter.scss +++ b/app/modules/components/filter/filter.scss @@ -44,7 +44,6 @@ tg-filter { } .filter-list { - display: none; overflow-y: auto; padding: 1rem; } diff --git a/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee index ebecd4ab..a2799d09 100644 --- a/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee +++ b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee @@ -44,7 +44,7 @@ class DiscoverHomeOrderByController orderBy: (type) -> @.currentOrderBy = type @.is_open = false - + console.log "Ijsdfkldsfklj" @.onChange({orderBy: @.currentOrderBy}) angular.module("taigaDiscover").controller("DiscoverHomeOrderBy", DiscoverHomeOrderByController) diff --git a/app/modules/history/comments/comment.controller.coffee b/app/modules/history/comments/comment.controller.coffee index c137af11..531480ef 100644 --- a/app/modules/history/comments/comment.controller.coffee +++ b/app/modules/history/comments/comment.controller.coffee @@ -28,7 +28,6 @@ class CommentController constructor: (@currentUserService, @permissionService, @lightboxFactory) -> @.hiddenDeletedComment = true - @.toggleEditComment = false @.commentContent = angular.copy(@.comment) showDeletedComment: () -> @@ -37,12 +36,9 @@ class CommentController hideDeletedComment: () -> @.hiddenDeletedComment = true - toggleCommentEditor: () -> - @.toggleEditComment = !@.toggleEditComment - checkCancelComment: (event) -> if event.keyCode == 27 - @.toggleCommentEditor() + @.onEditMode({commentId: @.comment.id}) canEditDeleteComment: () -> if @currentUserService.getUser() diff --git a/app/modules/history/comments/comment.controller.spec.coffee b/app/modules/history/comments/comment.controller.spec.coffee index 9765fe1f..f56c3c27 100644 --- a/app/modules/history/comments/comment.controller.spec.coffee +++ b/app/modules/history/comments/comment.controller.spec.coffee @@ -33,7 +33,6 @@ describe "CommentController", -> mocks.tgCheckPermissionsService = { check: sinon.stub() } - provide.value "tgCheckPermissionsService", mocks.tgCheckPermissionsService _mockTgLightboxFactory = () -> @@ -62,7 +61,6 @@ describe "CommentController", -> commentsCtrl.comment = "comment" commentsCtrl.hiddenDeletedComment = true - commentsCtrl.toggleEditComment = false commentsCtrl.commentContent = commentsCtrl.comment it "show deleted Comment", () -> @@ -77,21 +75,18 @@ describe "CommentController", -> commentsCtrl.hideDeletedComment() expect(commentsCtrl.hiddenDeletedComment).to.be.true - it "toggle deleted Comment", () -> - commentsCtrl = controller "CommentCtrl" - - commentsCtrl.toggleEditComment = false - commentsCtrl.toggleCommentEditor() - expect(commentsCtrl.toggleEditComment).to.be.true - it "cancel comment on keyup", () -> commentsCtrl = controller "CommentCtrl" - commentsCtrl.toggleCommentEditor = sinon.stub() + commentsCtrl.comment = { + id: 2 + } event = { keyCode: 27 } + commentsCtrl.onEditMode = sinon.stub() commentsCtrl.checkCancelComment(event) - expect(commentsCtrl.toggleCommentEditor).have.been.called + + expect(commentsCtrl.onEditMode).have.been.called it "can Edit Comment", () -> commentsCtrl = controller "CommentCtrl" diff --git a/app/modules/history/comments/comment.directive.coffee b/app/modules/history/comments/comment.directive.coffee index cb3c2bb4..0d001c7d 100644 --- a/app/modules/history/comments/comment.directive.coffee +++ b/app/modules/history/comments/comment.directive.coffee @@ -31,6 +31,8 @@ CommentDirective = () -> editing: "<", deleting: "<", objectId: "<", + editMode: "<", + onEditMode: "&", onDeleteComment: "&", onRestoreDeletedComment: "&", onEditComment: "&" diff --git a/app/modules/history/comments/comment.jade b/app/modules/history/comments/comment.jade index d607b991..78258e57 100644 --- a/app/modules/history/comments/comment.jade +++ b/app/modules/history/comments/comment.jade @@ -22,11 +22,11 @@ include ../../../partials/common/components/wysiwyg.jade tg-svg(svg-icon="icon-bulk") .comment-container .comment-text.wysiwyg( - ng-if="!vm.toggleEditComment" + ng-if="!vm.editMode" ng-bind-html="vm.comment.comment_html" ) .comment-editor( - ng-if="vm.toggleEditComment" + ng-if="vm.editMode" ng-keyup="vm.checkCancelComment($event)" ) .edit-comment(ng-model="vm.type") @@ -38,29 +38,29 @@ include ../../../partials/common/components/wysiwyg.jade type="button" title="{{'COMMENTS.EDIT_COMMENT' | translate}}" translate="COMMENTS.EDIT_COMMENT" - ng-disabled="!vm.commentContent.comment.length || vm.editing" + ng-disabled="!vm.commentContent.comment.length || vm.editing == vm.comment.id" ng-click="vm.onEditComment({commentId: vm.comment.id, commentData: vm.commentContent.comment})" - tg-loading="vm.editing" + tg-loading="vm.editing == vm.comment.id" ) .comment-options(ng-if="::vm.canEditDeleteComment()") tg-svg.comment-option( svg-icon="icon-edit" svg-title-translate="COMMON.EDIT" - ng-click="vm.toggleCommentEditor()" - ng-if="!vm.toggleEditComment" + ng-click="vm.onEditMode({commentId: vm.comment.id})" + ng-if="!vm.editMode" ) tg-svg.comment-option( svg-icon="icon-close" svg-title-translate="COMMON.CANCEL" - ng-click="vm.toggleCommentEditor()" - ng-if="vm.toggleEditComment" + ng-click="vm.onEditMode({commentId: vm.comment.id})" + ng-if="vm.editMode" ) tg-svg.comment-option( svg-icon="icon-trash" svg-title-translate="COMMON.DELETE" ng-click="vm.onDeleteComment({commentId: vm.comment.id})" - ng-if="!vm.toggleEditComment" - tg-loading="vm.deleting" + ng-if="!vm.editMode" + tg-loading="vm.deleting == vm.comment.id" ) .deleted-comment-wrapper( @@ -95,7 +95,7 @@ include ../../../partials/common/components/wysiwyg.jade a.restore-comment( href="" ng-click="vm.onRestoreDeletedComment({commentId: vm.comment.id})" - tg-loading="vm.editing" + tg-loading="vm.editing == vm.comment.id" ) tg-svg( svg-icon="icon-reload" diff --git a/app/modules/history/comments/comment.scss b/app/modules/history/comments/comment.scss index c5a68912..5aef9e53 100644 --- a/app/modules/history/comments/comment.scss +++ b/app/modules/history/comments/comment.scss @@ -39,9 +39,9 @@ width: 100%; } .comment-avatar { - flex-basis: 50px; flex-shrink: 0; margin-right: 1.5rem; + width: 60px; } .comment-data { align-items: center; diff --git a/app/modules/history/comments/comments.directive.coffee b/app/modules/history/comments/comments.directive.coffee index cb14c234..67d04bd2 100644 --- a/app/modules/history/comments/comments.directive.coffee +++ b/app/modules/history/comments/comments.directive.coffee @@ -29,10 +29,12 @@ CommentsDirective = () -> name: "@", object: "@", comments: "<", + onEditMode: "&", onDeleteComment: "&", onRestoreDeletedComment: "&", onAddComment: "&", onEditComment: "&", + editMode: "<", loading: "<", deleting: "<", editing: "<", diff --git a/app/modules/history/comments/comments.jade b/app/modules/history/comments/comments.jade index 00d2f4f5..249863eb 100644 --- a/app/modules/history/comments/comments.jade +++ b/app/modules/history/comments/comments.jade @@ -11,6 +11,8 @@ section.comments editing="vm.editing" deleting="vm.deleting" object="{{vm.object}}" + edit-mode="vm.editMode[comment.id]" + on-edit-mode="vm.onEditMode({commentId: commentId})" on-delete-comment="vm.onDeleteComment({commentId: commentId})" on-restore-deleted-comment="vm.onRestoreDeletedComment({commentId: commentId})" on-edit-comment="vm.onEditComment({commentId: commentId, commentData: commentData})" diff --git a/app/modules/history/history.controller.coffee b/app/modules/history/history.controller.coffee index 3eff5130..c419fd64 100644 --- a/app/modules/history/history.controller.coffee +++ b/app/modules/history/history.controller.coffee @@ -27,6 +27,9 @@ class HistorySectionController ] constructor: (@rs, @repo, @storage) -> + @.editing = null + @.deleting = null + @.editMode = {} @.viewComments = true @._loadHistory() @.reverse = @storage.get("orderComments") @@ -46,6 +49,9 @@ class HistorySectionController @.activities = _.filter(activities, (item) -> Object.keys(item.values_diff).length > 0) @.activitiesNum = @.activities.length + toggleEditMode: (commentId) -> + @.editMode[commentId] = !@.editMode[commentId] + onActiveHistoryTab: (active) -> @.viewComments = active @@ -53,28 +59,29 @@ class HistorySectionController type = @.name objectId = @.id activityId = commentId - @.deleting = true + @.deleting = commentId return @rs.history.deleteComment(type, objectId, activityId).then => @._loadHistory() - @.deleting = false + @.deleting = commentId editComment: (commentId, comment) -> type = @.name objectId = @.id activityId = commentId - @.editing = true + @.editing = commentId return @rs.history.editComment(type, objectId, activityId, comment).then => @._loadHistory() - @.editing = false + @.toggleEditMode(commentId) + @.editing = null restoreDeletedComment: (commentId) -> type = @.name objectId = @.id activityId = commentId - @.editing = true + @.editing = commentId return @rs.history.undeleteComment(type, objectId, activityId).then => @._loadHistory() - @.editing = false + @.editing = null addComment: () -> type = @.type diff --git a/app/modules/history/history.controller.spec.coffee b/app/modules/history/history.controller.spec.coffee index bd77887f..6194cc5c 100644 --- a/app/modules/history/history.controller.spec.coffee +++ b/app/modules/history/history.controller.spec.coffee @@ -147,7 +147,7 @@ describe "HistorySection", -> historyCtrl.deleting = true historyCtrl.deleteComment(commentId).then () -> expect(historyCtrl._loadHistory).have.been.called - expect(historyCtrl.deleting).to.be.false + expect(historyCtrl.deleting).to.be.equal(7) it "edit comment", () -> historyCtrl = controller "HistorySection" @@ -164,10 +164,10 @@ describe "HistorySection", -> promise = mocks.tgResources.history.editComment.withArgs(type, objectId, activityId, comment).promise().resolve() - historyCtrl.editing = true + historyCtrl.editing = 7 historyCtrl.editComment(commentId, comment).then () -> expect(historyCtrl._loadHistory).has.been.called - expect(historyCtrl.editing).to.be.false + expect(historyCtrl.editing).to.be.null it "restore comment", () -> historyCtrl = controller "HistorySection" @@ -183,10 +183,10 @@ describe "HistorySection", -> promise = mocks.tgResources.history.undeleteComment.withArgs(type, objectId, activityId).promise().resolve() - historyCtrl.editing = true + historyCtrl.editing = 7 historyCtrl.restoreDeletedComment(commentId).then () -> expect(historyCtrl._loadHistory).has.been.called - expect(historyCtrl.editing).to.be.false + expect(historyCtrl.editing).to.be.null it "add comment", () -> historyCtrl = controller "HistorySection" diff --git a/app/modules/history/history.jade b/app/modules/history/history.jade index e5fb19d3..86ee999a 100644 --- a/app/modules/history/history.jade +++ b/app/modules/history/history.jade @@ -13,8 +13,11 @@ section.history comments="vm.comments" on-delete-comment="vm.deleteComment(commentId)" on-restore-deleted-comment="vm.restoreDeletedComment(commentId)" + on-edit-mode="vm.toggleEditMode(commentId)" on-add-comment="vm.addComment()" on-edit-comment="vm.editComment(commentId, commentData)" + edit-mode="vm.editMode" + object="{{vm.id}}" type="vm.type" name="{{vm.name}}" diff --git a/app/modules/history/history/history.scss b/app/modules/history/history/history.scss index 48bd6da8..6e985fee 100644 --- a/app/modules/history/history/history.scss +++ b/app/modules/history/history/history.scss @@ -4,9 +4,9 @@ display: flex; padding: 2rem 0; .activity-avatar { - flex-basis: 50px; flex-shrink: 0; margin-right: 1.5rem; + width: 60px; } .activity-data { margin-bottom: 1rem; diff --git a/app/partials/admin/memberships-row-avatar.jade b/app/partials/admin/memberships-row-avatar.jade index ccebb315..89411cb6 100644 --- a/app/partials/admin/memberships-row-avatar.jade +++ b/app/partials/admin/memberships-row-avatar.jade @@ -4,9 +4,10 @@ figure.avatar src!="<%- imgurl %>", alt!="<%- full_name %>" ) figcaption - span.name(ng-non-bindable) <%- full_name %> + div.name + span(ng-non-bindable) <%- full_name %> <% if (isOwner) { %> - tg-svg( + tg-svg.owner-badge( svg-icon="icon-badge", svg-title-translate="COMMON.OWNER" ) diff --git a/app/partials/backlog/backlog.jade b/app/partials/backlog/backlog.jade index 75ca752c..5330e45f 100644 --- a/app/partials/backlog/backlog.jade +++ b/app/partials/backlog/backlog.jade @@ -36,7 +36,7 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", div.backlog-menu div.backlog-table-options - a.trans-button.move-to-current-sprint.move-to-sprint( + a.trans-button.move-to-current-sprint.move-to-sprint.e2e-move-to-sprint( ng-if="currentSprint" href="" title="{{'BACKLOG.MOVE_US_TO_CURRENT_SPRINT' | translate}}" @@ -44,7 +44,7 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", ) tg-svg(svg-icon="icon-move") span.text(translate="BACKLOG.MOVE_US_TO_CURRENT_SPRINT") - a.trans-button.move-to-latest-sprint.move-to-sprint( + a.trans-button.move-to-latest-sprint.move-to-sprint.e2e-move-to-sprint( ng-if="!currentSprint" href="" title="{{'BACKLOG.MOVE_US_TO_LATEST_SPRINT' | translate}}" diff --git a/app/partials/includes/modules/admin/admin-custom-attributes.jade b/app/partials/includes/modules/admin/admin-custom-attributes.jade index ddbb11b0..123d39ce 100644 --- a/app/partials/includes/modules/admin/admin-custom-attributes.jade +++ b/app/partials/includes/modules/admin/admin-custom-attributes.jade @@ -22,7 +22,7 @@ section.custom-fields-table.basic-table ) form.js-form(tg-bind-scope) div.row.single-custom-field.js-view-custom-field - tg-svg(svg-icon="icon-drag") + tg-svg.e2e-drag(svg-icon="icon-drag") div.custom-name {{ attr.name }} div.custom-description {{ attr.description }} div.custom-field-type(ng-switch on="attr.type") diff --git a/app/partials/team/team-member-current-user.jade b/app/partials/team/team-member-current-user.jade index 683d79ad..8f800638 100644 --- a/app/partials/team/team-member-current-user.jade +++ b/app/partials/team/team-member-current-user.jade @@ -23,7 +23,7 @@ .member-stats( tg-team-member-stats stats="stats" - user="currentUser.user" + user="currentUser.id" issuesEnabled="issuesEnabled" tasksenabled="tasksEnabled" wikienabled="wikiEnabled" diff --git a/app/partials/team/team-members.jade b/app/partials/team/team-members.jade index 0a1fbae4..247b42be 100644 --- a/app/partials/team/team-members.jade +++ b/app/partials/team/team-members.jade @@ -19,7 +19,7 @@ .member-stats( tg-team-member-stats stats="stats" - user="user.user" + user="user.id" issuesEnabled="issuesEnabled" tasksenabled="tasksEnabled" wikienabled="wikiEnabled" diff --git a/app/partials/wiki/wiki-nav.jade b/app/partials/wiki/wiki-nav.jade index 93aa3e0d..c2b97e46 100644 --- a/app/partials/wiki/wiki-nav.jade +++ b/app/partials/wiki/wiki-nav.jade @@ -10,7 +10,7 @@ ul.wiki-link-container ) ul.sortable.wiki-link-container - li.wiki-link( + li.wiki-link.e2e-wiki-page-link( ng-repeat="link in wikiLinks" data-id!="{{ $index }}" tg-bind-scope diff --git a/app/styles/components/editor-help.scss b/app/styles/components/editor-help.scss index 86ffd625..c03689e8 100644 --- a/app/styles/components/editor-help.scss +++ b/app/styles/components/editor-help.scss @@ -3,7 +3,7 @@ display: flex; justify-content: space-between; margin-top: -.5rem; - padding: .25rem .5rem; + padding: .45rem .5rem; a { display: inline-block; } diff --git a/app/styles/modules/backlog/taskboard-table.scss b/app/styles/modules/backlog/taskboard-table.scss index 77467948..45c5abe6 100644 --- a/app/styles/modules/backlog/taskboard-table.scss +++ b/app/styles/modules/backlog/taskboard-table.scss @@ -46,6 +46,7 @@ $column-padding: .5rem 1rem; .taskboard-table { display: flex; flex-direction: column; + height: 100%; overflow: hidden; width: 100%; &.zoom-0 { diff --git a/e2e/helpers/admin-attributes-helper.js b/e2e/helpers/admin-attributes-helper.js index 7590053f..fc485b94 100644 --- a/e2e/helpers/admin-attributes-helper.js +++ b/e2e/helpers/admin-attributes-helper.js @@ -40,7 +40,7 @@ helper.getTagsSection = function(item) { return { el: section, rows: function() { - return section.$$('.table-main > div'); + return section.$$('.table-main'); } }; }; diff --git a/e2e/helpers/admin-memberships.js b/e2e/helpers/admin-memberships.js index 89cdb4d5..e22bc3f5 100644 --- a/e2e/helpers/admin-memberships.js +++ b/e2e/helpers/admin-memberships.js @@ -28,7 +28,7 @@ helper.getNewMemberLightbox = function() { el.$$('.remove-fieldset').get(index).click(); }, submit: function() { - el.$('.submit-button').click(); + return el.$('.submit-button').click(); } }; @@ -49,7 +49,7 @@ helper.getMembers = function() { helper.getOwner = function() { return helper.getMembers().filter(async (member) => { - return !!await member.$$('.icon-badge').count(); + return !!await member.$$('.owner-badge').count(); }).first(); }; diff --git a/e2e/helpers/custom-fields-helper.js b/e2e/helpers/custom-fields-helper.js index 2ac05c33..19bef257 100644 --- a/e2e/helpers/custom-fields-helper.js +++ b/e2e/helpers/custom-fields-helper.js @@ -44,10 +44,10 @@ helper.edit = async function(indexType, indexCustomField, name, desc, option) { }; helper.drag = function(indexType, indexCustomField, indexNewPosition) { - let customField = helper.getCustomFiledsByType(indexType).get(indexCustomField); - let newPosition = helper.getCustomFiledsByType(indexType).get(indexNewPosition).getLocation(); + let customField = helper.getCustomFiledsByType(indexType).get(indexCustomField).$('.e2e-drag'); + let newPosition = helper.getCustomFiledsByType(indexType).get(indexNewPosition); - return utils.common.drag(customField, newPosition, {y: 30}); + return utils.common.drag(customField, newPosition, 0, 40); }; helper.getCustomFiledsByType = function(indexType) { @@ -66,7 +66,7 @@ helper.delete = async function(indexType, indexCustomField) { }; helper.getName = function(indexType, indexCustomField) { - return helper.getCustomFiledsByType(indexType).get(indexCustomField).$('.custom-name span').getText(); + return helper.getCustomFiledsByType(indexType).get(indexCustomField).$('.js-view-custom-field .custom-name').getText(); }; helper.getDetailFields = function() { diff --git a/e2e/helpers/detail-helper.js b/e2e/helpers/detail-helper.js index d0d33f7a..7e2a4ee2 100644 --- a/e2e/helpers/detail-helper.js +++ b/e2e/helpers/detail-helper.js @@ -155,7 +155,7 @@ helper.editComment = function() { }, saveComment: async function () { - el.$('.save-comment').click() + el.$('.save-comment').click(); await browser.waitForAngular(); } } @@ -204,7 +204,7 @@ helper.history = function() { }, editLastComment: async function() { - let lastComment = el.$$(".comment-wrapper").last() + let lastComment = el.$$(".comment-wrapper").last(); browser .actions() .mouseMove(lastComment) @@ -215,7 +215,8 @@ helper.history = function() { }, deleteLastComment: async function() { - let lastComment = el.$$(".comment-wrapper").last() + let lastComment = el.$$(".comment-wrapper").last(); + browser .actions() .mouseMove(lastComment) @@ -236,7 +237,7 @@ helper.history = function() { }, enableEditModeLastComment: async function() { - let lastComment = el.$$(".comment-wrapper").last() + let lastComment = el.$$(".comment-wrapper").last(); browser .actions() .mouseMove(lastComment) @@ -323,9 +324,7 @@ helper.attachment = function() { }, upload: async function(filePath, name) { let addAttach = el.$('#add-attach'); - let countAttachments = await $$('tg-attachment').count(); - let toggleInput = function() { $('#add-attach').toggle(); }; @@ -340,8 +339,8 @@ helper.attachment = function() { return !!count; }, 5000); - await el.$('tg-attachment .editable-attachment-comment input').sendKeys(name); + await browser.sleep(500); await browser.actions().sendKeys(protractor.Key.ENTER).perform(); await browser.executeScript(toggleInput); await browser.waitForAngular(); diff --git a/e2e/helpers/filters-helper.js b/e2e/helpers/filters-helper.js index 38cac018..f317b3ea 100644 --- a/e2e/helpers/filters-helper.js +++ b/e2e/helpers/filters-helper.js @@ -17,7 +17,7 @@ helper.open = async function() { var filter = helper.getFilter(); - return utils.common.transitionend('.e2e-open-filter') + return utils.common.transitionend('tg-filter'); }; helper.byText = function(text) { diff --git a/e2e/helpers/kanban-helper.js b/e2e/helpers/kanban-helper.js index a84460ca..86ad9047 100644 --- a/e2e/helpers/kanban-helper.js +++ b/e2e/helpers/kanban-helper.js @@ -7,7 +7,7 @@ helper.getHeaderColumns = function() { }; helper.openNewUsLb = function(column) { - helper.getHeaderColumns().get(column).$$('.option').get(4).click(); + helper.getHeaderColumns().get(column).$$('.option').get(2).click(); }; helper.getColumns = function() { @@ -23,7 +23,7 @@ helper.getBoxUss = function(column) { }; helper.getUss = function() { - return $$('tg-card') + return $$('tg-card'); }; helper.editUs = async function(column, us) { @@ -58,18 +58,6 @@ helper.unFoldColumn = function(column) { columnNode.$$('.options a').get(1).click(); }; -helper.foldCards = function(column) { - let columnNode = helper.getHeaderColumns().get(column); - - columnNode.$$('.options a').get(2).click(); -}; - -helper.unFoldCards = function(column) { - let columnNode = helper.getHeaderColumns().get(column); - - columnNode.$$('.options a').get(3).click(); -}; - helper.scrollRight = function() { return browser.executeScript('$(".kanban-table-body:last").scrollLeft(10000);'); }; diff --git a/e2e/helpers/taskboard-helper.js b/e2e/helpers/taskboard-helper.js index 734a2d58..7132c4fe 100644 --- a/e2e/helpers/taskboard-helper.js +++ b/e2e/helpers/taskboard-helper.js @@ -136,7 +136,7 @@ helper.watchersLinks = function() { helper.zoom = async function(level) { return browser .actions() - .mouseMove($('tg-board-zoom'), {y: 10, x: level * 74}) + .mouseMove($('tg-board-zoom'), {y: 14, x: level * 66}) .click() .perform(); }; diff --git a/e2e/helpers/wiki-helper.js b/e2e/helpers/wiki-helper.js index e9737056..c648d0c5 100644 --- a/e2e/helpers/wiki-helper.js +++ b/e2e/helpers/wiki-helper.js @@ -3,7 +3,7 @@ var utils = require('../utils'); var helper = module.exports; helper.links = function() { - let el = $('section[tg-wiki-nav]'); + let el = $('sidebar[tg-wiki-nav]'); let obj = { el: el, @@ -13,19 +13,25 @@ helper.links = function() { el.$(".new input").sendKeys(pageTitle); browser.actions().sendKeys(protractor.Key.ENTER).perform(); await browser.waitForAngular(); - let newLink = await el.$$(".wiki-link a").last(); + let newLink = await el.$$(".e2e-wiki-page-link a").last(); return newLink; }, get: function(index) { - if(index !== null && index !== undefined) - return el.$$(".wiki-link a.link-title").get(index) - return el.$$(".wiki-link a.link-title"); + if(index !== null && index !== undefined) { + return el.$$(".e2e-wiki-page-link a.link-title").get(index); + } + + return el.$$(".e2e-wiki-page-link a.link-title"); + }, + + row: function(index) { + return el.$$(".e2e-wiki-page-link").get(index); }, getNameOf: async function(index) { - let item = await obj.get(index) - return item.getText() + let item = await obj.get(index); + return item.getText(); }, deleteLink: async function(link){ @@ -40,7 +46,8 @@ helper.links = function() { }; helper.dragAndDropLinks = async function(indexFrom, indexTo) { - let selectedLink = helper.links().get(indexFrom); + let selectedLink = helper.links().row(indexFrom).$('.dragger'); + let newPosition = helper.links().get(indexTo).getLocation(); return utils.common.drag(selectedLink, newPosition); }; diff --git a/e2e/shared/detail.js b/e2e/shared/detail.js index abfde5d2..3024a595 100644 --- a/e2e/shared/detail.js +++ b/e2e/shared/detail.js @@ -225,6 +225,7 @@ shared.historyTesting = async function(screenshotsFolder) { //Deleting last comment let deletedCommentsCounter = await historyHelper.countDeletedComments(); await historyHelper.deleteLastComment(); + let newDeletedCommentsCounter = await historyHelper.countDeletedComments(); expect(newDeletedCommentsCounter).to.be.equal(deletedCommentsCounter+1); await utils.common.takeScreenshot(screenshotsFolder, "deleted comment"); @@ -243,6 +244,8 @@ shared.historyTesting = async function(screenshotsFolder) { let title = detailHelper.title(); title.setTitle('changed'); await title.save(); + await utils.notifications.success.close(); + newCommentsCounter = await historyHelper.countComments(); expect(newCommentsCounter).to.be.equal(commentsCounter+1); @@ -253,8 +256,7 @@ shared.historyTesting = async function(screenshotsFolder) { let activitiesCounter = await historyHelper.countActivities(); - expect(newCommentsCounter).to.be.least(activitiesCounter); - + expect(newCommentsCounter).to.be.least(1); } shared.blockTesting = async function() { @@ -289,6 +291,7 @@ shared.attachmentTesting = async function() { // Uploading attachment let attachmentsLength = await attachmentHelper.countAttachments(); + var fileToUpload = commonUtil.uploadFilePath(); await attachmentHelper.upload(fileToUpload, 'This is the testing name ' + date); @@ -311,7 +314,6 @@ shared.attachmentTesting = async function() { await attachmentHelper.renameLastAttchment('This is the new testing name ' + date); name = await attachmentHelper.getLastAttachmentName(); expect(name).to.be.equal('This is the new testing name ' + date); - // Deprecating let deprecatedAttachmentsLength = await attachmentHelper.countDeprecatedAttachments(); await attachmentHelper.deprecateLastAttachment(); @@ -345,7 +347,6 @@ shared.attachmentTesting = async function() { await attachmentHelper.upload(fileToUpload, 'testing image ' + date); await attachmentHelper.upload(fileToUploadImage, 'testing image ' + date); - await browser.sleep(5000); attachmentHelper.attachmentLinks().last().click(); diff --git a/e2e/shared/filters.js b/e2e/shared/filters.js index 54db32c4..f8cbff2d 100644 --- a/e2e/shared/filters.js +++ b/e2e/shared/filters.js @@ -10,6 +10,7 @@ var expect = chai.expect; module.exports = function(name, counter) { before(async () => { await filterHelper.open(); + await browser.sleep(4000); utils.common.takeScreenshot(name, 'filters'); }); diff --git a/e2e/suites/admin/attributes/custom-fields.e2e.js b/e2e/suites/admin/attributes/custom-fields.e2e.js index a4ee80d3..0a61db8c 100644 --- a/e2e/suites/admin/attributes/custom-fields.e2e.js +++ b/e2e/suites/admin/attributes/custom-fields.e2e.js @@ -42,9 +42,11 @@ describe('custom-fields', function() { it('edit', async function() { await customFieldsHelper.edit(typeIndex, 0, 'edit', 'desc2', 1); - let notification = await utils.notifications.success.open(); + let open = await utils.notifications.success.open(); - expect(notification).to.be.true; + expect(open).to.be.true; + + await utils.notifications.success.close(); }); it('drag', async function() { @@ -52,9 +54,9 @@ describe('custom-fields', function() { await customFieldsHelper.drag(typeIndex, 0, 1); - let nameNew = awcustomFieldsHelper.getName(typeIndex, 1); + let nameNew = await customFieldsHelper.getName(typeIndex, 1); - expect(nameNew).to.be.eventually.equal(nameOld); + expect(nameNew).to.be.equal(nameOld); }); it('delete', async function() { @@ -75,15 +77,12 @@ describe('custom-fields', function() { it('create', async function() { let oldCountCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); - await customFieldsHelper.create(typeIndex, 'test1-text', 'desc1', 1); - // debounce :( await utils.notifications.success.open(); await browser.sleep(2500); await customFieldsHelper.create(typeIndex, 'test1-multi', 'desc1', 3); - // debounce :( await utils.notifications.success.open(); await browser.sleep(2500); @@ -96,7 +95,11 @@ describe('custom-fields', function() { it('edit', async function() { customFieldsHelper.edit(typeIndex, 0, 'edit', 'desc2', 2); - expect(utils.notifications.success.open()).to.be.eventually.true; + let open = await utils.notifications.success.open(); + + expect(open).to.be.true; + + await utils.notifications.success.close(); }); it('drag', async function() { @@ -104,9 +107,9 @@ describe('custom-fields', function() { await customFieldsHelper.drag(typeIndex, 0, 1); - let nameNew = customFieldsHelper.getName(typeIndex, 1); + let nameNew = await customFieldsHelper.getName(typeIndex, 1); - expect(nameNew).to.be.eventually.equal(nameOld); + expect(nameNew).to.be.equal(nameOld); }); it('delete', async function() { @@ -148,7 +151,11 @@ describe('custom-fields', function() { it('edit', async function() { customFieldsHelper.edit(typeIndex, 0, 'edit', 'desc2', 2); - expect(utils.notifications.success.open()).to.be.eventually.true; + let open = await utils.notifications.success.open(); + + expect(open).to.be.true; + + await utils.notifications.success.close(); }); it('drag', async function() { @@ -156,9 +163,9 @@ describe('custom-fields', function() { await customFieldsHelper.drag(typeIndex, 0, 1); - let nameNew = customFieldsHelper.getName(typeIndex, 1); + let nameNew = await customFieldsHelper.getName(typeIndex, 1); - expect(nameNew).to.be.eventually.equal(nameOld); + expect(nameNew).to.be.equal(nameOld); }); it('delete', async function() { diff --git a/e2e/suites/admin/attributes/tags.e2e.js b/e2e/suites/admin/attributes/tags.e2e.js index 9963997f..a4b6010e 100644 --- a/e2e/suites/admin/attributes/tags.e2e.js +++ b/e2e/suites/admin/attributes/tags.e2e.js @@ -44,7 +44,6 @@ describe('attributes - tags', function() { let tagsFilter = adminAttributesHelper.getTagsFilter(); await tagsFilter.clear(); await tagsFilter.sendKeys('ad'); - await browser.waitForAngular(); let section = adminAttributesHelper.getTagsSection(0); let rows = section.rows(); diff --git a/e2e/suites/admin/members.e2e.js b/e2e/suites/admin/members.e2e.js index 419e6a66..2cbf6904 100644 --- a/e2e/suites/admin/members.e2e.js +++ b/e2e/suites/admin/members.e2e.js @@ -8,7 +8,7 @@ var chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); var expect = chai.expect; -describe('admin - members', function() { +describe.only('admin - members', function() { before(async function(){ browser.get(browser.params.glob.host + 'project/project-0/admin/memberships'); @@ -50,7 +50,8 @@ describe('admin - members', function() { }); it('submit', async function() { - newMemberLightbox.submit(); + await browser.sleep(1000); + await newMemberLightbox.submit(); await newMemberLightbox.waitClose(); @@ -101,11 +102,9 @@ describe('admin - members', function() { utils.common.takeScreenshot('memberships', 'delete-owner-lb'); let isLeaveProjectWarningOpen = await adminMembershipsHelper.isLeaveProjectWarningOpen(); - expect(isLeaveProjectWarningOpen).to.be.equal(true); let lb = adminMembershipsHelper.leavingProjectWarningLb(); - await utils.lightbox.open(lb); utils.lightbox.exit(lb); diff --git a/e2e/suites/backlog.e2e.js b/e2e/suites/backlog.e2e.js index dce0a0a5..9df1235b 100644 --- a/e2e/suites/backlog.e2e.js +++ b/e2e/suites/backlog.e2e.js @@ -217,7 +217,7 @@ describe('backlog', function() { expect(firstElementTextRef).to.be.equal(draggedElementRef); }); - it('reorder multiple us', async function() { + it.skip('reorder multiple us', async function() { let dragableElements = backlogHelper.userStories(); let count = await dragableElements.count(); @@ -245,7 +245,7 @@ describe('backlog', function() { expect(elementRef1).to.be.equal(draggedRefs[1]); }); - it('drag multiple us to milestone', async function() { + it.skip('drag multiple us to milestone', async function() { let sprint = backlogHelper.sprints().get(0); let initUssSprintCount = await backlogHelper.getSprintUsertories(sprint).count(); @@ -285,7 +285,7 @@ describe('backlog', function() { expect(ussSprintCount).to.be.equal(initUssSprintCount + 1); }); - it('move to current sprint button', async function() { + it('move to lastest sprint button', async function() { let dragElement = backlogHelper.userStories().first(); dragElement.$('input[type="checkbox"]').click(); @@ -294,7 +294,7 @@ describe('backlog', function() { let htmlChanges = await utils.common.outerHtmlChanges('.backlog-table-body'); - $('#move-to-current-sprint').click(); + $('.e2e-move-to-sprint').click(); await htmlChanges(); @@ -538,9 +538,9 @@ describe('backlog', function() { await utils.common.drag(dragElementHandler, sprint.$('.sprint-table')); await browser.waitForAngular(); - let closedSprints = await backlogHelper.closedSprints().count(); + let closedSprints = await $('.filter-closed-sprints').isPresent(); - expect(closedSprints).to.be.equal(0); + expect(closedSprints).to.be.false; }); }); }); diff --git a/e2e/suites/kanban.e2e.js b/e2e/suites/kanban.e2e.js index d92c80cc..3ea516c4 100644 --- a/e2e/suites/kanban.e2e.js +++ b/e2e/suites/kanban.e2e.js @@ -3,6 +3,7 @@ var kanbanHelper = require('../helpers').kanban; var backlogHelper = require('../helpers').backlog; var commonHelper = require('../helpers').common; var filterHelper = require('../helpers/filters-helper'); +var sharedFilters = require('../shared/filters'); var chai = require('chai'); var chaiAsPromised = require('chai-as-promised'); @@ -223,24 +224,6 @@ describe('kanban', function() { expect(foldedColumns).to.be.equal(0); }); - - it('fold cars', async function() { - kanbanHelper.foldCards(0); - - utils.common.takeScreenshot('kanban', 'fold-cards'); - - let minimized = await $$('.kanban-task-minimized').count(); - - expect(minimized).to.be.above(1); - }); - - it('unfold cars', async function() { - kanbanHelper.unFoldCards(0); - - let minimized = await $$('.kanban-task-minimized').count(); - - expect(minimized).to.be.equal(0); - }); }); it('move us between columns', async function() { @@ -250,7 +233,7 @@ describe('kanban', function() { let usOrigin = kanbanHelper.getBoxUss(0).first(); let destination = kanbanHelper.getColumns().get(1); - await utils.common.drag(usOrigin, destination); + await utils.common.drag(usOrigin, destination, 0, 10); browser.waitForAngular(); @@ -270,7 +253,7 @@ describe('kanban', function() { await kanbanHelper.scrollRight(); - await utils.common.drag(usOrigin, destination); + await utils.common.drag(usOrigin, destination, 0, 10); browser.waitForAngular(); diff --git a/e2e/suites/search.e2e.js b/e2e/suites/search.e2e.js index 7e3b86fb..84f30372 100644 --- a/e2e/suites/search.e2e.js +++ b/e2e/suites/search.e2e.js @@ -84,7 +84,7 @@ describe('search page', function() { let searchTerm = element(by.model('searchTerm')); await searchTerm.clear(); - let text = await $$('.table-main').get(0).$('a').getText(); + let text = await $$('.table-main').get(0).$$('a').first().getText(); let htmlChanges = await utils.common.outerHtmlChanges('.search-result-table-body'); diff --git a/e2e/suites/tasks/taskboard.e2e.js b/e2e/suites/tasks/taskboard.e2e.js index 8dd56f08..3220860b 100644 --- a/e2e/suites/tasks/taskboard.e2e.js +++ b/e2e/suites/tasks/taskboard.e2e.js @@ -24,6 +24,10 @@ describe('taskboard', function() { }); it('zoom', async function() { + taskboardHelper.zoom(0); + await browser.sleep(1000); + utils.common.takeScreenshot('taskboard', 'zoom1'); + taskboardHelper.zoom(1); await browser.sleep(1000); utils.common.takeScreenshot('taskboard', 'zoom1'); @@ -35,10 +39,6 @@ describe('taskboard', function() { taskboardHelper.zoom(3); await browser.sleep(1000); utils.common.takeScreenshot('taskboard', 'zoom3'); - - taskboardHelper.zoom(4); - await browser.sleep(1000); - utils.common.takeScreenshot('taskboard', 'zoom4'); }); describe('create task', function() { @@ -241,9 +241,9 @@ describe('taskboard', function() { let taskOrigin = taskboardHelper.getBoxTasks(0, 0).first(); let destination = taskboardHelper.getBox(0, 1); - await utils.common.drag(taskOrigin, destination); + await utils.common.drag(taskOrigin, destination, 0, 10); - browser.waitForAngular(); + await browser.waitForAngular(); let originTaskCount = await taskboardHelper.getBoxTasks(0, 0).count(); let destinationTaskCount = await taskboardHelper.getBoxTasks(0, 1).count(); @@ -254,17 +254,17 @@ describe('taskboard', function() { it('move task between US\s', async function() { let initOriginTaskCount = await taskboardHelper.getBoxTasks(0, 0).count(); - let initDestinationTaskCount = await taskboardHelper.getBoxTasks(1, 1).count(); + let initDestinationTaskCount = await taskboardHelper.getBoxTasks(1, 0).count(); let taskOrigin = taskboardHelper.getBoxTasks(0, 0).first(); let destination = taskboardHelper.getBox(1, 0); - await utils.common.drag(taskOrigin, destination); + await utils.common.drag(taskOrigin, destination, 0, 10); - browser.waitForAngular(); + await browser.waitForAngular(); let originTaskCount = await taskboardHelper.getBoxTasks(0, 0).count(); - let destinationTaskCount = await taskboardHelper.getBoxTasks(1, 1).count(); + let destinationTaskCount = await taskboardHelper.getBoxTasks(1, 0).count(); expect(originTaskCount).to.be.equal(initOriginTaskCount - 1); expect(destinationTaskCount).to.be.equal(initDestinationTaskCount + 1); @@ -285,7 +285,7 @@ describe('taskboard', function() { await lightbox.waitClose(); - let usAssignedTo = await taskboardHelper.getBoxTasks(0, 0).get(0).$('.task-assigned').getText(); + let usAssignedTo = await taskboardHelper.getBoxTasks(0, 0).get(0).$('.card-owner-name').getText(); expect(assgnedToName).to.be.equal(usAssignedTo); }); diff --git a/e2e/suites/team.e2e.js b/e2e/suites/team.e2e.js index 6da84e2e..d147cac3 100644 --- a/e2e/suites/team.e2e.js +++ b/e2e/suites/team.e2e.js @@ -9,7 +9,7 @@ var expect = chai.expect; describe('leaving project', function(){ before(async function(){ - browser.get(browser.params.glob.host + 'project/project-4/team'); + browser.get(browser.params.glob.host + 'project/project-3/team'); await utils.common.waitLoader(); }); diff --git a/e2e/suites/wiki.e2e.js b/e2e/suites/wiki.e2e.js index a75a418c..4f9b6e48 100644 --- a/e2e/suites/wiki.e2e.js +++ b/e2e/suites/wiki.e2e.js @@ -20,24 +20,14 @@ describe('wiki', function() { await utils.common.takeScreenshot("wiki", "empty"); }); - it("drag & drop links", async function() { - let nameOld = await wikiHelper.links().getNameOf(0); - - await wikiHelper.dragAndDropLinks(0, 1); - - // NOTE: Thre is a strange scroll and we have to take the - // fifth element instead of the second. - let nameNew = await wikiHelper.links().getNameOf(4); - - expect(nameNew).to.be.equal(nameOld); - - }); - it('add link', async function(){ + let linkText = "Test link" + new Date().getTime(); + await wikiHelper.links().addLink(linkText); + let timestamp = new Date().getTime(); currentWiki.slug = "test-link" + timestamp; - let linkText = "Test link" + timestamp; + linkText = "Test link" + timestamp; currentWiki.link = await wikiHelper.links().addLink(linkText); }); @@ -58,6 +48,17 @@ describe('wiki', function() { expect(url).to.be.equal(browser.params.glob.host + 'project/project-0/wiki/' + currentWiki.slug); }); + utils.common.browserSkip('internet explorer', "drag & drop links", async function() { + let nameOld = await wikiHelper.links().getNameOf(0); + + await wikiHelper.dragAndDropLinks(0, 1); + + let nameNew = await wikiHelper.links().getNameOf(0); + + expect(nameNew).to.be.equal(nameOld); + + }); + it('remove link', async function() { wikiHelper.links().deleteLink(currentWiki.link); await utils.common.takeScreenshot("wiki", "deleting-the-created-link"); diff --git a/e2e/utils/common.js b/e2e/utils/common.js index 8ea6cda5..4d50932c 100644 --- a/e2e/utils/common.js +++ b/e2e/utils/common.js @@ -179,13 +179,15 @@ common.dragEnd = function(elm) { let count = await $$('.gu-mirror').count(); return count === 0; - }, 1000); + }, 5000); }; -common.drag = async function(elm, elm2) { +common.drag = async function(elm, elm2, extrax = 0, extray = 0) { var drag = ` var drag = arguments[0].origin; var dest = arguments[0].dest; + var extrax = arguments[0].extrax; + var extray = arguments[0].extray; function triggerMouseEvent (node, eventType, opts) { var event = new CustomEvent(eventType); @@ -196,42 +198,47 @@ common.drag = async function(elm, elm2) { event.clientX = opts.cords.x; event.pageY = opts.cords.y; event.clientY = opts.cords.y - window.pageYOffset; - dest.scrollIntoView(); } event.which = 1; + node.dispatchEvent(event); } + drag.scrollIntoView(); + triggerMouseEvent(drag, "mousedown"); + dest.scrollIntoView(); + triggerMouseEvent(document.documentElement, "mousemove", { cords: { - x: $(dest).offset().left, - y: $(dest).offset().top + x: $(dest).offset().left + extrax, + y: $(dest).offset().top + extray } }); triggerMouseEvent(document.documentElement, "mousemove", { cords: { - x: $(dest).offset().left, - y: $(dest).offset().top + x: $(dest).offset().left + extrax, + y: $(dest).offset().top + extray } }); triggerMouseEvent(document.documentElement, "mouseup", { cords: { - x: $(dest).offset().left, - y: $(dest).offset().top + x: $(dest).offset().left + extrax, + y: $(dest).offset().top + extray } }); `; - // return browser.executeScript(drag, elm, elm2); return browser.executeScript(drag, { origin: elm.getWebElement(), - dest: elm2.getWebElement() + dest: elm2.getWebElement(), + extrax: extrax, + extray: extray }).then(common.dragEnd); }; diff --git a/e2e/utils/lightbox.js b/e2e/utils/lightbox.js index 2070f026..a6222d99 100644 --- a/e2e/utils/lightbox.js +++ b/e2e/utils/lightbox.js @@ -26,7 +26,7 @@ lightbox.open = async function(el) { return common.hasClass(el, 'open'); }, 4000); - await browser.sleep(transition); + await browser.sleep(transition + 100); if (open) { deferred.fulfill(true); diff --git a/e2e/utils/nav.js b/e2e/utils/nav.js index 14bce981..3712f716 100644 --- a/e2e/utils/nav.js +++ b/e2e/utils/nav.js @@ -47,7 +47,7 @@ var actions = { return common.waitLoader(); }, backlog: async function() { - await common.link($('#nav-backlog a')); + await common.link($$('#nav-backlog a').first()); return common.waitLoader(); }, @@ -75,7 +75,7 @@ var actions = { return common.waitLoader(); }, task: async function(index) { - let task = $$('div[tg-taskboard-task] a.task-name').get(index); + let task = $$('tg-card .card-title a').get(index); await common.link(task); diff --git a/e2e/utils/notifications.js b/e2e/utils/notifications.js index 57f3af4e..9e14a857 100644 --- a/e2e/utils/notifications.js +++ b/e2e/utils/notifications.js @@ -11,7 +11,7 @@ notifications.success.open = function() { return browser .wait(function() { return common.hasClass(el, 'active'); - }, 6000) + }, 6000, "notification success open") .then(function(active) { return browser.sleep(transition).then(function() { return active; @@ -25,7 +25,7 @@ notifications.success.close = function() { return browser .wait(function() { return common.hasClass(el, 'inactive'); - }, 6000) + }, 6000, "notification success close") .then(function(active) { return browser.sleep(transition).then(function() { return active; From f5f55889c351277bcf459cc0d12abec8eef927ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Fri, 19 Aug 2016 10:45:25 +0200 Subject: [PATCH 118/315] Sorting correctly related tasks --- app/coffee/modules/related-tasks.coffee | 2 +- app/coffee/modules/taskboard/main.coffee | 2 -- app/coffee/modules/userstories/detail.coffee | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/related-tasks.coffee b/app/coffee/modules/related-tasks.coffee index 459d800b..0e51b881 100644 --- a/app/coffee/modules/related-tasks.coffee +++ b/app/coffee/modules/related-tasks.coffee @@ -229,7 +229,7 @@ RelatedTasksDirective = ($repo, $rs, $rootscope) -> link = ($scope, $el, $attrs) -> loadTasks = -> return $rs.tasks.list($scope.projectId, null, $scope.usId).then (tasks) => - $scope.tasks = _.sortBy(tasks, 'ref') + $scope.tasks = _.sortBy(tasks, (x) => [x.us_order, x.ref]) return tasks _isVisible = -> diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee index b66d6957..4cc01f7b 100644 --- a/app/coffee/modules/taskboard/main.coffee +++ b/app/coffee/modules/taskboard/main.coffee @@ -339,7 +339,6 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga loadTasks: -> params = { include_attachments: true, - include_tasks: true } params = _.merge params, @location.search() @@ -401,7 +400,6 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga params = { status__is_archived: false, include_attachments: true, - include_tasks: true } options = { diff --git a/app/coffee/modules/userstories/detail.coffee b/app/coffee/modules/userstories/detail.coffee index 5f80cdaf..c19d4fdb 100644 --- a/app/coffee/modules/userstories/detail.coffee +++ b/app/coffee/modules/userstories/detail.coffee @@ -331,7 +331,7 @@ UsStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTransfor $el.html(html) - $compile($el.contents())($scope); + $compile($el.contents())($scope) save = (status) => $el.find(".pop-status").popover().close() From b45d760c6a9e2fb1549eb1dc2b1eea90a2a4aaf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 7 Jun 2016 12:30:17 +0200 Subject: [PATCH 119/315] US #4302: Improve tagging system. Use generic colors for porject tags --- .gitignore | 2 +- .../modules/admin/project-profile.coffee | 21 ++ .../modules/admin/project-values.coffee | 277 ++++++++++++++++-- app/coffee/modules/common/lightboxes.coffee | 37 +++ app/coffee/modules/common/tags.coffee | 245 ++-------------- app/coffee/modules/issues/lightboxes.coffee | 38 ++- app/coffee/modules/resources/projects.coffee | 28 ++ .../modules/taskboard/lightboxes.coffee | 40 +++ app/coffee/utils.coffee | 12 +- app/locales/taiga/locale-en.json | 12 +- .../color-selector.controller.coffee | 57 ++++ .../color-selector.controller.spec.coffee | 60 ++++ .../color-selector.directive.coffee | 61 ++++ .../tags/color-selector/color-selector.jade | 30 ++ .../tags/color-selector/color-selector.scss | 68 +++++ .../tags/components/add-tag-button.jade | 11 + .../tags/components/add-tag-input.jade | 33 +++ .../components/tags/components/add-tag.scss | 59 ++++ .../tag-dropdown.directive.coffee | 84 ++++++ .../tags/tag-dropdown/tag-dropdown.jade | 12 + .../tag-line-common.controller.coffee | 56 ++++ .../tag-line-common.controller.spec.coffee | 96 ++++++ .../tag-line-common.directive.coffee | 69 +++++ .../tags/tag-line-common/tag-line-common.jade | 26 ++ .../tag-line-detail.controller.coffee | 88 ++++++ .../tag-line-detail.controller.spec.coffee | 157 ++++++++++ .../tag-line-detail.directive.coffee | 35 +++ .../tags/tag-line-detail/tag-line-detail.jade | 9 + app/modules/components/tags/tag-line.scss | 22 ++ .../components/tags/tag-line.service.coffee | 35 +++ .../components/tags/tag/tag.directive.coffee | 33 +++ app/modules/components/tags/tag/tag.jade | 8 + app/modules/components/tags/tag/tag.scss | 21 ++ .../home/projects/home-project-list.jade | 6 - app/modules/projects/project/project.jade | 11 +- app/modules/projects/projects.service.coffee | 11 +- .../projects/projects.service.spec.coffee | 6 +- app/partials/admin/admin-project-profile.jade | 13 +- .../admin/admin-project-values-tags.jade | 65 +--- app/partials/common/tag/lb-tag-line-tags.jade | 5 +- app/partials/common/tag/tags-line-tags.jade | 2 +- .../includes/components/select-color.jade | 6 +- .../includes/modules/admin/project-tags.jade | 175 +++++++++++ .../modules/lightbox-create-issue.jade | 10 +- .../modules/lightbox-task-create-edit.jade | 10 +- .../modules/lightbox-us-create-edit.jade | 10 +- app/partials/issue/issues-detail.jade | 11 +- app/partials/task/task-detail.jade | 9 +- app/partials/us/us-detail.jade | 7 +- app/styles/components/select-color.scss | 3 + app/styles/components/tag.scss | 108 ------- .../dependencies/mixins/empty-color.scss | 39 +++ app/styles/dependencies/mixins/popover.scss | 14 +- app/styles/extras/dependencies.scss | 1 + app/styles/layout/admin-project-tags.scss | 123 ++++++-- app/styles/layout/admin-project-values.scss | 2 +- app/styles/modules/common/colors-table.scss | 26 +- app/svg/sprite.svg | 8 + e2e/helpers/backlog-helper.js | 17 +- e2e/helpers/common-helper.js | 17 ++ e2e/helpers/detail-helper.js | 13 +- e2e/suites/backlog.e2e.js | 8 +- e2e/suites/issues/issues.e2e.js | 6 +- e2e/suites/kanban.e2e.js | 6 +- e2e/suites/tasks/taskboard.e2e.js | 6 +- 65 files changed, 2068 insertions(+), 528 deletions(-) create mode 100644 app/modules/components/tags/color-selector/color-selector.controller.coffee create mode 100644 app/modules/components/tags/color-selector/color-selector.controller.spec.coffee create mode 100644 app/modules/components/tags/color-selector/color-selector.directive.coffee create mode 100644 app/modules/components/tags/color-selector/color-selector.jade create mode 100644 app/modules/components/tags/color-selector/color-selector.scss create mode 100644 app/modules/components/tags/components/add-tag-button.jade create mode 100644 app/modules/components/tags/components/add-tag-input.jade create mode 100644 app/modules/components/tags/components/add-tag.scss create mode 100644 app/modules/components/tags/tag-dropdown/tag-dropdown.directive.coffee create mode 100644 app/modules/components/tags/tag-dropdown/tag-dropdown.jade create mode 100644 app/modules/components/tags/tag-line-common/tag-line-common.controller.coffee create mode 100644 app/modules/components/tags/tag-line-common/tag-line-common.controller.spec.coffee create mode 100644 app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee create mode 100644 app/modules/components/tags/tag-line-common/tag-line-common.jade create mode 100644 app/modules/components/tags/tag-line-detail/tag-line-detail.controller.coffee create mode 100644 app/modules/components/tags/tag-line-detail/tag-line-detail.controller.spec.coffee create mode 100644 app/modules/components/tags/tag-line-detail/tag-line-detail.directive.coffee create mode 100644 app/modules/components/tags/tag-line-detail/tag-line-detail.jade create mode 100644 app/modules/components/tags/tag-line.scss create mode 100644 app/modules/components/tags/tag-line.service.coffee create mode 100644 app/modules/components/tags/tag/tag.directive.coffee create mode 100644 app/modules/components/tags/tag/tag.jade create mode 100644 app/modules/components/tags/tag/tag.scss create mode 100644 app/partials/includes/modules/admin/project-tags.jade delete mode 100644 app/styles/components/tag.scss create mode 100644 app/styles/dependencies/mixins/empty-color.scss diff --git a/.gitignore b/.gitignore index e2d57d5c..b5e83639 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ app/coffee/modules/locales/locale*.coffee *.swp *.swo .#* -tags +/tags tmp/ app/config/main.coffee scss-lint.log diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 3ab3ca98..06fba778 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -62,6 +62,7 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin) @scope.project = {} promise = @.loadInitialData() + @scope.projectTags = [] promise.then => sectionName = @translate.instant( @scope.sectionName) @@ -96,6 +97,11 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin) @scope.issueTypesList = _.sortBy(project.issue_types, "order") @scope.issueStatusList = _.sortBy(project.issue_statuses, "order") @scope.$emit('project:loaded', project) + + + @scope.projectTags = _.map @scope.project.tags, (it) => + return [it, @scope.project.tags_colors[it]] + return project loadInitialData: -> @@ -107,6 +113,21 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin) openDeleteLightbox: -> @rootscope.$broadcast("deletelightbox:new", @scope.project) + addTag: (name, color) -> + tags = _.clone(@scope.project.tags) + + tags.push(name) + + @scope.projectTags.push([name, null]) + @scope.project.tags = tags + + deleteTag: (tag) -> + tags = _.clone(@scope.project.tags) + _.pull(tags, tag[0]) + _.remove @scope.projectTags, (it) => it[0] == tag[0] + + @scope.project.tags = tags + module.controller("ProjectProfileController", ProjectProfileController) diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index ba9533d1..fade389f 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -331,6 +331,10 @@ ColorSelectionDirective = () -> ## Color selection Link link = ($scope, $el, $attrs, $model) -> + $scope.allowEmpty = false + if $attrs.tgAllowEmpty + $scope.allowEmpty = true + $ctrl = $el.controller() $scope.$watch $attrs.ngModel, (element) -> @@ -696,59 +700,268 @@ module.directive("tgProjectCustomAttributes", ["$log", "$tgConfirm", "animationF ## Tags Controller ############################################################################# -class ProjectTagsController extends mixOf(taiga.Controller, taiga.PageMixin) +class ProjectTagsController extends taiga.Controller @.$inject = [ "$scope", "$rootScope", "$tgRepo", - "tgAppMetaService", - "$translate" + "$tgConfirm", + "$tgResources", + "$tgModel", ] - constructor: (@scope, @rootscope, @repo, @appMetaService, @translate) -> + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @model) -> @.loading = true - @rootscope.$on "project:loaded", => - sectionName = @translate.instant(@scope.sectionName) - title = @translate.instant("ADMIN.CUSTOM_ATTRIBUTES.PAGE_TITLE", { - "sectionName": sectionName, - "projectName": @scope.project.name - }) - description = @scope.project.description - @appMetaService.setAll(title, description) + @rootscope.$on("project:loaded", @.loadTags) + loadTags: => + return @rs.projects.tagsColors(@scope.projectId).then (tags) => + @scope.projectTagsAll = _.map(tags.getAttrs(), (color, name) => @model.make_model('tag', {name: name, color: color})) + @.filterAndSortTags() @.loading = false - @.tagNames = Object.keys(@scope.project.tags_colors).sort() - @scope.projectTags = _.map(@.tagNames, (tagName) => {name: tagName, color: @scope.project.tags_colors[tagName]}) - updateTag: (tag) -> - tags_colors = angular.copy(@scope.project.tags_colors) - tags_colors[tag.name] = tag.color - @scope.project.tags_colors = tags_colors - return @repo.save(@scope.project) + filterAndSortTags: => + @scope.projectTags = _.filter( + _.sortBy(@scope.projectTagsAll, "name"), + (tag) => tag.name.indexOf(@scope.tagsFilter.name) != -1 + ) + + deleteTag: (tag) => + return @rs.projects.deleteTag(@scope.projectId, tag) + + createTag: (tag, color) => + return @rs.projects.createTag(@scope.projectId, tag, color) + + editTag: (from_tag, to_tag, color) => + if from_tag == to_tag + to_tag = null + return @rs.projects.editTag(@scope.projectId, from_tag, to_tag, color) + + startMixingTags: (tag) => + @scope.mixingTags.toTag = tag.name + + toggleMixingFromTags: (tag) => + if tag.name != @scope.mixingTags.toTag + index = @scope.mixingTags.fromTags.indexOf(tag.name) + if index == -1 + @scope.mixingTags.fromTags.push(tag.name) + else + @scope.mixingTags.fromTags.splice(index, 1) + + confirmMixingTags: () => + toTag = @scope.mixingTags.toTag + fromTags = @scope.mixingTags.fromTags + @rs.projects.mixTags(@scope.projectId, toTag, fromTags).then => + @.cancelMixingTags() + @.loadTags() + + cancelMixingTags: () => + @scope.mixingTags.toTag = null + @scope.mixingTags.fromTags = [] + + mixingClass: (tag) => + if @scope.mixingTags.toTag != null + if tag.name == @scope.mixingTags.toTag + return "mixing-tags-to" + else if @scope.mixingTags.fromTags.indexOf(tag.name) != -1 + return "mixing-tags-from" module.controller("ProjectTagsController", ProjectTagsController) ############################################################################# -## Tags Directive +## Tags directive ############################################################################# -ProjectTagDirective = () -> +ProjectTagsDirective = ($log, $repo, $confirm, $location, animationFrame, $translate, $rootscope) -> link = ($scope, $el, $attrs) -> - $el.color = $scope.tag.color + $window = $(window) $ctrl = $el.controller() + valueType = $attrs.type + objName = $attrs.objname + + initializeNewValue = -> + $scope.newValue = { + "name": "" + "color": "" + } + + initializeTagsFilter = -> + $scope.tagsFilter = { + "name": "" + } + + initializeMixingTags = -> + $scope.mixingTags = { + "toTag": null, + "fromTags": [] + } + + initializeTextTranslations = -> + $scope.addNewElementText = $translate.instant("ADMIN.PROJECT_VALUES_TAGS.ACTION_ADD") + + initializeNewValue() + initializeTagsFilter() + initializeMixingTags() + initializeTextTranslations() + + $rootscope.$on "$translateChangeEnd", -> + $scope.$evalAsync(initializeTextTranslations) + + goToBottomList = (focus = false) => + table = $el.find(".table-main") + + $(document.body).scrollTop(table.offset().top + table.height()) + + if focus + $el.find(".new-value input:visible").first().focus() + + saveValue = (target) -> + formEl = target.parents("form") + form = formEl.checksley() + return if not form.validate() + + tag = formEl.scope().tag + originalTag = tag.clone() + originalTag.revert() + + promise = $ctrl.editTag(originalTag.name, tag.name, tag.color) + promise.then => + row = target.parents(".row.table-main") + row.addClass("hidden") + row.siblings(".visualization").removeClass('hidden') + $ctrl.loadTags() + + promise.then null, (data) -> + form.setErrors(data) + + saveNewValue = (target) -> + formEl = target.parents("form") + formEl = target + form = formEl.checksley() + return if not form.validate() + + promise = $ctrl.createTag($scope.newValue.name, $scope.newValue.color) + promise.then (data) => + target.addClass("hidden") + $ctrl.loadTags() + initializeNewValue() + + promise.then null, (data) -> + form.setErrors(data) + + cancel = (target) -> + row = target.parents(".row.table-main") + formEl = target.parents("form") + tag = formEl.scope().tag + + $scope.$apply -> + row.addClass("hidden") + tag.revert() + row.siblings(".visualization").removeClass('hidden') + + $scope.$watch "tagsFilter.name", (tagsFilter) -> + $ctrl.filterAndSortTags() + + $window.on "keyup", (event) -> + if event.keyCode == 27 + $scope.$apply -> + initializeMixingTags() + + $el.on "click", ".show-add-new", (event) -> + event.preventDefault() + $el.find(".new-value").removeClass('hidden') + + $el.on "click", ".add-new", debounce 2000, (event) -> + event.preventDefault() + target = $el.find(".new-value") + saveNewValue(target) + + $el.on "click", ".delete-new", (event) -> + event.preventDefault() + $el.find(".new-value").addClass("hidden") + initializeNewValue() + + $el.on "click", ".mix-tags", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + $scope.$apply -> + $ctrl.startMixingTags(target.parents('form').scope().tag) + + $el.on "click", ".mixing-row", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + $scope.$apply -> + $ctrl.toggleMixingFromTags(target.parents('form').scope().tag) + + $el.on "click", ".mixing-confirm", (event) -> + event.preventDefault() + event.stopPropagation() + $scope.$apply -> + $ctrl.confirmMixingTags() + + $el.on "click", ".mixing-cancel", (event) -> + event.preventDefault() + event.stopPropagation() + $scope.$apply -> + $ctrl.cancelMixingTags() + + $el.on "click", ".edit-value", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + + row = target.parents(".row.table-main") + row.addClass("hidden") + + editionRow = row.siblings(".edition") + editionRow.removeClass('hidden') + editionRow.find('input:visible').first().focus().select() + + $el.on "keyup", ".new-value input", (event) -> + if event.keyCode == 13 + target = $el.find(".new-value") + saveNewValue(target) + else if event.keyCode == 27 + $el.find(".new-value").addClass("hidden") + initializeNewValue() + + $el.on "keyup", ".status-name input", (event) -> + target = angular.element(event.currentTarget) + if event.keyCode == 13 + saveValue(target) + else if event.keyCode == 27 + cancel(target) + + $el.on "click", ".save", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + saveValue(target) + + $el.on "click", ".cancel", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + cancel(target) + + $el.on "click", ".delete-tag", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + formEl = target.parents("form") + tag = formEl.scope().tag + + title = $translate.instant("ADMIN.COMMON.TITLE_ACTION_DELETE_TAG") + + $confirm.askOnDelete(title, tag.name).then (response) -> + onSucces = -> + $ctrl.loadTags().finally -> + response.finish() + onError = -> + $confirm.notify("error") + $ctrl.deleteTag(tag.name).then(onSucces, onError) $scope.$on "$destroy", -> $el.off() + $window.off() - $scope.$watch "tag.color", (newColor) => - if $el.color != newColor - promise = $ctrl.updateTag($scope.tag) - promise.then null, (data) -> - form.setErrors(data) + return {link:link} - $el.color = newColor - - return {link: link} - -module.directive("tgProjectTag", [ProjectTagDirective]) +module.directive("tgProjectTags", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", "$translate", "$rootScope", ProjectTagsDirective]) diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index f31c9d4a..0f149171 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -28,6 +28,7 @@ bindOnce = @.taiga.bindOnce timeout = @.taiga.timeout debounce = @.taiga.debounce sizeFormat = @.taiga.sizeFormat +trim = @.taiga.trim ############################################################################# ## Common Lightbox Services @@ -295,6 +296,42 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, if attachment.get("id") attachmentsToDelete = attachmentsToDelete.push(attachment) + $scope.addTag = (tag, color) -> + value = trim(tag.toLowerCase()) + + tags = $scope.project.tags + projectTags = $scope.project.tags_colors + + tags = [] if not tags? + projectTags = {} if not projectTags? + + if value not in tags + tags.push(value) + + projectTags[tag] = color || null + + $scope.project.tags = tags + + itemtags = _.clone($scope.us.tags) + + inserted = _.find itemtags, (it) -> it[0] == value + + if !inserted + itemtags.push([tag , color]) + $scope.us.tags = itemtags + + $scope.deleteTag = (tag) -> + value = trim(tag[0].toLowerCase()) + + tags = $scope.project.tags + itemtags = _.clone($scope.us.tags) + + _.remove itemtags, (tag) -> tag[0] == value + + $scope.us.tags = itemtags + + _.pull($scope.us.tags, value) + $scope.$on "usform:new", (ctx, projectId, status, statusList) -> form.reset() if form $scope.isNew = true diff --git a/app/coffee/modules/common/tags.coffee b/app/coffee/modules/common/tags.coffee index 22e7dc30..faa3ec3a 100644 --- a/app/coffee/modules/common/tags.coffee +++ b/app/coffee/modules/common/tags.coffee @@ -26,6 +26,7 @@ taiga = @.taiga trim = @.taiga.trim bindOnce = @.taiga.bindOnce + module = angular.module("taigaCommon") # Directive that parses/format tags inputfield. @@ -61,28 +62,38 @@ ColorizeTagsDirective = -> templates = { backlog: _.template(""" <% _.each(tags, function(tag) { %> - <%- tag.name %> + + style="border-left: 5px solid <%- tag[1] %>" + <% } %> + title="<%- tag[0] %>"><%- tag[0] %> <% }) %> """) kanban: _.template(""" <% _.each(tags, function(tag) { %> - + + style="border-color: <%- tag[1] %>" + <% } %> + title="<%- tag[0] %>" /> <% }) %> """) taskboard: _.template(""" <% _.each(tags, function(tag) { %> - + + style="border-color: <%- tag[1] %>" + <% } %> + title="<%- tag[0] %>" /> <% }) %> """) } link = ($scope, $el, $attrs, $ctrl) -> - render = (srcTags) -> + render = (tags) -> template = templates[$attrs.tgColorizeTagsType] - srcTags.sort() - tags = _.map srcTags, (tag) -> - color = $scope.project.tags_colors[tag] - return {name: tag, color: color} html = template({tags: tags}) $el.html(html) @@ -111,15 +122,18 @@ LbTagLineDirective = ($rs, $template, $compile) -> autocomplete = null link = ($scope, $el, $attrs, $model) -> + withoutColors = _.has($attrs, "withoutColors") + ## Render renderTags = (tags, tagsColors = []) -> - ctx = { - tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]}) - } + color = if not withoutColors then tagsColors[t] else null - _.map ctx.tags, (tag) => - if tag.color - tag.style = "border-left: 5px solid #{tag.color}" + ctx = { + tags: _.map(tags, (t) -> { + name: t, + style: if color then "border-left: 5px solid #{color}" else "" + }) + } html = $compile(templateTags(ctx))($scope) $el.find(".tags-container").html(html) @@ -196,7 +210,7 @@ LbTagLineDirective = ($rs, $template, $compile) -> autocomplete = new Awesomplete(input[0], { list: _.keys(project.tags_colors) - }); + }) input.on "awesomplete-selectcomplete", () -> addValue(input.val()) @@ -216,204 +230,3 @@ LbTagLineDirective = ($rs, $template, $compile) -> } module.directive("tgLbTagLine", ["$tgResources", "$tgTemplate", "$compile", LbTagLineDirective]) - - -############################################################################# -## TagLine Directive (for detail pages) -############################################################################# - -TagLineDirective = ($rootScope, $repo, $rs, $confirm, $modelTransform, $template, $compile) -> - ENTER_KEY = 13 - ESC_KEY = 27 - COMMA_KEY = 188 - - templateTags = $template.get("common/tag/tags-line-tags.html", true) - - link = ($scope, $el, $attrs, $model) -> - autocomplete = null - loading = false - deleteTagLoading = null - - isEditable = -> - if $attrs.requiredPerm? - return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 - - return true - - ## Render - renderTags = (tags, tagsColors) -> - ctx = { - tags: _.map(tags, (t) -> {name: t, color: tagsColors[t]}) - isEditable: isEditable() - loading: loading - deleteTagLoading: deleteTagLoading - } - - html = $compile(templateTags(ctx))($scope) - $el.find("div.tags-container").html(html) - - renderInReadModeOnly = -> - $el.find(".add-tag").remove() - $el.find("input").remove() - $el.find(".save").remove() - - showAddTagButton = -> $el.find(".add-tag").removeClass("hidden") - hideAddTagButton = -> $el.find(".add-tag").addClass("hidden") - - showAddTagButtonText = -> $el.find(".add-tag-text").removeClass("hidden") - hideAddTagButtonText = -> $el.find(".add-tag-text").addClass("hidden") - - showSaveButton = -> $el.find(".save").removeClass("hidden") - hideSaveButton = -> $el.find(".save").addClass("hidden") - - showInput = -> $el.find("input").removeClass("hidden").focus() - hideInput = -> $el.find("input").addClass("hidden").blur() - resetInput = -> - $el.find("input").val("") - - autocomplete.close() - - ## Aux methods - addValue = (value) -> - loading = true - value = trim(value.toLowerCase()) - return if value.length == 0 - renderTags($model.$modelValue.tags, $scope.project?.tags_colors) - - transform = $modelTransform.save (item) -> - if not item.tags - item.tags = [] - - tags = _.clone(item.tags) - - tags.push(value) if value not in tags - - item.tags = tags - - return item - - onSuccess = -> - $rootScope.$broadcast("object:updated") - loading = false - renderTags($model.$modelValue.tags, $scope.project?.tags_colors) - - onError = -> - $confirm.notify("error") - - hideSaveButton() - - return transform.then(onSuccess, onError) - - deleteValue = (value) -> - value = trim(value.toLowerCase()) - return if value.length == 0 - deleteTagLoading = value - renderTags($model.$modelValue.tags, $scope.project?.tags_colors) - - transform = $modelTransform.save (item) -> - tags = _.clone(item.tags, false) - item.tags = _.pull(tags, value) - - return item - - onSuccess = -> - $rootScope.$broadcast("object:updated") - renderTags($model.$modelValue.tags, $scope.project?.tags_colors) - deleteTagLoading = null - - onError = -> - $confirm.notify("error") - deleteTagLoading = null - - return transform.then(onSuccess, onError) - - saveInputTag = () -> - value = $el.find("input").val() - - addValue(value) - resetInput() - - ## Events - $el.on "keypress", "input", (event) -> - target = angular.element(event.currentTarget) - - if event.keyCode == ENTER_KEY - saveInputTag() - else if String.fromCharCode(event.keyCode) == ',' - event.preventDefault() - saveInputTag() - else - if target.val().length - showSaveButton() - else - hideSaveButton() - - $el.on "keyup", "input", (event) -> - if event.keyCode == ESC_KEY - resetInput() - hideInput() - hideSaveButton() - showAddTagButton() - - $el.on "click", ".save", (event) -> - event.preventDefault() - saveInputTag() - - $el.on "click", ".add-tag", (event) -> - event.preventDefault() - hideAddTagButton() - showInput() - - $el.on "click", ".remove-tag", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget) - - value = target.siblings(".tag-name").text() - - deleteValue(value) - $scope.$digest() - - bindOnce $scope, "project.tags_colors", (tags_colors) -> - if not isEditable() - renderInReadModeOnly() - return - - showAddTagButton() - - input = $el.find("input") - - autocomplete = new Awesomplete(input[0], { - list: _.keys(tags_colors) - }); - - input.on "awesomplete-selectcomplete", () -> - addValue(input.val()) - input.val("") - - - $scope.$watchCollection () -> - return $model.$modelValue?.tags - , () -> - model = $model.$modelValue - - return if not model - - if model.tags?.length - hideAddTagButtonText() - else - showAddTagButtonText() - - tagsColors = $scope.project?.tags_colors or [] - renderTags(model.tags, tagsColors) - - $scope.$on "$destroy", -> - $el.off() - - return { - link:link, - require:"ngModel" - templateUrl: "common/tag/tag-line.html" - } - -module.directive("tgTagLine", ["$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$tgQueueModelTransformation", - "$tgTemplate", "$compile", TagLineDirective]) diff --git a/app/coffee/modules/issues/lightboxes.coffee b/app/coffee/modules/issues/lightboxes.coffee index 3be9bb6b..94ee2d33 100644 --- a/app/coffee/modules/issues/lightboxes.coffee +++ b/app/coffee/modules/issues/lightboxes.coffee @@ -25,6 +25,7 @@ taiga = @.taiga bindOnce = @.taiga.bindOnce debounce = @.taiga.debounce +trim = @.taiga.trim module = angular.module("taigaIssues") @@ -76,6 +77,42 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading, $scope.addAttachment = (attachment) -> attachmentsToAdd = attachmentsToAdd.push(attachment) + $scope.addTag = (tag, color) -> + value = trim(tag.toLowerCase()) + + tags = $scope.project.tags + projectTags = $scope.project.tags_colors + + tags = [] if not tags? + projectTags = {} if not projectTags? + + if value not in tags + tags.push(value) + + projectTags[tag] = color || null + + $scope.project.tags = tags + + itemtags = _.clone($scope.issue.tags) + + inserted = _.find itemtags, (it) -> it[0] == value + + if !inserted + itemtags.push([tag , color]) + $scope.issue.tags = itemtags + + $scope.deleteTag = (tag) -> + value = trim(tag[0].toLowerCase()) + + tags = $scope.project.tags + itemtags = _.clone($scope.us.tags) + + _.remove itemtags, (tag) -> tag[0] == value + + $scope.us.tags = itemtags + + _.pull($scope.issue.tags, value) + submit = debounce 2000, (event) => event.preventDefault() @@ -101,7 +138,6 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading, currentLoading.finish() $confirm.notify("error") - submitButton = $el.find(".submit-button") $el.on "submit", "form", submit diff --git a/app/coffee/modules/resources/projects.coffee b/app/coffee/modules/resources/projects.coffee index b93912f2..108012cd 100644 --- a/app/coffee/modules/resources/projects.coffee +++ b/app/coffee/modules/resources/projects.coffee @@ -83,6 +83,34 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) -> service.tagsColors = (projectId) -> return $repo.queryOne("projects", "#{projectId}/tags_colors") + service.deleteTag = (projectId, tag) -> + url = "#{$urls.resolve("projects")}/#{projectId}/delete_tag" + return $http.post(url, {tag: tag}) + + service.createTag = (projectId, tag, color) -> + url = "#{$urls.resolve("projects")}/#{projectId}/create_tag" + data = {} + data.tag = tag + data.color = null + if color + data.color = color + return $http.post(url, data) + + service.editTag = (projectId, from_tag, to_tag, color) -> + url = "#{$urls.resolve("projects")}/#{projectId}/edit_tag" + data = {} + data.from_tag = from_tag + if to_tag + data.to_tag = to_tag + data.color = null + if color + data.color = color + return $http.post(url, data) + + service.mixTags = (projectId, to_tag, from_tags) -> + url = "#{$urls.resolve("projects")}/#{projectId}/mix_tags" + return $http.post(url, {to_tag: to_tag, from_tags: from_tags}) + service.export = (projectId) -> url = "#{$urls.resolve("exporter")}/#{projectId}" return $http.get(url) diff --git a/app/coffee/modules/taskboard/lightboxes.coffee b/app/coffee/modules/taskboard/lightboxes.coffee index caf92987..9626e245 100644 --- a/app/coffee/modules/taskboard/lightboxes.coffee +++ b/app/coffee/modules/taskboard/lightboxes.coffee @@ -25,6 +25,7 @@ taiga = @.taiga bindOnce = @.taiga.bindOnce debounce = @.taiga.debounce +trim = @.taiga.trim CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService, $translate, $q, attachmentsService) -> link = ($scope, $el, attrs) -> @@ -56,6 +57,45 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer return $q.all(promises) + tagsToAdd = [] + + $scope.addTag = (tag, color) -> + value = trim(tag.toLowerCase()) + + tags = $scope.project.tags + projectTags = $scope.project.tags_colors + + tags = [] if not tags? + projectTags = {} if not projectTags? + + if value not in tags + tags.push(value) + + projectTags[tag] = color || null + + $scope.project.tags = tags + + itemtags = _.clone($scope.task.tags) + + inserted = _.find itemtags, (it) -> it[0] == value + + if !inserted + itemtags.push([tag , color]) + $scope.task.tags = itemtags + + + $scope.deleteTag = (tag) -> + value = trim(tag[0].toLowerCase()) + + tags = $scope.project.tags + itemtags = _.clone($scope.task.tags) + + _.remove itemtags, (tag) -> tag[0] == value + + $scope.task.tags = itemtags + + _.pull($scope.task.tags, value) + $scope.$on "taskform:new", (ctx, sprintId, usId) -> $scope.task = { project: $scope.projectId diff --git a/app/coffee/utils.coffee b/app/coffee/utils.coffee index d69a91c3..1bffd7c7 100644 --- a/app/coffee/utils.coffee +++ b/app/coffee/utils.coffee @@ -28,10 +28,12 @@ addClass = (el, className) -> else el.className += ' ' + className + nl2br = (str) => breakTag = '
' return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + breakTag + '$2') + bindMethods = (object) => dependencies = _.keys(object) @@ -43,6 +45,7 @@ bindMethods = (object) => _.bindAll(object, methods) + bindOnce = (scope, attr, continuation) => val = scope.$eval(attr) if val != undefined @@ -75,6 +78,7 @@ slugify = (data) -> .replace(/[^\w\-]+/g, '') .replace(/\-\-+/g, '-') + unslugify = (data) -> if data return _.capitalize(data.replace(/-/g, ' ')) @@ -165,6 +169,7 @@ 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') @@ -172,6 +177,7 @@ stripTags = (str, exception) -> else return String(str).replace(/<\/?[^>]+>/g, '') + replaceTags = (str, tags, replace) -> # open tag pattern = new RegExp('<(' + tags + ')>', 'gi') @@ -183,6 +189,7 @@ replaceTags = (str, tags, replace) -> return str + defineImmutableProperty = (obj, name, fn) => Object.defineProperty obj, name, { get: () => @@ -197,6 +204,7 @@ defineImmutableProperty = (obj, name, fn) => return fn_result } + _.mixin removeKeys: (obj, keys) -> _.chain([keys]).flatten().reduce( @@ -211,13 +219,14 @@ _.mixin , [ [] ]) - isImage = (name) -> return name.match(/\.(jpe?g|png|gif|gifv|webm)/i) != null + isPdf = (name) -> return name.match(/\.(pdf)/i) != null + patch = (oldImmutable, newImmutable) -> pathObj = {} @@ -230,6 +239,7 @@ patch = (oldImmutable, newImmutable) -> return pathObj + taiga = @.taiga taiga.addClass = addClass taiga.nl2br = nl2br diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 84bc19e5..96549209 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -426,7 +426,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Edit value", - "TITLE_ACTION_DELETE_VALUE": "Delete value" + "TITLE_ACTION_DELETE_VALUE": "Delete value", + "TITLE_ACTION_DELETE_TAG": "Delete tag" }, "HELP": "Do you need help? Check out our support page!", "PROJECT_DEFAULT_VALUES": { @@ -582,9 +583,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Tags", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Add tag", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Roles - {{projectName}}", diff --git a/app/modules/components/tags/color-selector/color-selector.controller.coffee b/app/modules/components/tags/color-selector/color-selector.controller.coffee new file mode 100644 index 00000000..2d817a15 --- /dev/null +++ b/app/modules/components/tags/color-selector/color-selector.controller.coffee @@ -0,0 +1,57 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: color-selector.controller.coffee +### + +module = angular.module('taigaCommon') + +class ColorSelectorController + + constructor: () -> + @.colorList = [ + '#fce94f', + '#edd400', + '#c4a000', + '#8ae234', + '#73d216', + '#4e9a06', + '#d3d7cf', + '#fcaf3e', + '#f57900', + '#ce5c00', + '#729fcf', + '#3465a4', + '#204a87', + '#888a85', + '#ad7fa8', + '#75507b', + '#5c3566', + '#ef2929', + '#cc0000', + '#a40000' + ] + @.displaycolorList = false + + toggleColorList: () -> + @.displaycolorList = !@.displaycolorList + + onSelectDropdownColor: (color) -> + @.onSelectColor({color: color}) + @.toggleColorList() + + +module.controller("ColorSelectorCtrl", ColorSelectorController) diff --git a/app/modules/components/tags/color-selector/color-selector.controller.spec.coffee b/app/modules/components/tags/color-selector/color-selector.controller.spec.coffee new file mode 100644 index 00000000..ec212331 --- /dev/null +++ b/app/modules/components/tags/color-selector/color-selector.controller.spec.coffee @@ -0,0 +1,60 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: color-selector.controller.spec.coffee +### + +describe "ColorSelector", -> + provide = null + controller = null + colorSelectorCtrl = null + mocks = {} + + _mocks = () -> + module ($provide) -> + provide = $provide + return null + + beforeEach -> + module "taigaCommon" + + _mocks() + + inject ($controller) -> + controller = $controller + + colorSelectorCtrl = controller "ColorSelectorCtrl" + colorSelectorCtrl.colorList = [ + '#fce94f', + '#edd400', + '#c4a000', + ] + colorSelectorCtrl.displaycolorList = false + + it "display Color Selector", () -> + colorSelectorCtrl.toggleColorList() + expect(colorSelectorCtrl.displaycolorList).to.be.true + + it "on select Color", () -> + colorSelectorCtrl.toggleColorList = sinon.stub() + + color = '#FFFFFF' + + colorSelectorCtrl.onSelectColor = sinon.spy() + + colorSelectorCtrl.onSelectDropdownColor(color) + expect(colorSelectorCtrl.toggleColorList).have.been.called + expect(colorSelectorCtrl.onSelectColor).to.have.been.calledWith({color: color}) diff --git a/app/modules/components/tags/color-selector/color-selector.directive.coffee b/app/modules/components/tags/color-selector/color-selector.directive.coffee new file mode 100644 index 00000000..67e02f57 --- /dev/null +++ b/app/modules/components/tags/color-selector/color-selector.directive.coffee @@ -0,0 +1,61 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: color-selector.directive.coffee +### + +module = angular.module('taigaCommon') + +ColorSelectorDirective = ($timeout) -> + link = (scope, el) -> + timeout = null + + cancel = () -> + $timeout.cancel(timeout) + timeout = null + + close = () -> + return if timeout + + timeout = $timeout (() -> + scope.vm.displaycolorList = false + ), 400 + + el.find('.color-selector') + .mouseenter(cancel) + .mouseleave(close) + + el.find('.color-selector-dropdown') + .mouseenter(cancel) + .mouseleave(close) + + return { + link: link, + scope:{ + onSelectColor: "&", + color: "=" + }, + templateUrl:"components/tags/color-selector/color-selector.html", + controller: "ColorSelectorCtrl", + controllerAs: "vm", + bindToController: true + } + +ColorSelectorDirective.$inject = [ + "$timeout" +] + +module.directive("tgColorSelector", ColorSelectorDirective) diff --git a/app/modules/components/tags/color-selector/color-selector.jade b/app/modules/components/tags/color-selector/color-selector.jade new file mode 100644 index 00000000..c66e83aa --- /dev/null +++ b/app/modules/components/tags/color-selector/color-selector.jade @@ -0,0 +1,30 @@ +.color-selector + .tag-color.e2e-open-color-selector( + ng-click="vm.toggleColorList()" + ng-class="{'empty-color': !vm.color}" + ng-style="{'background': vm.color}" + ) + .color-selector-dropdown(ng-show="vm.displaycolorList") + ul.color-selector-dropdown-list.e2e-color-dropdown + li.color-selector-option( + ng-repeat="color in vm.colorList" + ng-style="{'background': color}" + ng-title="color" + ng-click="vm.onSelectDropdownColor(color)" + ) + li.empty-color(ng-click="vm.onSelectDropdownColor(null)") + .custom-color-selector + .display-custom-color.empty-color( + ng-if="!customColor.color || customColor.color.length < 7" + ) + .display-custom-color( + ng-if="customColor.color.length === 7" + ng-style="{'background': customColor.color}" + ng-click="vm.onSelectDropdownColor(customColor.color)" + ) + input.custom-color-input( + type="text" + maxlength="7" + placeholder="#000000" + ng-model="customColor.color" + ) diff --git a/app/modules/components/tags/color-selector/color-selector.scss b/app/modules/components/tags/color-selector/color-selector.scss new file mode 100644 index 00000000..2f06ccb0 --- /dev/null +++ b/app/modules/components/tags/color-selector/color-selector.scss @@ -0,0 +1,68 @@ +@mixin color-selector-option { + border-radius: 2px; + cursor: pointer; + height: 2.25rem; + width: 2.25rem; + min-width: 2.25rem; + margin: 0 .5rem .5rem 0; + &:nth-child(7n) { + margin-right: 0; + } +} + +.color-selector { + position: relative; + .tag-color { + @include color-selector-option; + border: 1px solid $gray-light; + border-left: 0; + border-radius: 0; + margin: 0; + transition: background .3s ease-out; + &.empty-color { + @include empty-color(34); + } + } +} + +.color-selector-dropdown { + background: $blackish; + left: 0; + padding: 1rem; + position: absolute; + top: 2.25rem; + width: 332px; + z-index: 99; +} + +.color-selector-dropdown-list { + display: flex; + flex-wrap: wrap; + list-style-type: none; + margin-bottom: 0; + .color-selector-option { + @include color-selector-option; + } + .empty-color { + @include color-selector-option; + @include empty-color(34); + } +} + +.custom-color-selector { + display: flex; + .custom-color-input { + margin: 0; + width: 100%; + } + .display-custom-color { + @include color-selector-option; + flex-shrink: 0; + margin: 0; + margin-right: .5rem; + &.empty-color { + @include empty-color(34); + cursor: default; + } + } +} diff --git a/app/modules/components/tags/components/add-tag-button.jade b/app/modules/components/tags/components/add-tag-button.jade new file mode 100644 index 00000000..88bf6bd6 --- /dev/null +++ b/app/modules/components/tags/components/add-tag-button.jade @@ -0,0 +1,11 @@ +a.add-tag-button.ng-animate-disabled.e2e-show-tag-input( + ng-if="!vm.addTag && vm.checkPermissions()" + href="#" + title="{{'COMMON.TAGS.ADD' | translate}}" + ng-click="vm.displayTagInput()" +) + tg-svg( + svg-icon="icon-add" + svg-title-translate="COMMON.TAGS.ADD" + ) + span.add-tag-text(translate="COMMON.TAGS.ADD") diff --git a/app/modules/components/tags/components/add-tag-input.jade b/app/modules/components/tags/components/add-tag-input.jade new file mode 100644 index 00000000..7e1b11ca --- /dev/null +++ b/app/modules/components/tags/components/add-tag-input.jade @@ -0,0 +1,33 @@ +.add-tag-input( + novalidate + ng-if="vm.addTag && vm.checkPermissions()" + tg-loading="vm.loadingAddTag" +) + input.tag-input.e2e-add-tag-input( + type="text" + placeholder="{{'COMMON.TAGS.PLACEHOLDER' | translate}}" + autofocus + ng-model="vm.newTag.name" + ng-model-options="{debounce: 200}" + ) + + tg-tags-dropdown( + ng-if="!vm.disableColorSelection" + ng-show="vm.newTag.name.length", + color-array="vm.colorArray", + tag="vm.newTag", + on-select-tag="vm.addNewTag(name, color)" + ) + + tg-color-selector( + ng-if="!vm.disableColorSelection" + color="vm.newTag.color", + on-select-color="vm.selectColor(color)" + ) + + tg-svg.save( + ng-show="vm.newTag.name.length" + svg-icon="icon-save" + svg-title-translate="COMMON.TAGS.ADD" + ng-click="vm.addNewTag(vm.newTag.name, vm.newTag.color)" + ) diff --git a/app/modules/components/tags/components/add-tag.scss b/app/modules/components/tags/components/add-tag.scss new file mode 100644 index 00000000..bccccb5b --- /dev/null +++ b/app/modules/components/tags/components/add-tag.scss @@ -0,0 +1,59 @@ +$tag-input-width: 250px; + +.add-tag-input { + align-items: flex-start; + display: flex; + flex-grow: 0; + flex-shrink: 0; + position: relative; + width: $tag-input-width; + input { + border-color: $gray-light; + padding: 6px; + width: 14rem; + } + .save { + cursor: pointer; + display: inline-block; + fill: $grayer; + margin: .5rem 0 0 .5rem; + transition: .2s linear; + &:hover { + fill: $primary; + } + } + .tags-dropdown { + @include font-size(small); + background: $white; + border: 1px solid $gray-light; + border-top: 0; + box-shadow: 2px 2px 3px rgba($black, .2); + left: 0; + max-height: 20vh; + min-height: 0; + overflow-x: hidden; + overflow-y: auto; + position: absolute; + top: 2.25rem; + width: 85%; + z-index: 99; + } + .tags-dropdown-option { + display: flex; + justify-content: space-between; + padding: .5rem; + } + .tags-dropdown-color { + height: 1rem; + width: 1rem; + } + li { + &:hover, + &.selected { + background: lighten($primary-light, 50%); + cursor: pointer; + transition: .2s; + transition-delay: .1s; + } + } +} diff --git a/app/modules/components/tags/tag-dropdown/tag-dropdown.directive.coffee b/app/modules/components/tags/tag-dropdown/tag-dropdown.directive.coffee new file mode 100644 index 00000000..c1386e2f --- /dev/null +++ b/app/modules/components/tags/tag-dropdown/tag-dropdown.directive.coffee @@ -0,0 +1,84 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: tag-line.directive.coffee +### + +module = angular.module('taigaCommon') + +TagOptionDirective = () -> + select = (selected) -> + selected.addClass('selected') + + selectedPosition = selected.position().top + selected.outerHeight() + containerHeight = selected.parent().outerHeight() + + if selectedPosition > containerHeight + diff = selectedPosition - containerHeight + selected.parent().scrollTop(selected.parent().scrollTop() + diff) + else if selected.position().top < 0 + selected.parent().scrollTop(selected.parent().scrollTop() + selected.position().top) + + dispatch = (el, code, scope) -> + activeElement = el.find(".selected") + + # Key: down + if code == 40 + if not activeElement.length + select(el.find('li:first')) + else + next = activeElement.next('li') + if next.length + activeElement.removeClass('selected') + select(next) + # Key: up + else if code == 38 + if not activeElement.length + select(el.find('li:last')) + else + prev = activeElement.prev('li') + + if prev.length + activeElement.removeClass('selected') + select(prev) + + stop = -> + $(document).off(".tags-keyboard-navigation") + + link = (scope, el) -> + stop() + + $(document).on "keydown.tags-keyboard-navigation", (event) => + code = if event.keyCode then event.keyCode else event.which + + if code == 40 || code == 38 + event.preventDefault() + + dispatch(el, code, scope) + + scope.$on("$destroy", stop) + + return { + link: link, + templateUrl:"components/tags/tag-dropdown/tag-dropdown.html", + scope: { + onSelectTag: "&", + colorArray: "=", + tag: "=" + } + } + +module.directive("tgTagsDropdown", TagOptionDirective) diff --git a/app/modules/components/tags/tag-dropdown/tag-dropdown.jade b/app/modules/components/tags/tag-dropdown/tag-dropdown.jade new file mode 100644 index 00000000..e25ad740 --- /dev/null +++ b/app/modules/components/tags/tag-dropdown/tag-dropdown.jade @@ -0,0 +1,12 @@ +ul.tags-dropdown + li( + ng-repeat="tag in colorArray | filter: tag.name", + ng-click="onSelectTag({name: tag[0], color: tag[1]})" + ) + .tags-dropdown-option + span.tags-dropdown-name {{tag[0]}} + span.tags-dropdown-color( + ng-if="tag[1]" + ng-style="{'background': tag[1]}" + ng-title="tag[1]" + ) diff --git a/app/modules/components/tags/tag-line-common/tag-line-common.controller.coffee b/app/modules/components/tags/tag-line-common/tag-line-common.controller.coffee new file mode 100644 index 00000000..26082ae3 --- /dev/null +++ b/app/modules/components/tags/tag-line-common/tag-line-common.controller.coffee @@ -0,0 +1,56 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: tag-line.controller.coffee +### + +trim = @.taiga.trim + +module = angular.module('taigaCommon') + +class TagLineCommonController + + @.$inject = [ + "tgTagLineService" + ] + + constructor: (@tagLineService) -> + @.newTag = {name: "", color: null} + @.colorArray = [] + @.addTag = false + + checkPermissions: () -> + return @tagLineService.checkPermissions(@.project.my_permissions, @.permissions) + + _createColorsArray: (projectTagColors) -> + @.colorArray = @tagLineService.createColorsArray(projectTagColors) + + displayTagInput: () -> + @.addTag = true + + addNewTag: (name, color) -> + @.newTag.name = "" + @.newTag.color = null + + if @.project.tags_colors[name] + color = @.project.tags_colors[name] + + @.onAddTag({name: name, color: color}) if name.length + + selectColor: (color) -> + @.newTag.color = color + +module.controller("TagLineCommonCtrl", TagLineCommonController) diff --git a/app/modules/components/tags/tag-line-common/tag-line-common.controller.spec.coffee b/app/modules/components/tags/tag-line-common/tag-line-common.controller.spec.coffee new file mode 100644 index 00000000..188df081 --- /dev/null +++ b/app/modules/components/tags/tag-line-common/tag-line-common.controller.spec.coffee @@ -0,0 +1,96 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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:tag-line-common.controller.spec.coffee +### + +describe "TagLineCommon", -> + provide = null + controller = null + TagLineCommonCtrl = null + mocks = {} + + _mockTgTagLineService = () -> + mocks.tgTagLineService = { + checkPermissions: sinon.stub() + createColorsArray: sinon.stub() + renderTags: sinon.stub() + } + + provide.value "tgTagLineService", mocks.tgTagLineService + + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgTagLineService() + return null + + beforeEach -> + module "taigaCommon" + + _mocks() + + inject ($controller) -> + controller = $controller + + TagLineCommonCtrl = controller "TagLineCommonCtrl" + TagLineCommonCtrl.tags = [] + TagLineCommonCtrl.colorArray = [] + TagLineCommonCtrl.addTag = false + + it "check permissions", () -> + TagLineCommonCtrl.project = { + } + TagLineCommonCtrl.project.my_permissions = [ + 'permission1', + 'permission2' + ] + TagLineCommonCtrl.permissions = 'permissions1' + + TagLineCommonCtrl.checkPermissions() + expect(mocks.tgTagLineService.checkPermissions).have.been.calledWith(TagLineCommonCtrl.project.my_permissions, TagLineCommonCtrl.permissions) + + it "create Colors Array", () -> + projectTagColors = 'string' + mocks.tgTagLineService.createColorsArray.withArgs(projectTagColors).returns(true) + TagLineCommonCtrl._createColorsArray(projectTagColors) + expect(TagLineCommonCtrl.colorArray).to.be.equal(true) + + it "display tag input", () -> + TagLineCommonCtrl.addTag = false + TagLineCommonCtrl.displayTagInput() + expect(TagLineCommonCtrl.addTag).to.be.true + + it "on add tag", () -> + TagLineCommonCtrl.loadingAddTag = true + tag = 'tag1' + tags = ['tag1', 'tag2'] + color = "CC0000" + + TagLineCommonCtrl.project = { + tags: ['tag1', 'tag2'], + tags_colors: ["#CC0000", "CCBB00"] + } + + TagLineCommonCtrl.onAddTag = sinon.spy() + TagLineCommonCtrl.newTag = {name: "11", color: "22"} + + TagLineCommonCtrl.addNewTag(tag, color) + + expect(TagLineCommonCtrl.onAddTag).have.been.calledWith({name: tag, color: color}) + expect(TagLineCommonCtrl.newTag.name).to.be.eql("") + expect(TagLineCommonCtrl.newTag.color).to.be.null diff --git a/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee b/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee new file mode 100644 index 00000000..668a899f --- /dev/null +++ b/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee @@ -0,0 +1,69 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: tag-line.directive.coffee +### + +module = angular.module('taigaCommon') + +TagLineCommonDirective = () -> + link = (scope, el, attr, ctrl) -> + if !_.isUndefined(attr.disableColorSelection) + ctrl.disableColorSelection = true + + unwatch = scope.$watch "vm.project", (project) -> + return if !project || !Object.keys(project).length + + unwatch() + ctrl.colorArray = ctrl._createColorsArray(ctrl.project.tags_colors) + + el.on "keydown", ".tag-input", (event) -> + if event.keyCode == 27 && ctrl.newTag.name.length + ctrl.addTag = false + + ctrl.newTag.name = "" + ctrl.newTag.color = "" + + event.stopPropagation() + else if event.keyCode == 13 + event.preventDefault() + + if el.find('.tags-dropdown .selected').length + tagName = $('.tags-dropdown .selected .tags-dropdown-name').text() + ctrl.addNewTag(tagName, null) + else + ctrl.addNewTag(ctrl.newTag.name, ctrl.newTag.color) + + scope.$apply() + + return { + link: link, + scope: { + permissions: "@", + loadingAddTag: "=", + loadingRemoveTag: "=", + tags: "=", + project: "=", + onAddTag: "&", + onDeleteTag: "&" + }, + templateUrl:"components/tags/tag-line-common/tag-line-common.html", + controller: "TagLineCommonCtrl", + controllerAs: "vm", + bindToController: true + } + +module.directive("tgTagLineCommon", TagLineCommonDirective) diff --git a/app/modules/components/tags/tag-line-common/tag-line-common.jade b/app/modules/components/tags/tag-line-common/tag-line-common.jade new file mode 100644 index 00000000..a1fd6848 --- /dev/null +++ b/app/modules/components/tags/tag-line-common/tag-line-common.jade @@ -0,0 +1,26 @@ +.tags-container + .tag( + ng-if="tag[1]" + ng-repeat="tag in vm.tags" + ng-style="{'border-left': '.3rem solid' + tag[1]}" + ) + tg-tag( + tag="tag" + loading-remove-tag="vm.loadingRemoveTag" + project="vm.project" + on-delete-tag="vm.onDeleteTag({tag: tag})" + has-permissions="{{vm.checkPermissions()}}" + ) + .tag( + ng-if="!tag[1]" + ng-repeat="tag in vm.tags" + ) + tg-tag( + tag="tag" + loading-remove-tag="vm.loadingRemoveTag" + on-delete-tag="vm.onDeleteTag({tag: tag})" + has-permissions="{{vm.checkPermissions()}}" + ) + +include ../components/add-tag-button +include ../components/add-tag-input diff --git a/app/modules/components/tags/tag-line-detail/tag-line-detail.controller.coffee b/app/modules/components/tags/tag-line-detail/tag-line-detail.controller.coffee new file mode 100644 index 00000000..5130cac6 --- /dev/null +++ b/app/modules/components/tags/tag-line-detail/tag-line-detail.controller.coffee @@ -0,0 +1,88 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: tag-line.controller.coffee +### + +trim = @.taiga.trim + +module = angular.module('taigaCommon') + +class TagLineController + + @.$inject = [ + "$rootScope", + "$tgConfirm", + "$tgQueueModelTransformation", + ] + + constructor: (@rootScope, @confirm, @modelTransform) -> + @.loadingAddTag = false + + onDeleteTag: (tag) -> + @.loadingRemoveTag = tag[0] + + onDeleteTagSuccess = (item) => + @rootScope.$broadcast("object:updated") + @.loadingRemoveTag = false + + return item + + onDeleteTagError = () => + @confirm.notify("error") + @.loadingRemoveTag = false + + tagName = trim(tag[0].toLowerCase()) + + transform = @modelTransform.save (item) -> + itemtags = _.clone(item.tags) + + _.remove itemtags, (tag) -> tag[0] == tagName + + item.tags = itemtags + + return item + + return transform.then(onDeleteTagSuccess, onDeleteTagError) + + onAddTag: (tag, color) -> + @.loadingAddTag = true + + onAddTagSuccess = (item) => + @rootScope.$broadcast("object:updated") #its a kind of magic. + @.addTag = false + @.loadingAddTag = false + + return item + + onAddTagError = () => + @.loadingAddTag = false + @confirm.notify("error") + + transform = @modelTransform.save (item) => + value = trim(tag.toLowerCase()) + + itemtags = _.clone(item.tags) + + itemtags.push([tag , color]) + + item.tags = itemtags + + return item + + return transform.then(onAddTagSuccess, onAddTagError) + +module.controller("TagLineCtrl", TagLineController) diff --git a/app/modules/components/tags/tag-line-detail/tag-line-detail.controller.spec.coffee b/app/modules/components/tags/tag-line-detail/tag-line-detail.controller.spec.coffee new file mode 100644 index 00000000..8b323169 --- /dev/null +++ b/app/modules/components/tags/tag-line-detail/tag-line-detail.controller.spec.coffee @@ -0,0 +1,157 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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:tag-line-detail.controller.spec.coffee +### + +describe "TagLineDetail", -> + provide = null + controller = null + TagLineController = null + mocks = {} + + _mockRootScope = () -> + mocks.rootScope = { + $broadcast: sinon.stub() + } + + provide.value "$rootScope", mocks.rootScope + + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + _mockTgQueueModelTransformation = () -> + mocks.tgQueueModelTransformation = { + save: sinon.stub() + } + + provide.value "$tgQueueModelTransformation", mocks.tgQueueModelTransformation + + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockRootScope() + _mockTgConfirm() + _mockTgQueueModelTransformation() + + return null + + beforeEach -> + module "taigaCommon" + + _mocks() + + inject ($controller) -> + controller = $controller + + TagLineController = controller "TagLineCtrl" + + it "on delete tag success", (done) -> + tag = { + name: 'tag1' + } + tagName = tag.name + + item = { + tags: [ + ['tag1'], + ['tag2'], + ['tag3'] + ] + } + + mocks.tgQueueModelTransformation.save.callsArgWith(0, item) + mocks.tgQueueModelTransformation.save.promise().resolve(item) + + TagLineController.onDeleteTag(['tag1', '#000']).then (item) -> + expect(item.tags).to.be.eql([ + ['tag2'], + ['tag3'] + ]) + expect(TagLineController.loadingRemoveTag).to.be.false + expect(mocks.rootScope.$broadcast).to.be.calledWith("object:updated") + done() + + it "on delete tag error", (done) -> + mocks.tgQueueModelTransformation.save.promise().reject(new Error('error')) + + TagLineController.onDeleteTag(['tag1']).finally () -> + expect(TagLineController.loadingRemoveTag).to.be.false + expect(mocks.tgConfirm.notify).to.be.calledWith("error") + done() + + it "on add tag success", (done) -> + tag = 'tag1' + tagColor = '#eee' + + item = { + tags: [ + ['tag2'], + ['tag3'] + ] + } + + mockPromise = mocks.tgQueueModelTransformation.save.promise() + + mocks.tgQueueModelTransformation.save.callsArgWith(0, item) + promise = TagLineController.onAddTag(tag, tagColor) + + expect(TagLineController.loadingAddTag).to.be.true + + mockPromise.resolve(item) + + promise.then (item) -> + expect(item.tags).to.be.eql([ + ['tag2'], + ['tag3'], + ['tag1', '#eee'] + ]) + + expect(mocks.rootScope.$broadcast).to.be.calledWith("object:updated") + expect(TagLineController.addTag).to.be.false + expect(TagLineController.loadingAddTag).to.be.false + + done() + + it "on add tag error", (done) -> + tag = 'tag1' + tagColor = '#eee' + + item = { + tags: [ + ['tag2'], + ['tag3'] + ] + } + + mockPromise = mocks.tgQueueModelTransformation.save.promise() + + mocks.tgQueueModelTransformation.save.callsArgWith(0, item) + promise = TagLineController.onAddTag(tag, tagColor) + + expect(TagLineController.loadingAddTag).to.be.true + + mockPromise.reject(new Error('error')) + + promise.then (item) -> + expect(TagLineController.loadingAddTag).to.be.false + expect(mocks.tgConfirm.notify).to.be.calledWith("error") + done() diff --git a/app/modules/components/tags/tag-line-detail/tag-line-detail.directive.coffee b/app/modules/components/tags/tag-line-detail/tag-line-detail.directive.coffee new file mode 100644 index 00000000..50976ab6 --- /dev/null +++ b/app/modules/components/tags/tag-line-detail/tag-line-detail.directive.coffee @@ -0,0 +1,35 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: tag-line.directive.coffee +### + +module = angular.module('taigaCommon') + +TagLineDirective = () -> + return { + scope: { + item: "=", + permissions: "@", + project: "=" + }, + templateUrl:"components/tags/tag-line-detail/tag-line-detail.html", + controller: "TagLineCtrl", + controllerAs: "vm", + bindToController: true + } + +module.directive("tgTagLine", TagLineDirective) diff --git a/app/modules/components/tags/tag-line-detail/tag-line-detail.jade b/app/modules/components/tags/tag-line-detail/tag-line-detail.jade new file mode 100644 index 00000000..3a688aa6 --- /dev/null +++ b/app/modules/components/tags/tag-line-detail/tag-line-detail.jade @@ -0,0 +1,9 @@ +tg-tag-line-common.tags-block( + project="vm.project" + tags="vm.item.tags" + permissions="{{vm.permissions}}" + loading-remove-tag="vm.loadingRemoveTag" + loading-add-tag="vm.loadingAddTag" + on-add-tag="vm.onAddTag(name, color)" + on-delete-tag="vm.onDeleteTag(tag)" +) diff --git a/app/modules/components/tags/tag-line.scss b/app/modules/components/tags/tag-line.scss new file mode 100644 index 00000000..292458b0 --- /dev/null +++ b/app/modules/components/tags/tag-line.scss @@ -0,0 +1,22 @@ +.tags-block { + align-content: center; + display: flex; + flex-wrap: wrap; +} + +.add-tag-button { + color: $gray-light; + cursor: pointer; + display: inline-block; + &:hover { + color: $primary-light; + } + .icon-add { + @include svg-size(.9rem); + fill: currentColor; + margin: .5rem .25rem 0 0; + } + .add-tag-text { + @include font-size(small); + } +} diff --git a/app/modules/components/tags/tag-line.service.coffee b/app/modules/components/tags/tag-line.service.coffee new file mode 100644 index 00000000..f8257b8a --- /dev/null +++ b/app/modules/components/tags/tag-line.service.coffee @@ -0,0 +1,35 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: tag-line.service.coffee +### + +module = angular.module('taigaCommon') + +class TagLineService extends taiga.Service + @.$inject = [] + + constructor: () -> + + checkPermissions: (myPermissions, projectPermissions) -> + return _.includes(myPermissions, projectPermissions) + + createColorsArray: (projectTagColors) -> + return _.map(projectTagColors, (index, value) -> + return [value, index] + ) + +module.service("tgTagLineService", TagLineService) diff --git a/app/modules/components/tags/tag/tag.directive.coffee b/app/modules/components/tags/tag/tag.directive.coffee new file mode 100644 index 00000000..ccd366f1 --- /dev/null +++ b/app/modules/components/tags/tag/tag.directive.coffee @@ -0,0 +1,33 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: tag-line.directive.coffee +### + +module = angular.module('taigaCommon') + +TagDirective = () -> + return { + templateUrl:"components/tags/tag/tag.html", + scope: { + tag: "<", + loadingRemoveTag: "<", + onDeleteTag: "&", + hasPermissions: "@" + } + } + +module.directive("tgTag", TagDirective) diff --git a/app/modules/components/tags/tag/tag.jade b/app/modules/components/tags/tag/tag.jade new file mode 100644 index 00000000..acd7692a --- /dev/null +++ b/app/modules/components/tags/tag/tag.jade @@ -0,0 +1,8 @@ +span {{ tag[0] }} +tg-svg.icon-close.e2e-delete-tag( + ng-if="hasPermissions" + svg-icon="icon-close" + svg-title-translate="COMMON.TAG.DELETE" + ng-click="onDeleteTag(tag)" + tg-loading="loadingRemoveTag == tag[0]" +) diff --git a/app/modules/components/tags/tag/tag.scss b/app/modules/components/tags/tag/tag.scss new file mode 100644 index 00000000..185940e8 --- /dev/null +++ b/app/modules/components/tags/tag/tag.scss @@ -0,0 +1,21 @@ +.tag { + @include font-type(light); + @include font-size(small); + background: $mass-white; + border-radius: 0 5px 5px 0; + color: $grayer; + display: inline-block; + margin: 0 .5rem .5rem 0; + padding: .5rem; + text-align: center; + .icon-close { + @include svg-size(.7rem); + cursor: pointer; + fill: $red-light; + margin-left: .25rem; + } + .loading-spinner { + height: 1rem; + width: 1rem; + } +} diff --git a/app/modules/home/projects/home-project-list.jade b/app/modules/home/projects/home-project-list.jade index 9ed81a65..beecb13c 100644 --- a/app/modules/home/projects/home-project-list.jade +++ b/app/modules/home/projects/home-project-list.jade @@ -5,12 +5,6 @@ section.home-project-list(ng-if="vm.projects.size") tg-repeat="project in vm.projects" ng-class="{'blocked-project': project.get('blocked_code')}" ) - .tags-container - .project-tag( - style="background: {{tag.get('color')}}" - title="{{tag.get('name')}}" - tg-repeat="tag in project.get('colorized_tags') track by tag.get('name')" - ) .project-card-inner( href="#" tg-nav="project:project=project.get('slug')" diff --git a/app/modules/projects/project/project.jade b/app/modules/projects/project/project.jade index cc3acb5a..01feadbc 100644 --- a/app/modules/projects/project/project.jade +++ b/app/modules/projects/project/project.jade @@ -43,11 +43,8 @@ div.wrapper p.description {{vm.project.get('description')}} div.single-project-tags.tags-container(ng-if="::vm.project.get('tags').size") - span.tag( - style='border-left: 5px solid {{::tag.get("color")}};', - tg-repeat="tag in ::vm.project.get('colorized_tags')" - ) - span.tag-name {{::tag.get('name')}} + span.tag(tg-repeat="tag in ::vm.project.get('tags')") + span.tag-name {{::tag}} div.project-data section.timeline(ng-if="vm.project") @@ -60,7 +57,9 @@ div.wrapper title="{{'PROJECT.LOOKING_FOR_PEOPLE' | translate}}" ) h3 {{'PROJECT.LOOKING_FOR_PEOPLE' | translate}} - p(ng-if="vm.project.get('looking_for_people_note')") {{::vm.project.get('looking_for_people_note')}} + p(ng-if="vm.project.get('looking_for_people_note')") + | {{::vm.project.get('looking_for_people_note')}} + h2.title {{"PROJECT.SECTION.TEAM" | translate}} ul.involved-team li(tg-repeat="member in vm.members") diff --git a/app/modules/projects/projects.service.coffee b/app/modules/projects/projects.service.coffee index ab3973a3..3c1415d5 100644 --- a/app/modules/projects/projects.service.coffee +++ b/app/modules/projects/projects.service.coffee @@ -20,6 +20,7 @@ taiga = @.taiga groupBy = @.taiga.groupBy + class ProjectsService extends taiga.Service @.$inject = ["tgResources", "$projectUrl", "tgLightboxFactory"] @@ -42,16 +43,6 @@ class ProjectsService extends taiga.Service 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 diff --git a/app/modules/projects/projects.service.spec.coffee b/app/modules/projects/projects.service.spec.coffee index 1829e8a8..80a98389 100644 --- a/app/modules/projects/projects.service.spec.coffee +++ b/app/modules/projects/projects.service.spec.coffee @@ -129,8 +129,7 @@ describe "tgProjectsService", -> 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'}] + tags_colors: {xx: "red", yy: "blue", aa: "white"} } ) @@ -157,8 +156,7 @@ describe "tgProjectsService", -> 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'}] + tags_colors: {xx: "red", yy: "blue", aa: "white"} } ]) diff --git a/app/partials/admin/admin-project-profile.jade b/app/partials/admin/admin-project-profile.jade index b03e2890..8adefd42 100644 --- a/app/partials/admin/admin-project-profile.jade +++ b/app/partials/admin/admin-project-profile.jade @@ -72,10 +72,15 @@ div.wrapper( ) fieldset label(for="tags") {{ 'ADMIN.PROJECT_PROFILE.TAGS' | translate }} - div.tags-block( - ng-if="project.id" - tg-lb-tag-line - ng-model="project.tags" + + tg-tag-line-common.tags-block( + disable-color-selection + ng-if="project" + project="project" + tags="projectTags" + permissions="modify_project" + on-add-tag="ctrl.addTag(name, color)" + on-delete-tag="ctrl.deleteTag(tag)" ) fieldset(ng-if="project.owner.id != user.id") diff --git a/app/partials/admin/admin-project-values-tags.jade b/app/partials/admin/admin-project-values-tags.jade index fc52290b..ff8830d9 100644 --- a/app/partials/admin/admin-project-values-tags.jade +++ b/app/partials/admin/admin-project-values-tags.jade @@ -13,55 +13,20 @@ div.wrapper( sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-tags") include ../includes/modules/admin-submenu-project-values - section.main.admin-common.admin-attributes.colors-table - include ../includes/components/mainTitle - p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_TAGS.SUBTITLE") - - .admin-attributes-section( - ng-controller="ProjectTagsController as ctrl" - ) - .admin-attributes-section-wrapper-empty( - ng-if="!projectTags.length" - tg-loading="ctrl.loading" - ) - p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY") - .admin-attributes-section-wrapper( - ng-if="projectTags.length" - ) - .table-header.table-tags-editor - .row - .color-column(translate="COMMON.FIELDS.COLOR") - .color-name(translate="COMMON.FIELDS.NAME") - .color-filter - input.e2e-tags-filter( - type="text" - name="name" - ng-model="tagsFilter.name" - ng-model-options="{debounce: 200}" - ) - tg-svg( - svg-icon="icon-search" - ) - - p.admin-attributes-section-wrapper-empty( - tg-loading="ctrl.loading" - translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY_SEARCH" - ng-if="!(projectTags | filter:tagsFilter).length" + section.main.admin-common.admin-attributes.colors-table.tags-table( + tg-project-tags, + ng-controller="ProjectTagsController as ctrl" + ) + header.header-with-actions + .title + include ../includes/components/mainTitle + p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_TAGS.SUBTITLE") + .action-buttons + a.button.button-green.show-add-new( + href="" + title="{{ 'ADMIN.PROJECT_VALUES_TAGS.NEW_TAG'|translate }}" + translate="ADMIN.PROJECT_VALUES_TAGS.NEW_TAG" ) - .table-main( - ng-repeat="tag in projectTags | filter:tagsFilter" - tg-bind-scope - ) - form( - tg-project-tag - ng-model="tag" - ) - .row.edition.no-draggable - .color-column( - tg-color-selection - ng-model="tag" - ) - .current-color(ng-style="{background: tag.color}") - include ../includes/components/select-color - .color-name {{ tag.name }} + .admin-attributes-section + include ../includes/modules/admin/project-tags diff --git a/app/partials/common/tag/lb-tag-line-tags.jade b/app/partials/common/tag/lb-tag-line-tags.jade index cfe107d7..af7568f9 100644 --- a/app/partials/common/tag/lb-tag-line-tags.jade +++ b/app/partials/common/tag/lb-tag-line-tags.jade @@ -1,5 +1,8 @@ <% _.each(tags, function(tag) { %> -span(class="tag", style!="<%- tag.style %>") +span( + class="tag" + style!="<%- tag.style %>" +) span.tag-name <%- tag.name %> a.remove-tag(href="", title="{{'COMMON.TAGS.DELETE' | translate}}") tg-svg(svg-icon="icon-close") diff --git a/app/partials/common/tag/tags-line-tags.jade b/app/partials/common/tag/tags-line-tags.jade index 53ee66ac..07f3a981 100644 --- a/app/partials/common/tag/tags-line-tags.jade +++ b/app/partials/common/tag/tags-line-tags.jade @@ -2,7 +2,7 @@ <% if (tag.name == deleteTagLoading) { %> div(tg-loading="true") <% } else { %> -span.tag(style!="border-left: 5px solid <%- tag.color %>;") +span.tag(style!="border-left: 5px solid <%- tag.style %>;") span.tag-name <%- tag.name %> <% if (isEditable) { %> a.remove-tag( diff --git a/app/partials/includes/components/select-color.jade b/app/partials/includes/components/select-color.jade index 3f9f4306..448455fa 100644 --- a/app/partials/includes/components/select-color.jade +++ b/app/partials/includes/components/select-color.jade @@ -20,7 +20,9 @@ div.popover.select-color li.color(style="background: #ef2929", data-color="#ef2929") li.color(style="background: #cc0000", data-color="#cc0000") li.color(style="background: #a40000", data-color="#a40000") - li.color(style="background: #2e3436", data-color="#2e3436") + li.color(style="background: #2e3436", data-color="#2e3436", ng-if="!allowEmpty") + li.color(data-color="", ng-class="{'empty-color': allowEmpty}") input(type="text", placeholder="personalized colors", ng-model="color") - div.selected-color(ng-style="{'background-color': color}") + div.selected-color(ng-style="{'background-color': color}", ng-if="color !== null") + div.selected-color(ng-style="{'background-color': none}", ng-if="color === null") diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade new file mode 100644 index 00000000..3952d08c --- /dev/null +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -0,0 +1,175 @@ +section + .admin-tags-section-wrapper-empty( + ng-show="!projectTagsAll.length" + tg-loading="ctrl.loading" + ) + p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY") + + .admin-tags-section-wrapper( + ng-show="projectTagsAll.length" + ) + form.add-tag-container.new-value.hidden + tg-color-selection.color-column( + tg-allow-empty="true" + ng-model="newValue" + ) + .current-color( + ng-style="{background: newValue.color}" + ng-if="newValue.color" + ) + .current-color.empty-color(ng-if="!newValue.color") + include ../../components/select-color + + .tag-name + input( + name="name" + type="text" + placeholder="{{'ADMIN.TYPES.PLACEHOLDER_WRITE_NAME' | translate}}", + ng-model="newValue.name" + data-required="true" + data-maxlength="255" + ) + + .options-column + a.add-new.e2e-save(href="") + tg-svg( + title="{{'COMMON.ADD' | translate}}", + svg-icon="icon-save" + ) + a.delete-new(href="") + tg-svg( + title="{{'COMMON.CANCEL' | translate}}", + svg-icon="icon-close" + ) + + .table-header.table-tags-editor + div.row.header-tag-row + .color-column(translate="COMMON.FIELDS.COLOR") + .status-name(translate="COMMON.FIELDS.NAME") + .color-filter + input.e2e-tags-filter( + type="text" + name="name" + ng-model="tagsFilter.name" + ng-model-options="{debounce: 200}" + ) + tg-svg( + svg-icon="icon-search" + ) + + .table-main.table-admin-tags + div(ng-show="!mixingTags.toTag") + .admin-attributes-section-wrapper-empty( + ng-show="!projectTags.length" + tg-loading="ctrl.loading" + ) + p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY_SEARCH") + p lalalaal + + div( + ng-repeat="tag in projectTags" + tg-bind-scope + ) + form(tg-bind-scope) + .row.tag-row.table-main.visualization(ng-class="{{ ctrl.mixingClass(tag) }}") + .color-column + .current-color( + ng-style="{background: tag.color}" + ng-if="tag.color" + ) + .current-color.empty-color(ng-if="!tag.color") + + .status-name + span(tg-bo-html="tag.name") + + .options-column + a.mix-tags(href="") + tg-svg( + title="{{'ADMIN.PROJECT_VALUES_TAGS.MIXING_MERGE' | translate}}" + svg-icon="icon-merge" + ) + div.popover.merge-explanation + span(translate="ADMIN.PROJECT_VALUES_TAGS.MIXING_MERGE") + + a.edit-value(href="") + tg-svg( + svg-icon="icon-edit" + title="{{'ADMIN.COMMON.TITLE_ACTION_EDIT_VALUE' | translate}}" + ) + + a.delete-tag(href="") + tg-svg( + svg-icon="icon-trash" + title="{{'ADMIN.COMMON.TITLE_ACTION_DELETE_VALUE' | translate}}" + ) + + .row.tag-row.table-main.edition.hidden + .color-column( + tg-color-selection + tg-allow-empty="true" + ng-model="tag" + ) + .current-color( + ng-style="{background: tag.color}" + ng-if="tag.color" + ) + .current-color.empty-color(ng-if="!tag.color") + include ../../components/select-color + + .status-name + input( + name="name" + type="text" + placeholder="{{'ADMIN.TYPES.PLACEHOLDER_WRITE_NAME' | translate}}", + ng-model="tag.name" + data-required="true" + data-maxlength="255" + ) + + .options-column + a.save.e2e-save(href="") + tg-svg( + title="{{'COMMON.SAVE' | translate}}" + svg-icon="icon-save" + ) + a.cancel(href="") + tg-svg( + title="{{'COMMON.CANCEL' | translate}}" + svg-icon="icon-close" + ) + + div(ng-show="mixingTags.toTag") + div( + ng-repeat="tag in projectTags" + tg-bind-scope + ) + form(tg-bind-scope) + .row.mixing-row.table-main.visualization(class="{{ ctrl.mixingClass(tag) }}") + .color-column + .current-color(ng-style="{background: tag.color}") + + .status-name + span(tg-bo-html="tag.name") + + .mixing-options-column + .mixing-help-text( + ng-if="mixingTags.toTag === tag.name" + translate="ADMIN.PROJECT_VALUES_TAGS.MIXING_HELP_TEXT" + ) + a.mixing-confirm.button-green( + href="" + ng-if="mixingTags.toTag === tag.name && mixingTags.fromTags.length" + translate="ADMIN.PROJECT_VALUES_TAGS.MIXING_MERGE" + ) + a.mixing-cancel.button-gray( + href="" + ng-if="mixingTags.toTag === tag.name" + translate="COMMON.CANCEL" + ) + span.mixing-selected( + ng-if="mixingTags.fromTags.indexOf(tag.name) !== -1" + ) + tg-svg( + title="{{'ADMIN.PROJECT_VALUES_TAGS.SELECTED' | translate}}" + svg-icon="icon-merge" + ) diff --git a/app/partials/includes/modules/lightbox-create-issue.jade b/app/partials/includes/modules/lightbox-create-issue.jade index d1f4f37b..69209b57 100644 --- a/app/partials/includes/modules/lightbox-create-issue.jade +++ b/app/partials/includes/modules/lightbox-create-issue.jade @@ -29,9 +29,13 @@ form ) fieldset - .tags-block( - tg-lb-tag-line - ng-model="issue.tags" + tg-tag-line-common.tags-block( + ng-if="project" + project="project" + tags="issue.tags" + permissions="add_issue" + on-add-tag="addTag(name, color)" + on-delete-tag="deleteTag(tag)" ) fieldset diff --git a/app/partials/includes/modules/lightbox-task-create-edit.jade b/app/partials/includes/modules/lightbox-task-create-edit.jade index 41fcf957..3cd129b5 100644 --- a/app/partials/includes/modules/lightbox-task-create-edit.jade +++ b/app/partials/includes/modules/lightbox-task-create-edit.jade @@ -30,9 +30,13 @@ form ) fieldset - div.tags-block( - tg-lb-tag-line - ng-model="task.tags" + tg-tag-line-common.tags-block( + ng-if="project" + project="project" + tags="task.tags" + permissions="add_task" + on-add-tag="addTag(name, color)" + on-delete-tag="deleteTag(tag)" ) fieldset diff --git a/app/partials/includes/modules/lightbox-us-create-edit.jade b/app/partials/includes/modules/lightbox-us-create-edit.jade index 7345e627..d33b86b1 100644 --- a/app/partials/includes/modules/lightbox-us-create-edit.jade +++ b/app/partials/includes/modules/lightbox-us-create-edit.jade @@ -24,9 +24,13 @@ form ) fieldset - div.tags-block( - tg-lb-tag-line - ng-model="us.tags" + tg-tag-line-common.tags-block( + ng-if="project" + project="project" + tags="us.tags" + permissions="add_us" + on-add-tag="addTag(name, color)" + on-delete-tag="deleteTag(tag)" ) fieldset diff --git a/app/partials/issue/issues-detail.jade b/app/partials/issue/issues-detail.jade index ab8bce2c..1b4185f0 100644 --- a/app/partials/issue/issues-detail.jade +++ b/app/partials/issue/issues-detail.jade @@ -64,10 +64,11 @@ div.wrapper( svg-icon="icon-arrow-right" ) .subheader - .tags-block( - tg-tag-line - ng-model="issue" - required-perm="modify_issue" + tg-tag-line.tags-block( + ng-if="issue && project" + project="project" + item="issue" + permissions="modify_issue" ) tg-created-by-display.ticket-created-by(ng-model="issue") @@ -92,7 +93,7 @@ div.wrapper( project-id="projectId" edit-permission = "modify_issue" ) - + tg-history-section( ng-if="issue" type="issue" diff --git a/app/partials/task/task-detail.jade b/app/partials/task/task-detail.jade index 68d7dff9..6ec12a6b 100644 --- a/app/partials/task/task-detail.jade +++ b/app/partials/task/task-detail.jade @@ -75,7 +75,12 @@ div.wrapper( ) tg-svg(svg-icon="icon-arrow-right") .subheader - div.tags-block(tg-tag-line, ng-model="task", required-perm="modify_task") + tg-tag-line.tags-block( + ng-if="task && project" + project="project" + item="task" + permissions="modify_task" + ) tg-created-by-display.ticket-created-by(ng-model="task") section.duty-content(tg-editable-description, tg-editable-wysiwyg, ng-model="task", required-perm="modify_task") @@ -94,7 +99,7 @@ div.wrapper( project-id="projectId" edit-permission = "modify_task" ) - + tg-history-section( ng-if="task" type="task" diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index 54976f35..8baf2bec 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -68,7 +68,12 @@ div.wrapper( ) tg-svg(svg-icon="icon-arrow-right") .subheader - .tags-block(tg-tag-line, ng-model="us", required-perm="modify_us") + tg-tag-line.tags-block( + ng-if="us && project" + project="project" + item="us" + permissions="modify_us" + ) tg-created-by-display.ticket-created-by(ng-model="us") section.duty-content(tg-editable-description, tg-editable-wysiwyg, ng-model="us", required-perm="modify_us") diff --git a/app/styles/components/select-color.scss b/app/styles/components/select-color.scss index dc329393..92941c0c 100644 --- a/app/styles/components/select-color.scss +++ b/app/styles/components/select-color.scss @@ -19,6 +19,9 @@ height: 35px; width: 35px; } + .empty-color { + @include empty-color(33); + } ul { float: left; margin-bottom: 1rem; diff --git a/app/styles/components/tag.scss b/app/styles/components/tag.scss deleted file mode 100644 index 810eb23d..00000000 --- a/app/styles/components/tag.scss +++ /dev/null @@ -1,108 +0,0 @@ -.tag { - @include font-type(light); - @include font-size(small); - background: $mass-white; - border-radius: 0 5px 5px 0; - color: $grayer; - display: inline-block; - margin: 0 .5rem .5rem 0; - padding: .5rem; - text-align: center; - .icon-delete { - color: $gray-light; - margin-left: 1rem; - &:hover { - color: $red; - } - } -} - -.ui-autocomplete { - background: $white; - border: 1px solid $gray-light; - z-index: 99910; - .ui-state-focus { - background: $primary-light; - } - li { - cursor: pointer; - } -} - -.ui-helper-hidden-accessible { - display: none; -} - -.tags-block { - align-content: center; - display: flex; - flex-wrap: wrap; - .tags-container { - align-items: center; - display: flex; - flex-wrap: wrap; - } - .add-tag-input { - align-items: flex-start; - display: flex; - flex-grow: 0; - flex-shrink: 0; - width: 250px; - .icon-save { - margin-top: .5rem; - } - } - input { - margin-right: .25rem; - padding: .4rem; - width: 14rem; - } - .save { - cursor: pointer; - display: inline-block; - margin-left: .5rem; - } - .icon-save { - @include svg-size(); - fill: $grayer; - &:hover { - fill: $primary; - transition: .2s linear; - } - } - .loading-spinner { - margin-right: .5rem; - } - .tag { - @include font-size(small); - margin: 0 .5rem .5rem 0; - padding: .5rem; - } - .icon-close { - @include svg-size(.7rem); - fill: $gray-light; - margin-left: .25rem; - &:hover { - cursor: pointer; - fill: $red; - } - } - .add-tag { - color: $gray-light; - display: inline-block; - &:hover { - color: $primary-light; - } - } - .icon-add { - @include svg-size(.9rem); - margin-right: .25rem; - margin-top: .5rem; - } - .add-tag-text { - @include font-size(small); - } - .remove-tag { - display: inline-block; - } -} diff --git a/app/styles/dependencies/mixins/empty-color.scss b/app/styles/dependencies/mixins/empty-color.scss new file mode 100644 index 00000000..9c49729b --- /dev/null +++ b/app/styles/dependencies/mixins/empty-color.scss @@ -0,0 +1,39 @@ +@function sqrt($r) { + $x0: 1; + $x1: $x0; + + @for $i from 1 through 10 { + $x1: $x0 - ($x0 * $x0 - abs($r)) / (2 * $x0); + $x0: $x1; + } + + @return round($x1); +} + +@mixin empty-color($width) { + background: $mass-white; + border: 1px solid $whitish; + position: relative; + &:after { + content: ""; + width: 2px; + height: #{sqrt(2*$width*$width)}px; + background: #ff8282; + transform: rotate(-45deg); + position: absolute; + top: 0; + left: 0; + transform-origin: top; + } + &:before { + content: ""; + width: 2px; + height: #{sqrt(2*$width*$width)}px; + background: #ff8282; + transform: rotate(45deg); + position: absolute; + top: 0; + right: 0; + transform-origin: top; + } +} diff --git a/app/styles/dependencies/mixins/popover.scss b/app/styles/dependencies/mixins/popover.scss index 19fd9038..595896a7 100644 --- a/app/styles/dependencies/mixins/popover.scss +++ b/app/styles/dependencies/mixins/popover.scss @@ -1,4 +1,15 @@ -@mixin popover($width, $top: '', $left: '', $bottom: '', $right: '', $arrow-width: 0, $arrow-top: '', $arrow-left: '', $arrow-bottom: '', $arrow-height: 15px) { +@mixin popover( + $width, + $top: '', + $left: '', + $bottom: '', + $right: '', + $arrow-width: 0, + $arrow-top: '', + $arrow-left: '', + $arrow-bottom: '', + $arrow-height: 15px +) { @include font-type(light); @include font-size(small); background: $blackish; @@ -14,6 +25,7 @@ top: #{$top}; width: $width; z-index: 99; + text-align: center; a { @include font-size(small); border-bottom: 1px solid $grayer; diff --git a/app/styles/extras/dependencies.scss b/app/styles/extras/dependencies.scss index a7fff44b..2d94ceff 100644 --- a/app/styles/extras/dependencies.scss +++ b/app/styles/extras/dependencies.scss @@ -21,3 +21,4 @@ @import '../dependencies/mixins/slide'; @import '../dependencies/mixins/svg'; @import '../dependencies/mixins/track-buttons'; +@import '../dependencies/mixins/empty-color'; diff --git a/app/styles/layout/admin-project-tags.scss b/app/styles/layout/admin-project-tags.scss index f341214f..c4c1b136 100644 --- a/app/styles/layout/admin-project-tags.scss +++ b/app/styles/layout/admin-project-tags.scss @@ -1,20 +1,109 @@ -.table-tags-editor { - input[type="text"] { - background-color: transparent; - border: 0; - border-bottom: 1px solid transparent; - box-shadow: none; - transition: border-bottom .2s linear; - &:focus { - border-bottom: 1px solid $gray; - outline: none; - } - } - .color-filter { - align-items: center; - display: flex; - flex-grow: 1; - padding: 0 10px; +.add-tag-container { + align-items: center; + background: $mass-white; + display: flex; + margin: .5rem 0; + padding: 1rem; + .color-column { + cursor: pointer; + flex-basis: 60px; + flex-grow: 0; + flex-shrink: 0; position: relative; } + .tag-name { + flex-basis: 80%; + margin-right: 1rem; + } + .options-column { + display: flex; + } + .current-color { + &.empty-color { + @include empty-color(38); + } + } + input[type="text"] { + background: $white; + } + .icon { + opacity: 1; + } +} + +.tags-table { + .table-tags-editor { + input[type="text"] { + background-color: transparent; + border: 0; + border-bottom: 1px solid transparent; + box-shadow: none; + transition: border-bottom .2s linear; + &:focus { + border-bottom: 1px solid $gray; + outline: none; + } + } + .row { + &.header-tag-row { + padding-left: 1rem; + } + } + .color-filter { + align-items: center; + display: flex; + flex-grow: 1; + padding: 0 10px; + position: relative; + input { + padding: 0; + } + } + } + .row { + &.tag-row { + margin: .3rem 0; + padding: .7rem; + &:hover { + cursor: default; + } + } + } + .mix-tags { + position: relative; + .popover { + @include popover(120px, '', '', 2rem, -85%, 1rem, '', 50%, -5px); + } + &:hover { + .popover { + display: block; + } + } + } + .mixing-options-column { + text-align: right; + } + .mixing-tags-from, + .mixing-tags-to { + background: lighten(rgba($primary-light, .2), 30%); + } + .mixing-confirm { + margin: 0 .5rem; + } + .mixing-help-text { + @include font-size(xsmall); + color: $primary-dark; + display: inline; + padding-right: .5rem; + text-align: center; + @include breakpoint(laptop) { + display: block; + padding: .5rem; + } + } + .current-color { + &.empty-color { + @include empty-color(38); + } + } } diff --git a/app/styles/layout/admin-project-values.scss b/app/styles/layout/admin-project-values.scss index cae0e99f..2d17fb9e 100644 --- a/app/styles/layout/admin-project-values.scss +++ b/app/styles/layout/admin-project-values.scss @@ -11,7 +11,7 @@ width: 100%; } } - .admin-attributes-section-wrapper-empty { + .admin-tags-section-wrapper-empty { color: $gray-light; padding: 10vh 0 0; text-align: center; diff --git a/app/styles/modules/common/colors-table.scss b/app/styles/modules/common/colors-table.scss index bc5d0d36..42df79c6 100644 --- a/app/styles/modules/common/colors-table.scss +++ b/app/styles/modules/common/colors-table.scss @@ -15,7 +15,6 @@ } .row { align-items: center; - border-bottom: 1px solid $whitish; display: flex; justify-content: center; padding: 1rem; @@ -27,15 +26,11 @@ cursor: pointer; } } - &.edition, - &.new-value { - padding-left: 50px; - } &.hidden { display: none; } &:hover { - background: lighten($primary, 60%); + background: lighten(rgba($primary-light, .2), 30%); cursor: move; transition: background .2s ease-in; .icon { @@ -110,16 +105,10 @@ } .options-column { a { + cursor: pointer; display: inline-block; } } - form { - &:last-child { - .row { - border: 0; - } - } - } .row-edit { .options-column { @@ -133,13 +122,14 @@ height: 40px; width: 40px; } + .icon { cursor: pointer; fill: $gray-light; margin-right: 1rem; opacity: 0; &:hover { - fill: $primary; + fill: $primary-light; transition: all .2s ease-in; } &.icon-check { @@ -147,6 +137,10 @@ fill: $primary; opacity: 1; } + &.icon-merge { + cursor: default; + opacity: 1; + } &.icon-search { cursor: none; fill: $primary; @@ -156,9 +150,7 @@ cursor: move; } &.icon-trash { - &:hover { - fill: $red-light; - } + fill: $red-light; } } .gu-mirror { diff --git a/app/svg/sprite.svg b/app/svg/sprite.svg index ff8ef151..8fa6cc2a 100644 --- a/app/svg/sprite.svg +++ b/app/svg/sprite.svg @@ -429,6 +429,7 @@ fill="#fff" d="M511.998 107.939c-222.856 0-404.061 181.204-404.061 404.061s181.205 404.061 404.061 404.061c222.856 0 404.061-181.203 404.061-404.061s-181.205-404.061-404.061-404.061zM511.998 158.447c88.671 0 169.621 32.484 231.616 86.222l-498.947 498.948c-53.74-61.998-86.223-142.945-86.223-231.617 0-195.561 157.992-353.553 353.553-353.553zM779.328 280.383c53.74 61.998 86.223 142.945 86.223 231.617 0 195.561-157.992 353.553-353.553 353.553-88.671 0-169.617-32.484-231.616-86.222l498.947-498.948z"> +<<<<<<< b929b5ecdaefcb0f2430ea5c8e41ce8fdcdbe761 Add user View more + + Merge + + + + Fill + diff --git a/e2e/helpers/backlog-helper.js b/e2e/helpers/backlog-helper.js index 954ec830..932e1c34 100644 --- a/e2e/helpers/backlog-helper.js +++ b/e2e/helpers/backlog-helper.js @@ -19,8 +19,21 @@ helper.getCreateEditUsLightbox = function() { subject: function() { return el.$('input[name="subject"]'); }, - tags: function() { - return el.$('.tag-input'); + tags: async function() { + $('.e2e-show-tag-input').click(); + $('.e2e-open-color-selector').click(); + + $$('.e2e-color-dropdown li').get(1).click(); + $('.e2e-add-tag-input') + .sendKeys('xxxyy') + .sendKeys(protractor.Key.ENTER); + + $$('.e2e-delete-tag').last().click(); + + $('.e2e-add-tag-input') + .sendKeys('a') + .sendKeys(protractor.Key.ARROW_DOWN) + .sendKeys(protractor.Key.ENTER); }, description: function() { return el.$('textarea[name="description"]'); diff --git a/e2e/helpers/common-helper.js b/e2e/helpers/common-helper.js index d2e24891..83762161 100644 --- a/e2e/helpers/common-helper.js +++ b/e2e/helpers/common-helper.js @@ -63,3 +63,20 @@ helper.lightboxAttachment = async function() { expect(countAttachments + 1).to.be.equal(newCountAttachments); }; + +helper.tags = function() { + $('.e2e-show-tag-input').click(); + $('.e2e-open-color-selector').click(); + + $$('.e2e-color-dropdown li').get(1).click(); + $('.e2e-add-tag-input') + .sendKeys('xxxyy') + .sendKeys(protractor.Key.ENTER); + + $$('.e2e-delete-tag').last().click(); + + $('.e2e-add-tag-input') + .sendKeys('a') + .sendKeys(protractor.Key.ARROW_DOWN) + .sendKeys(protractor.Key.ENTER); +} diff --git a/e2e/helpers/detail-helper.js b/e2e/helpers/detail-helper.js index 7e2a4ee2..1148d21c 100644 --- a/e2e/helpers/detail-helper.js +++ b/e2e/helpers/detail-helper.js @@ -57,34 +57,35 @@ helper.description = function(){ helper.tags = function() { - let el = $('div[tg-tag-line]'); + let el = $('tg-tag-line-common'); let obj = { el:el, clearTags: async function() { - let tags = await el.$$('.icon-delete'); + let tags = await el.$$('.e2e-delete-tag'); let totalTags = tags.length; let htmlChanges = null; while (totalTags > 0) { htmlChanges = await utils.common.outerHtmlChanges(el.$(".tags-container")); - await el.$$('.icon-delete').first().click(); + await el.$$('.e2e-delete-tag').first().click(); totalTags --; await htmlChanges(); } }, getTagsText: function() { - return el.$$('.tag-name').getText(); + return el.$$('tg-tag span').getText(); }, addTags: async function(tags) { let htmlChanges = null - el.$('.add-tag').click(); + $('.e2e-show-tag-input').click(); + for (let tag of tags){ htmlChanges = await utils.common.outerHtmlChanges(el.$(".tags-container")); - el.$('.tag-input').sendKeys(tag); + el.$('.e2e-add-tag-input').sendKeys(tag); await browser.actions().sendKeys(protractor.Key.ENTER).perform(); await htmlChanges(); } diff --git a/e2e/suites/backlog.e2e.js b/e2e/suites/backlog.e2e.js index 9df1235b..9ec2b4ee 100644 --- a/e2e/suites/backlog.e2e.js +++ b/e2e/suites/backlog.e2e.js @@ -49,11 +49,7 @@ describe('backlog', function() { createUSLightbox.status(2).click(); // tags - createUSLightbox.tags().sendKeys('aaa'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); - - createUSLightbox.tags().sendKeys('bbb'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); + commonHelper.tags(); // description createUSLightbox.description().sendKeys('test test'); @@ -245,7 +241,7 @@ describe('backlog', function() { expect(elementRef1).to.be.equal(draggedRefs[1]); }); - it.skip('drag multiple us to milestone', async function() { + it('drag multiple us to milestone', async function() { let sprint = backlogHelper.sprints().get(0); let initUssSprintCount = await backlogHelper.getSprintUsertories(sprint).count(); diff --git a/e2e/suites/issues/issues.e2e.js b/e2e/suites/issues/issues.e2e.js index 785badac..264b4300 100644 --- a/e2e/suites/issues/issues.e2e.js +++ b/e2e/suites/issues/issues.e2e.js @@ -38,11 +38,7 @@ describe('issues list', function() { createIssueLightbox.subject().sendKeys('subject'); // tags - await createIssueLightbox.tags().sendKeys('aaa'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); - - await createIssueLightbox.tags().sendKeys('bbb'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); + commonHelper.tags(); }); it('upload attachments', commonHelper.lightboxAttachment); diff --git a/e2e/suites/kanban.e2e.js b/e2e/suites/kanban.e2e.js index 3ea516c4..85d100f3 100644 --- a/e2e/suites/kanban.e2e.js +++ b/e2e/suites/kanban.e2e.js @@ -74,11 +74,7 @@ describe('kanban', function() { expect(totalPoints).to.be.equal('4'); // tags - createUSLightbox.tags().sendKeys('www'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); - - createUSLightbox.tags().sendKeys('xxx'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); + commonHelper.tags(); // description createUSLightbox.description().sendKeys(formFields.description); diff --git a/e2e/suites/tasks/taskboard.e2e.js b/e2e/suites/tasks/taskboard.e2e.js index 3220860b..61a66b08 100644 --- a/e2e/suites/tasks/taskboard.e2e.js +++ b/e2e/suites/tasks/taskboard.e2e.js @@ -66,11 +66,7 @@ describe('taskboard', function() { createTaskLightbox.subject().sendKeys(formFields.subject); createTaskLightbox.description().sendKeys(formFields.description); - createTaskLightbox.tags().sendKeys('aaa'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); - - createTaskLightbox.tags().sendKeys('bbb'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); + commonHelper.tags(); await createTaskLightbox.blocked().click(); await createTaskLightbox.blockedNote().sendKeys(formFields.blockedNote); From c583dd15b63af2cbcc7ea6bc80442520a4cb1359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 23 Aug 2016 13:58:27 +0200 Subject: [PATCH 120/315] Fix some errors in the admin tag panel --- .../modules/admin/project-values.coffee | 20 ++++++++++--------- .../includes/modules/admin/project-tags.jade | 6 +++--- app/styles/layout/admin-project-tags.scss | 1 + 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index fade389f..cf25e9c7 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -716,7 +716,8 @@ class ProjectTagsController extends taiga.Controller loadTags: => return @rs.projects.tagsColors(@scope.projectId).then (tags) => - @scope.projectTagsAll = _.map(tags.getAttrs(), (color, name) => @model.make_model('tag', {name: name, color: color})) + @scope.projectTagsAll = _.map(tags.getAttrs(), (color, name) => + @model.make_model('tag', {name: name, color: color})) @.filterAndSortTags() @.loading = false @@ -782,7 +783,7 @@ ProjectTagsDirective = ($log, $repo, $confirm, $location, animationFrame, $trans initializeNewValue = -> $scope.newValue = { - "name": "" + "tag": "" "color": "" } @@ -827,13 +828,13 @@ ProjectTagsDirective = ($log, $repo, $confirm, $location, animationFrame, $trans promise = $ctrl.editTag(originalTag.name, tag.name, tag.color) promise.then => + $ctrl.loadTags() row = target.parents(".row.table-main") row.addClass("hidden") row.siblings(".visualization").removeClass('hidden') - $ctrl.loadTags() - promise.then null, (data) -> - form.setErrors(data) + promise.then null, (response) -> + form.setErrors(response.data) saveNewValue = (target) -> formEl = target.parents("form") @@ -841,14 +842,14 @@ ProjectTagsDirective = ($log, $repo, $confirm, $location, animationFrame, $trans form = formEl.checksley() return if not form.validate() - promise = $ctrl.createTag($scope.newValue.name, $scope.newValue.color) + promise = $ctrl.createTag($scope.newValue.tag, $scope.newValue.color) promise.then (data) => target.addClass("hidden") $ctrl.loadTags() initializeNewValue() - promise.then null, (data) -> - form.setErrors(data) + promise.then null, (response) -> + form.setErrors(response.data) cancel = (target) -> row = target.parents(".row.table-main") @@ -964,4 +965,5 @@ ProjectTagsDirective = ($log, $repo, $confirm, $location, animationFrame, $trans return {link:link} -module.directive("tgProjectTags", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", "$translate", "$rootScope", ProjectTagsDirective]) +module.directive("tgProjectTags", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", + "$translate", "$rootScope", ProjectTagsDirective]) diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index 3952d08c..758925b2 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -22,10 +22,10 @@ section .tag-name input( - name="name" + name="tag" type="text" placeholder="{{'ADMIN.TYPES.PLACEHOLDER_WRITE_NAME' | translate}}", - ng-model="newValue.name" + ng-model="newValue.tag" data-required="true" data-maxlength="255" ) @@ -118,7 +118,7 @@ section .status-name input( - name="name" + name="to_tag" type="text" placeholder="{{'ADMIN.TYPES.PLACEHOLDER_WRITE_NAME' | translate}}", ng-model="tag.name" diff --git a/app/styles/layout/admin-project-tags.scss b/app/styles/layout/admin-project-tags.scss index c4c1b136..3880e826 100644 --- a/app/styles/layout/admin-project-tags.scss +++ b/app/styles/layout/admin-project-tags.scss @@ -46,6 +46,7 @@ } .row { &.header-tag-row { + cursor: default; padding-left: 1rem; } } From 4f51bd953f51804d2a89c788c93a03cb20ab9c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 23 Aug 2016 14:58:34 +0200 Subject: [PATCH 121/315] Fix error: show add tag form if project has no ntags --- .../includes/modules/admin/project-tags.jade | 20 ++++++++++++------- app/styles/layout/admin-project-tags.scss | 5 +++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index 758925b2..485e6326 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -1,13 +1,10 @@ section .admin-tags-section-wrapper-empty( - ng-show="!projectTagsAll.length" + ng-if="!projectTagsAll.length && ctrl.loading" tg-loading="ctrl.loading" ) - p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY") - .admin-tags-section-wrapper( - ng-show="projectTagsAll.length" - ) + .admin-tags-section-wrapper form.add-tag-container.new-value.hidden tg-color-selection.color-column( tg-allow-empty="true" @@ -42,7 +39,14 @@ section svg-icon="icon-close" ) - .table-header.table-tags-editor + p.tags-empty( + ng-if="!projectTagsAll.length && !ctrl.loading" + translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY" + ) + + .table-header.table-tags-editor( + ng-if="projectTagsAll.length" + ) div.row.header-tag-row .color-column(translate="COMMON.FIELDS.COLOR") .status-name(translate="COMMON.FIELDS.NAME") @@ -57,7 +61,9 @@ section svg-icon="icon-search" ) - .table-main.table-admin-tags + .table-main.table-admin-tags( + ng-if="projectTagsAll.length" + ) div(ng-show="!mixingTags.toTag") .admin-attributes-section-wrapper-empty( ng-show="!projectTags.length" diff --git a/app/styles/layout/admin-project-tags.scss b/app/styles/layout/admin-project-tags.scss index 3880e826..0fdeb511 100644 --- a/app/styles/layout/admin-project-tags.scss +++ b/app/styles/layout/admin-project-tags.scss @@ -1,3 +1,8 @@ +.tags-empty { + padding: 10vh 0 0; + text-align: center; +} + .add-tag-container { align-items: center; background: $mass-white; From 17bbdfa1badc5b6524c3e7bf096ca4edf85583b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 24 Aug 2016 19:15:46 +0200 Subject: [PATCH 122/315] Fix tgTagLineCommon directive if colors are disabled --- .../tag-line-common/tag-line-common.controller.coffee | 11 ++++++++--- .../tag-line-common/tag-line-common.directive.coffee | 4 +++- .../tags/tag-line-common/tag-line-common.jade | 4 ++-- app/partials/admin/admin-project-profile.jade | 3 +-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/modules/components/tags/tag-line-common/tag-line-common.controller.coffee b/app/modules/components/tags/tag-line-common/tag-line-common.controller.coffee index 26082ae3..dde598ea 100644 --- a/app/modules/components/tags/tag-line-common/tag-line-common.controller.coffee +++ b/app/modules/components/tags/tag-line-common/tag-line-common.controller.coffee @@ -28,6 +28,7 @@ class TagLineCommonController ] constructor: (@tagLineService) -> + @.disableColorSelection = false @.newTag = {name: "", color: null} @.colorArray = [] @.addTag = false @@ -45,10 +46,14 @@ class TagLineCommonController @.newTag.name = "" @.newTag.color = null - if @.project.tags_colors[name] - color = @.project.tags_colors[name] + return if not name.length - @.onAddTag({name: name, color: color}) if name.length + if @.disableColorSelection + @.onAddTag({name: name, color: color}) if name.length + else + if @.project.tags_colors[name] + color = @.project.tags_colors[name] + @.onAddTag({name: name, color: color}) selectColor: (color) -> @.newTag.color = color diff --git a/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee b/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee index 668a899f..536c229a 100644 --- a/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee +++ b/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee @@ -28,7 +28,9 @@ TagLineCommonDirective = () -> return if !project || !Object.keys(project).length unwatch() - ctrl.colorArray = ctrl._createColorsArray(ctrl.project.tags_colors) + + if not ctrl.disableColorSelection + ctrl.colorArray = ctrl._createColorsArray(ctrl.project.tags_colors) el.on "keydown", ".tag-input", (event) -> if event.keyCode == 27 && ctrl.newTag.name.length diff --git a/app/modules/components/tags/tag-line-common/tag-line-common.jade b/app/modules/components/tags/tag-line-common/tag-line-common.jade index a1fd6848..e5c44ae3 100644 --- a/app/modules/components/tags/tag-line-common/tag-line-common.jade +++ b/app/modules/components/tags/tag-line-common/tag-line-common.jade @@ -1,6 +1,6 @@ .tags-container .tag( - ng-if="tag[1]" + ng-if="tag[1] && !vm.disableColorSelection" ng-repeat="tag in vm.tags" ng-style="{'border-left': '.3rem solid' + tag[1]}" ) @@ -12,7 +12,7 @@ has-permissions="{{vm.checkPermissions()}}" ) .tag( - ng-if="!tag[1]" + ng-if="!tag[1] || vm.disableColorSelection" ng-repeat="tag in vm.tags" ) tg-tag( diff --git a/app/partials/admin/admin-project-profile.jade b/app/partials/admin/admin-project-profile.jade index 8adefd42..50a41dc9 100644 --- a/app/partials/admin/admin-project-profile.jade +++ b/app/partials/admin/admin-project-profile.jade @@ -75,11 +75,10 @@ div.wrapper( tg-tag-line-common.tags-block( disable-color-selection - ng-if="project" project="project" tags="projectTags" permissions="modify_project" - on-add-tag="ctrl.addTag(name, color)" + on-add-tag="ctrl.addTag(name)" on-delete-tag="ctrl.deleteTag(tag)" ) From cc913a5f9952ff48548f3ad3f50d1727c7e77ab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 26 Aug 2016 21:37:19 +0200 Subject: [PATCH 123/315] Remove unnecessary code --- app/coffee/modules/issues/lightboxes.coffee | 4 ++-- app/partials/includes/modules/lightbox-create-issue.jade | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/coffee/modules/issues/lightboxes.coffee b/app/coffee/modules/issues/lightboxes.coffee index 94ee2d33..a9782e59 100644 --- a/app/coffee/modules/issues/lightboxes.coffee +++ b/app/coffee/modules/issues/lightboxes.coffee @@ -145,8 +145,8 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading, return {link:link} -module.directive("tgLbCreateIssue", ["$tgRepo", "$tgConfirm", "$rootScope", "lightboxService", "$tgLoading", "$q", "tgAttachmentsService", - CreateIssueDirective]) +module.directive("tgLbCreateIssue", ["$tgRepo", "$tgConfirm", "$rootScope", "lightboxService", "$tgLoading", + "$q", "tgAttachmentsService", CreateIssueDirective]) ############################################################################# diff --git a/app/partials/includes/modules/lightbox-create-issue.jade b/app/partials/includes/modules/lightbox-create-issue.jade index 69209b57..f572c874 100644 --- a/app/partials/includes/modules/lightbox-create-issue.jade +++ b/app/partials/includes/modules/lightbox-create-issue.jade @@ -40,11 +40,10 @@ form fieldset section - tg-attachments-simple( - attachments="attachments", - on-add="addAttachment(attachment)" - on-delete="deleteAttachment(attachment)" - ) + tg-attachments-simple( + attachments="attachments", + on-add="addAttachment(attachment)" + ) fieldset textarea.description( From 5918ca1e16a3ed326a2433c1d78b806b4261fb3b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 29 Aug 2016 12:15:46 +0200 Subject: [PATCH 124/315] Fixing not found urls --- app/coffee/app.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index e538a865..ccc7247c 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -62,7 +62,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven projectLoaded: ["$q", "tgProjectService", "$route", ($q, projectService, $route) -> deferred = $q.defer() - projectService.setSection($route.current.$$route.section) + projectService.setSection($route.current.$$route?.section) if $route.current.params.pslug projectService.setProjectBySlug($route.current.params.pslug).then(deferred.resolve) From d06bede8de29930955ee8bdc932cd49128ed9b48 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 29 Aug 2016 12:49:21 +0200 Subject: [PATCH 125/315] Fixing style for popup status in detail views --- app/styles/modules/common/ticket-data.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/styles/modules/common/ticket-data.scss b/app/styles/modules/common/ticket-data.scss index 1f18c223..edcdeea1 100644 --- a/app/styles/modules/common/ticket-data.scss +++ b/app/styles/modules/common/ticket-data.scss @@ -37,6 +37,7 @@ a { @include font-type(text); padding: .5rem 1rem; + text-align: left; } a:hover { background: rgba($primary-light, .2); From fdb07f6c7a0e464743b04bc175b06d535a366479 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 25 Aug 2016 08:07:46 +0200 Subject: [PATCH 126/315] Allowing empty current password for users with no password --- app/partials/user/user-change-password.jade | 1 - 1 file changed, 1 deletion(-) diff --git a/app/partials/user/user-change-password.jade b/app/partials/user/user-change-password.jade index 60d7eaf1..11e6b70c 100644 --- a/app/partials/user/user-change-password.jade +++ b/app/partials/user/user-change-password.jade @@ -17,7 +17,6 @@ div.wrapper( fieldset label(for="current-password", translate="CHANGE_PASSWORD.FIELD_CURRENT_PASSWORD") input( - data-required="true" type="password" name="password" id="current-password" From 825ced482d7a0195d765c7ed6fcf8b45298dca68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 1 Sep 2016 14:45:51 +0200 Subject: [PATCH 127/315] Remove ; --- app/coffee/modules/admin/project-values.coffee | 2 +- app/coffee/modules/backlog/sortable.coffee | 2 +- app/coffee/modules/base/http.coffee | 2 +- app/coffee/modules/controllerMixins.coffee | 2 +- app/coffee/modules/issues/list.coffee | 6 +++--- app/coffee/modules/kanban/sortable.coffee | 2 +- app/coffee/modules/taskboard/main.coffee | 2 +- app/coffee/modules/taskboard/sortable.coffee | 2 +- app/coffee/modules/wiki/nav.coffee | 2 +- .../attachments-drop.directive.coffee | 2 +- .../attachments-preview.controller.spec.coffee | 14 +++++++------- .../attachments-sortable.directive.coffee | 2 +- app/modules/components/card/card.controller.coffee | 2 +- .../filter/filter.controller.spec.coffee | 2 +- .../vote-button/vote-button.controller.spec.coffee | 8 ++++---- .../watch-button.controller.spec.coffee | 8 ++++---- .../discover-search-bar.controller.spec.coffee | 8 ++++---- .../services/discover-projects.service.spec.coffee | 2 +- .../like-project-button.controller.spec.coffee | 8 ++++---- .../components/sort-projects.directive.coffee | 2 +- .../projects-listing.controller.spec.coffee | 2 +- .../transfer-project.controller.spec.coffee | 2 +- .../services/current-user.service.spec.coffee | 2 +- app/modules/services/error-handling.service.coffee | 2 +- .../services/project-logo.service.spec.coffee | 4 ++-- app/modules/services/project.service.spec.coffee | 10 +++++----- ...imeline-pagination-sequence.service.spec.coffee | 2 +- .../user-timeline.controller.spec.coffee | 10 +++++----- 28 files changed, 57 insertions(+), 57 deletions(-) diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index cf25e9c7..89797b06 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -157,7 +157,7 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, $tra pixels: 30, scrollWhenOutside: true, autoScroll: () -> - return this.down && drake.dragging; + return this.down && drake.dragging }) $scope.$on "$destroy", -> diff --git a/app/coffee/modules/backlog/sortable.coffee b/app/coffee/modules/backlog/sortable.coffee index 0575c96b..b6b6299d 100644 --- a/app/coffee/modules/backlog/sortable.coffee +++ b/app/coffee/modules/backlog/sortable.coffee @@ -129,7 +129,7 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm) -> pixels: 30, scrollWhenOutside: true, autoScroll: () -> - return this.down && drake.dragging; + return this.down && drake.dragging }) $scope.$on "$destroy", -> diff --git a/app/coffee/modules/base/http.coffee b/app/coffee/modules/base/http.coffee index 0c425720..57fa8e82 100644 --- a/app/coffee/modules/base/http.coffee +++ b/app/coffee/modules/base/http.coffee @@ -30,7 +30,7 @@ class HttpService extends taiga.Service constructor: (@http, @q, @storage, @rootScope, @cacheFactory, @translate) -> super() - @.cache = @cacheFactory("httpget"); + @.cache = @cacheFactory("httpget") headers: -> headers = {} diff --git a/app/coffee/modules/controllerMixins.coffee b/app/coffee/modules/controllerMixins.coffee index 68454e25..08d0ac50 100644 --- a/app/coffee/modules/controllerMixins.coffee +++ b/app/coffee/modules/controllerMixins.coffee @@ -278,7 +278,7 @@ class UsFiltersMixin dataType: "owner", content: owner } - ]; + ] @.customFilters = [] _.forOwn customFiltersRaw, (value, key) => diff --git a/app/coffee/modules/issues/list.coffee b/app/coffee/modules/issues/list.coffee index 8c5c3c18..c1172408 100644 --- a/app/coffee/modules/issues/list.coffee +++ b/app/coffee/modules/issues/list.coffee @@ -271,7 +271,7 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi dataType: "owner", content: owner } - ]; + ] @.customFilters = [] _.forOwn customFiltersRaw, (value, key) => @@ -472,7 +472,7 @@ IssuesDirective = ($log, $location, $template, $compile) -> svg = $("").attr("svg-icon", icon) colHeadElement.append(svg) - $compile(colHeadElement.contents())($scope); + $compile(colHeadElement.contents())($scope) $el.on "click", ".row.title > div", (event) -> target = angular.element(event.currentTarget) @@ -495,7 +495,7 @@ IssuesDirective = ($log, $location, $template, $compile) -> .attr("svg-icon", icon) target.append(svg) - $compile(target.contents())($scope); + $compile(target.contents())($scope) ## Issues Link link = ($scope, $el, $attrs) -> diff --git a/app/coffee/modules/kanban/sortable.coffee b/app/coffee/modules/kanban/sortable.coffee index 0ae86a41..fda8e063 100644 --- a/app/coffee/modules/kanban/sortable.coffee +++ b/app/coffee/modules/kanban/sortable.coffee @@ -94,7 +94,7 @@ KanbanSortableDirective = ($repo, $rs, $rootscope) -> pixels: 30, scrollWhenOutside: true, autoScroll: () -> - return this.down && drake.dragging; + return this.down && drake.dragging }) $scope.$on "$destroy", -> diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee index 4cc01f7b..1a13a106 100644 --- a/app/coffee/modules/taskboard/main.coffee +++ b/app/coffee/modules/taskboard/main.coffee @@ -217,7 +217,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga dataType: "owner", content: owner } - ]; + ] @.customFilters = [] _.forOwn customFiltersRaw, (value, key) => diff --git a/app/coffee/modules/taskboard/sortable.coffee b/app/coffee/modules/taskboard/sortable.coffee index b0c7bc4e..9b92eb51 100644 --- a/app/coffee/modules/taskboard/sortable.coffee +++ b/app/coffee/modules/taskboard/sortable.coffee @@ -110,7 +110,7 @@ TaskboardSortableDirective = ($repo, $rs, $rootscope, $translate) -> pixels: 30, scrollWhenOutside: true, autoScroll: () -> - return this.down && drake.dragging; + return this.down && drake.dragging }) $scope.$on "$destroy", -> diff --git a/app/coffee/modules/wiki/nav.coffee b/app/coffee/modules/wiki/nav.coffee index 40c7bbd7..5b6bb01b 100644 --- a/app/coffee/modules/wiki/nav.coffee +++ b/app/coffee/modules/wiki/nav.coffee @@ -90,7 +90,7 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $analytics, $loading, $t pixels: 30, scrollWhenOutside: true, autoScroll: () -> - return this.down && drake.dragging; + return this.down && drake.dragging }) $el.on "click", ".add-button", (event) -> diff --git a/app/modules/components/attachments-drop/attachments-drop.directive.coffee b/app/modules/components/attachments-drop/attachments-drop.directive.coffee index 7fb995a5..a236828a 100644 --- a/app/modules/components/attachments-drop/attachments-drop.directive.coffee +++ b/app/modules/components/attachments-drop/attachments-drop.directive.coffee @@ -29,7 +29,7 @@ AttachmentsDropDirective = ($parse) -> e.stopPropagation() e.preventDefault() - dataTransfer = e.dataTransfer || (e.originalEvent && e.originalEvent.dataTransfer); + dataTransfer = e.dataTransfer || (e.originalEvent && e.originalEvent.dataTransfer) scope.$apply () -> eventAttr(scope, {files: dataTransfer.files}) diff --git a/app/modules/components/attachments-preview/attachments-preview.controller.spec.coffee b/app/modules/components/attachments-preview/attachments-preview.controller.spec.coffee index a33464fe..40a6108d 100644 --- a/app/modules/components/attachments-preview/attachments-preview.controller.spec.coffee +++ b/app/modules/components/attachments-preview/attachments-preview.controller.spec.coffee @@ -81,7 +81,7 @@ describe "AttachmentsPreviewController", -> } ]) - mocks.attachmentsPreviewService.fileId = 2; + mocks.attachmentsPreviewService.fileId = 2 current = ctrl.getCurrent() @@ -125,7 +125,7 @@ describe "AttachmentsPreviewController", -> } ]) - mocks.attachmentsPreviewService.fileId = 1; + mocks.attachmentsPreviewService.fileId = 1 pagination = ctrl.hasPagination() @@ -173,7 +173,7 @@ describe "AttachmentsPreviewController", -> } ]) - mocks.attachmentsPreviewService.fileId = 2; + mocks.attachmentsPreviewService.fileId = 2 currentIndex = ctrl.getIndex() @@ -215,7 +215,7 @@ describe "AttachmentsPreviewController", -> } ]) - mocks.attachmentsPreviewService.fileId = 1; + mocks.attachmentsPreviewService.fileId = 1 currentIndex = ctrl.next() @@ -256,7 +256,7 @@ describe "AttachmentsPreviewController", -> } ]) - mocks.attachmentsPreviewService.fileId = 3; + mocks.attachmentsPreviewService.fileId = 3 currentIndex = ctrl.next() @@ -298,7 +298,7 @@ describe "AttachmentsPreviewController", -> } ]) - mocks.attachmentsPreviewService.fileId = 3; + mocks.attachmentsPreviewService.fileId = 3 currentIndex = ctrl.previous() @@ -339,7 +339,7 @@ describe "AttachmentsPreviewController", -> } ]) - mocks.attachmentsPreviewService.fileId = 1; + mocks.attachmentsPreviewService.fileId = 1 currentIndex = ctrl.previous() diff --git a/app/modules/components/attachments-sortable/attachments-sortable.directive.coffee b/app/modules/components/attachments-sortable/attachments-sortable.directive.coffee index dcbc615f..f1e91147 100644 --- a/app/modules/components/attachments-sortable/attachments-sortable.directive.coffee +++ b/app/modules/components/attachments-sortable/attachments-sortable.directive.coffee @@ -42,7 +42,7 @@ AttachmentSortableDirective = ($parse) -> pixels: 30, scrollWhenOutside: true, autoScroll: () -> - return this.down && drake.dragging; + return this.down && drake.dragging }) diff --git a/app/modules/components/card/card.controller.coffee b/app/modules/components/card/card.controller.coffee index 25dce94c..97ec971b 100644 --- a/app/modules/components/card/card.controller.coffee +++ b/app/modules/components/card/card.controller.coffee @@ -27,7 +27,7 @@ class CardController @.onToggleFold({id: @.item.get('id')}) getClosedTasks: () -> - return @.item.getIn(['model', 'tasks']).filter (task) -> return task.get('is_closed'); + return @.item.getIn(['model', 'tasks']).filter (task) -> return task.get('is_closed') closedTasksPercent: () -> return @.getClosedTasks().size * 100 / @.item.getIn(['model', 'tasks']).size diff --git a/app/modules/components/filter/filter.controller.spec.coffee b/app/modules/components/filter/filter.controller.spec.coffee index c7166a4b..bb9a9140 100644 --- a/app/modules/components/filter/filter.controller.spec.coffee +++ b/app/modules/components/filter/filter.controller.spec.coffee @@ -51,7 +51,7 @@ describe "Filter", -> isOpen = ctrl.isOpen('filter1') - expect(isOpen).to.be.true; + expect(isOpen).to.be.true it "save custom filter", () -> ctrl = $controller("Filter") diff --git a/app/modules/components/vote-button/vote-button.controller.spec.coffee b/app/modules/components/vote-button/vote-button.controller.spec.coffee index 241210e0..a0201853 100644 --- a/app/modules/components/vote-button/vote-button.controller.spec.coffee +++ b/app/modules/components/vote-button/vote-button.controller.spec.coffee @@ -67,13 +67,13 @@ describe "VoteButton", -> promise = ctrl.toggleVote() - expect(ctrl.loading).to.be.true; + expect(ctrl.loading).to.be.true mocks.onUpvote.resolve() promise.finally () -> expect(mocks.onUpvote).to.be.calledOnce - expect(ctrl.loading).to.be.false; + expect(ctrl.loading).to.be.false done() @@ -90,12 +90,12 @@ describe "VoteButton", -> promise = ctrl.toggleVote() - expect(ctrl.loading).to.be.true; + expect(ctrl.loading).to.be.true mocks.onDownvote.resolve() promise.finally () -> expect(mocks.onDownvote).to.be.calledOnce - expect(ctrl.loading).to.be.false; + expect(ctrl.loading).to.be.false done() diff --git a/app/modules/components/watch-button/watch-button.controller.spec.coffee b/app/modules/components/watch-button/watch-button.controller.spec.coffee index 41e95efb..77247468 100644 --- a/app/modules/components/watch-button/watch-button.controller.spec.coffee +++ b/app/modules/components/watch-button/watch-button.controller.spec.coffee @@ -68,13 +68,13 @@ describe "WatchButton", -> promise = ctrl.toggleWatch() - expect(ctrl.loading).to.be.true; + expect(ctrl.loading).to.be.true mocks.onWatch.resolve() promise.finally () -> expect(mocks.onWatch).to.be.calledOnce - expect(ctrl.loading).to.be.false; + expect(ctrl.loading).to.be.false done() @@ -91,13 +91,13 @@ describe "WatchButton", -> promise = ctrl.toggleWatch() - expect(ctrl.loading).to.be.true; + expect(ctrl.loading).to.be.true mocks.onUnwatch.resolve() promise.finally () -> expect(mocks.onUnwatch).to.be.calledOnce - expect(ctrl.loading).to.be.false; + expect(ctrl.loading).to.be.false done() diff --git a/app/modules/discover/components/discover-search-bar/discover-search-bar.controller.spec.coffee b/app/modules/discover/components/discover-search-bar/discover-search-bar.controller.spec.coffee index 36b5dd4e..fbde731f 100644 --- a/app/modules/discover/components/discover-search-bar/discover-search-bar.controller.spec.coffee +++ b/app/modules/discover/components/discover-search-bar/discover-search-bar.controller.spec.coffee @@ -57,8 +57,8 @@ describe "DiscoverSearchBarController", -> ctrl.selectFilter('text') - expect(mocks.discoverProjectsService.fetchStats).to.have.been.called; - expect(ctrl.onChange).to.have.been.calledWith(sinon.match({filter: 'text', q: 'query'})); + expect(mocks.discoverProjectsService.fetchStats).to.have.been.called + expect(ctrl.onChange).to.have.been.calledWith(sinon.match({filter: 'text', q: 'query'})) it "submit filter", () -> ctrl = $controller("DiscoverSearchBar") @@ -68,5 +68,5 @@ describe "DiscoverSearchBarController", -> ctrl.submitFilter() - expect(mocks.discoverProjectsService.fetchStats).to.have.been.called; - expect(ctrl.onChange).to.have.been.calledWith(sinon.match({filter: 'all', q: 'query'})); + expect(mocks.discoverProjectsService.fetchStats).to.have.been.called + expect(ctrl.onChange).to.have.been.calledWith(sinon.match({filter: 'all', q: 'query'})) diff --git a/app/modules/discover/services/discover-projects.service.spec.coffee b/app/modules/discover/services/discover-projects.service.spec.coffee index 75c13e43..971502c7 100644 --- a/app/modules/discover/services/discover-projects.service.spec.coffee +++ b/app/modules/discover/services/discover-projects.service.spec.coffee @@ -174,6 +174,6 @@ describe "tgDiscoverProjectsService", -> expect(result).to.have.length(5) - expect(result[4].decorate).to.be.ok; + expect(result[4].decorate).to.be.ok done() diff --git a/app/modules/projects/components/like-project-button/like-project-button.controller.spec.coffee b/app/modules/projects/components/like-project-button/like-project-button.controller.spec.coffee index 8a020485..f29bbc33 100644 --- a/app/modules/projects/components/like-project-button/like-project-button.controller.spec.coffee +++ b/app/modules/projects/components/like-project-button/like-project-button.controller.spec.coffee @@ -72,13 +72,13 @@ describe "LikeProjectButton", -> promise = ctrl.toggleLike() - expect(ctrl.loading).to.be.true; + expect(ctrl.loading).to.be.true mocks.tgLikeProjectButton.like.withArgs(project.get('id')).resolve() promise.finally () -> expect(mocks.tgLikeProjectButton.like).to.be.calledOnce - expect(ctrl.loading).to.be.false; + expect(ctrl.loading).to.be.false done() @@ -109,13 +109,13 @@ describe "LikeProjectButton", -> promise = ctrl.toggleLike() - expect(ctrl.loading).to.be.true; + expect(ctrl.loading).to.be.true mocks.tgLikeProjectButton.unlike.withArgs(project.get('id')).resolve() promise.finally () -> expect(mocks.tgLikeProjectButton.unlike).to.be.calledOnce - expect(ctrl.loading).to.be.false; + expect(ctrl.loading).to.be.false done() diff --git a/app/modules/projects/components/sort-projects.directive.coffee b/app/modules/projects/components/sort-projects.directive.coffee index b31bbf83..d432de2d 100644 --- a/app/modules/projects/components/sort-projects.directive.coffee +++ b/app/modules/projects/components/sort-projects.directive.coffee @@ -49,7 +49,7 @@ SortProjectsDirective = (currentUserService) -> pixels: 30, scrollWhenOutside: true, autoScroll: () -> - return this.down && drake.dragging; + return this.down && drake.dragging }) scope.$on "$destroy", -> diff --git a/app/modules/projects/listing/projects-listing.controller.spec.coffee b/app/modules/projects/listing/projects-listing.controller.spec.coffee index fe3d2b2b..d7a3f00d 100644 --- a/app/modules/projects/listing/projects-listing.controller.spec.coffee +++ b/app/modules/projects/listing/projects-listing.controller.spec.coffee @@ -77,4 +77,4 @@ describe "ProjectsListingController", -> pageCtrl.newProject() - expect(mocks.projectsService.newProject).to.be.calledOnce; + expect(mocks.projectsService.newProject).to.be.calledOnce diff --git a/app/modules/projects/transfer/transfer-project.controller.spec.coffee b/app/modules/projects/transfer/transfer-project.controller.spec.coffee index c87baecb..3a854312 100644 --- a/app/modules/projects/transfer/transfer-project.controller.spec.coffee +++ b/app/modules/projects/transfer/transfer-project.controller.spec.coffee @@ -127,7 +127,7 @@ describe "TransferProject", -> ctrl = $controller("TransferProjectController") ctrl.project = project ctrl.initialize().then () -> - expect(mocks.errorHandlingService.notfound).have.been.called; + expect(mocks.errorHandlingService.notfound).have.been.called done() it "valid token private project with max projects for user", (done) -> diff --git a/app/modules/services/current-user.service.spec.coffee b/app/modules/services/current-user.service.spec.coffee index 90c08398..96f4fff2 100644 --- a/app/modules/services/current-user.service.spec.coffee +++ b/app/modules/services/current-user.service.spec.coffee @@ -179,7 +179,7 @@ describe "tgCurrentUserService", -> backlog: false, kanban: false, dashboard: false - }); + }) it "load joyride config", (done) -> mocks.resources.user.getUserStorage.withArgs('joyride').promise().resolve(true) diff --git a/app/modules/services/error-handling.service.coffee b/app/modules/services/error-handling.service.coffee index a3ceef53..66576c39 100644 --- a/app/modules/services/error-handling.service.coffee +++ b/app/modules/services/error-handling.service.coffee @@ -27,7 +27,7 @@ class ErrorHandlingService constructor: (@rootScope) -> init: () -> - @rootScope.errorHandling = {}; + @rootScope.errorHandling = {} notfound: -> @rootScope.errorHandling.showingError = true diff --git a/app/modules/services/project-logo.service.spec.coffee b/app/modules/services/project-logo.service.spec.coffee index 1c6cae74..d4858e42 100644 --- a/app/modules/services/project-logo.service.spec.coffee +++ b/app/modules/services/project-logo.service.spec.coffee @@ -38,5 +38,5 @@ describe "tgProjectLogoService", -> logo = projectLogoService.getDefaultProjectLogo('slug/slug', 2) - expect(logo.src).to.be.equal('/123/images/project-logos/project-logo-04.png'); - expect(logo.color).to.be.equal('rgba( 152, 224, 168, 1 )'); + expect(logo.src).to.be.equal('/123/images/project-logos/project-logo-04.png') + expect(logo.color).to.be.equal('rgba( 152, 224, 168, 1 )') diff --git a/app/modules/services/project.service.spec.coffee b/app/modules/services/project.service.spec.coffee index 8b4a11f9..20f529d1 100644 --- a/app/modules/services/project.service.spec.coffee +++ b/app/modules/services/project.service.spec.coffee @@ -89,7 +89,7 @@ describe "tgProjectService", -> .then () -> projectService.setProjectBySlug('slug-1') .then () -> projectService.setProjectBySlug('slug-2') .finally () -> - expect(projectService.setProject).to.be.called.twice; + expect(projectService.setProject).to.be.called.twice done() it "set project and set active members", () -> @@ -136,10 +136,10 @@ describe "tgProjectService", -> projectService.cleanProject() - expect(projectService.project).to.be.null; - expect(projectService.activeMembers.size).to.be.equal(0); - expect(projectService.section).to.be.null; - expect(projectService.sectionsBreadcrumb.size).to.be.equal(0); + expect(projectService.project).to.be.null + expect(projectService.activeMembers.size).to.be.equal(0) + expect(projectService.section).to.be.null + expect(projectService.sectionsBreadcrumb.size).to.be.equal(0) it "has permissions", () -> project = Immutable.Map({ diff --git a/app/modules/user-timeline/user-timeline-pagination-sequence/user-timeline-pagination-sequence.service.spec.coffee b/app/modules/user-timeline/user-timeline-pagination-sequence/user-timeline-pagination-sequence.service.spec.coffee index 3ee1bf8d..7baebf6e 100644 --- a/app/modules/user-timeline/user-timeline-pagination-sequence/user-timeline-pagination-sequence.service.spec.coffee +++ b/app/modules/user-timeline/user-timeline-pagination-sequence/user-timeline-pagination-sequence.service.spec.coffee @@ -144,7 +144,7 @@ describe "tgUserTimelinePaginationSequenceService", -> config.minItems = 1 - config.map = (item) => item + 1; + config.map = (item) => item + 1 seq = userTimelinePaginationSequenceService.generate(config) 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 e66cf9b3..519f79be 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 @@ -49,7 +49,7 @@ describe "UserTimelineController", -> $rootScope = _$rootScope_ it "timelineList should be an array", () -> - $scope = $rootScope.$new(); + $scope = $rootScope.$new() mocks.userTimelineService.getUserTimeline = sinon.stub().returns(true) @@ -63,7 +63,7 @@ describe "UserTimelineController", -> it "project timeline sequence", () -> mocks.userTimelineService.getProjectTimeline = sinon.stub().withArgs(4).returns(true) - $scope = $rootScope.$new(); + $scope = $rootScope.$new() myCtrl = controller("UserTimeline", $scope, { projectId: 4 @@ -74,7 +74,7 @@ describe "UserTimelineController", -> it "currentUser timeline sequence", () -> mocks.userTimelineService.getProfileTimeline = sinon.stub().withArgs(2).returns(true) - $scope = $rootScope.$new(); + $scope = $rootScope.$new() myCtrl = controller("UserTimeline", $scope, { currentUser: true, @@ -86,7 +86,7 @@ describe "UserTimelineController", -> it "currentUser timeline sequence", () -> mocks.userTimelineService.getUserTimeline = sinon.stub().withArgs(2).returns(true) - $scope = $rootScope.$new(); + $scope = $rootScope.$new() myCtrl = controller("UserTimeline", $scope, { user: Immutable.Map({id: 2}) @@ -99,7 +99,7 @@ describe "UserTimelineController", -> beforeEach () -> mocks.userTimelineService.getUserTimeline = sinon.stub().returns({}) - $scope = $rootScope.$new(); + $scope = $rootScope.$new() myCtrl = controller("UserTimeline", $scope, { user: Immutable.Map({id: 2}) }) From aaee97c4ebda0f24b981b8ce875cea19a0a4bab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 1 Sep 2016 17:38:36 +0200 Subject: [PATCH 128/315] Remove unnecessary directive call --- app/partials/backlog/backlog.jade | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/partials/backlog/backlog.jade b/app/partials/backlog/backlog.jade index 5330e45f..5faa48b1 100644 --- a/app/partials/backlog/backlog.jade +++ b/app/partials/backlog/backlog.jade @@ -78,10 +78,7 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", section.backlog-table(ng-class="{'hidden': !userstories.length}") include ../includes/modules/backlog-table - div.empty-backlog( - ng-class="{'hidden': userstories === undefined || userstories.length}" - tg-backlog-empty-sortable - ) + div.empty-backlog(ng-class="{'hidden': userstories === undefined || userstories.length}") img( src="/#{v}/images/backlog-empty.png" alt="{{'BACKLOG.EMPTY' | translate}}" From 79f0ac8114211fa2f38b34297f9e682778a1514a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 1 Sep 2016 19:23:56 +0200 Subject: [PATCH 129/315] Remove some code --- app/coffee/modules/backlog/main.coffee | 41 +++++++++++----------- app/coffee/modules/backlog/sortable.coffee | 16 ++------- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index a9819327..831d24cd 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -68,7 +68,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F milestonesOrder: {} constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @appMetaService, @navUrls, - @events, @analytics, @translate, @loading, @rs2, @modelTransform, @errorHandlingService, @storage, @filterRemoteStorageService) -> + @events, @analytics, @translate, @loading, @rs2, @modelTransform, @errorHandlingService, + @storage, @filterRemoteStorageService) -> bindMethods(@) @.backlogOrder = {} @@ -329,22 +330,15 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F prepareBulkUpdateData: (uses, field="backlog_order") -> return _.map(uses, (x) -> {"us_id": x.id, "order": x[field]}) - resortUserStories: (uses, field="backlog_order") -> - items = [] - - for item, index in uses - item[field] = index - if item.isModified() - items.push(item) - - return items - # --move us api behavior-- # if your are moving multiples USs you must use the bulk api # if there is only one US you must use patch (repo.save) # the new US position is the position of the previous US + 1 - # if the previous US has a position value that it is equal to other USs, you must send all the USs with that position value only if they are before of the target position - # with this USs if it's a patch you must add them to the header, if is a bulk you must send them with the other USs + # if the previous US has a position value that it is equal to + # other USs, you must send all the USs with that position value + # only if they are before of the target position with this USs + # if it's a patch you must add them to the header, if is a bulk + # you must send them with the other USs. moveUs: (ctx, usList, newUsIndex, newSprintId) -> oldSprintId = usList[0].milestone project = usList[0].project @@ -414,12 +408,17 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F else if previous startIndex = orderList[previous.id] + 1 - previousWithTheSameOrder = _.filter beforeDestination, (it) -> it[orderField] == orderList[previous.id] + previousWithTheSameOrder = _.filter beforeDestination, (it) -> + return it[orderField] == orderList[previous.id] - # we must send the USs previous to the dropped USs to tell the backend which USs are before the dropped USs, - # if they have the same value to order, the backend doens't know after which one do you want to drop the USs + # we must send the USs previous to the dropped USs to + # tell the backend which USs are before the dropped + # USs, if they have the same value to order, the backend + # doens't know after which one do you want to drop + # the USs if previousWithTheSameOrder.length > 1 - setPreviousOrders = _.map previousWithTheSameOrder, (it) -> {us_id: it.id, order: orderList[it.id]} + setPreviousOrders = _.map previousWithTheSameOrder, (it) -> + return {us_id: it.id, order: orderList[it.id]} modifiedUs = [] @@ -444,7 +443,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F for sprint in @scope.closedSprints sprint.user_stories = _.sortBy sprint.user_stories, (it) => @.milestonesOrder[sprint.id][it.id] - #saving + # saving if usList.length > 1 && (newSprintId != oldSprintId) # drag multiple to sprint data = modifiedUs.concat(setPreviousOrders) promise = @rs.userstories.bulkUpdateMilestone(project, newSprintId, data) @@ -527,10 +526,10 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F currentDate = new Date().getTime() return _.find @scope.sprints, (sprint) -> - start = moment(sprint.estimated_start, 'YYYY-MM-DD').format('x') - end = moment(sprint.estimated_finish, 'YYYY-MM-DD').format('x') + start = moment(sprint.estimated_start, 'YYYY-MM-DD').format('x') + end = moment(sprint.estimated_finish, 'YYYY-MM-DD').format('x') - return currentDate >= start && currentDate <= end + return currentDate >= start && currentDate <= end module.controller("BacklogController", BacklogController) diff --git a/app/coffee/modules/backlog/sortable.coffee b/app/coffee/modules/backlog/sortable.coffee index b6b6299d..555d75c4 100644 --- a/app/coffee/modules/backlog/sortable.coffee +++ b/app/coffee/modules/backlog/sortable.coffee @@ -23,16 +23,10 @@ ### taiga = @.taiga - -mixOf = @.taiga.mixOf -toggleText = @.taiga.toggleText -scopeDefer = @.taiga.scopeDefer bindOnce = @.taiga.bindOnce -groupBy = @.taiga.groupBy module = angular.module("taigaBacklog") - ############################################################################# ## Sortable Directive ############################################################################# @@ -42,7 +36,7 @@ deleteElement = (el) -> $(el).off() $(el).remove() -BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm) -> +BacklogSortableDirective = () -> link = ($scope, $el, $attrs) -> bindOnce $scope, "project", (project) -> # If the user has not enough permissions we don't enable the sortable @@ -138,10 +132,4 @@ BacklogSortableDirective = ($repo, $rs, $rootscope, $tgConfirm) -> return {link: link} -module.directive("tgBacklogSortable", [ - "$tgRepo", - "$tgResources", - "$rootScope", - "$tgConfirm", - BacklogSortableDirective -]) +module.directive("tgBacklogSortable", BacklogSortableDirective) From 12150dcce1e0e9699e9886acc390eed2af6b517a Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 14 Sep 2016 09:53:12 +0200 Subject: [PATCH 130/315] Fixing register form on invitation --- app/partials/includes/modules/invitation-register-form.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/partials/includes/modules/invitation-register-form.jade b/app/partials/includes/modules/invitation-register-form.jade index c943db40..1afc1d31 100644 --- a/app/partials/includes/modules/invitation-register-form.jade +++ b/app/partials/includes/modules/invitation-register-form.jade @@ -1,4 +1,4 @@ -form.register-form(ng-if="publicRegisterEnabled") +form.register-form(ng-show="publicRegisterEnabled") p.form-header(translate="REGISTER_FORM.TITLE") fieldset input( From d19acbd1adfda30faf5fc2f2eaa1b827fd0c7c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 14 Sep 2016 10:11:49 +0200 Subject: [PATCH 131/315] Fix space between items in user dashboard --- app/styles/components/list-items.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/styles/components/list-items.scss b/app/styles/components/list-items.scss index 06cdef94..fb72b3a8 100644 --- a/app/styles/components/list-items.scss +++ b/app/styles/components/list-items.scss @@ -110,11 +110,10 @@ } .ticket-id { color: $gray-light; - margin-right: .3rem; } .ticket-blocked { color: $red; - margin-left: .3rem; + margin-right: .25rem; } } From 0a504ad6b74f096d6c34436aab04f3bc814323fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 16 Sep 2016 12:52:43 +0200 Subject: [PATCH 132/315] Empty illustrations --- app/images/backlog-empty.png | Bin 23914 -> 0 bytes app/images/empty/empty_contact.png | Bin 0 -> 730 bytes app/images/empty/empty_des.png | Bin 0 -> 1800 bytes app/images/empty/empty_field.png | Bin 0 -> 2267 bytes app/images/empty/empty_like.png | Bin 0 -> 746 bytes app/images/empty/empty_mex.png | Bin 0 -> 2004 bytes app/images/empty/empty_moon.png | Bin 0 -> 1854 bytes app/images/empty/empty_sprint.png | Bin 0 -> 1164 bytes app/images/empty/empty_tex.png | Bin 0 -> 2177 bytes app/images/empty/empty_upvote.png | Bin 0 -> 770 bytes app/images/empty/empty_watch.png | Bin 0 -> 722 bytes app/images/issues-empty.png | Bin 26569 -> 0 bytes app/images/search-empty.png | Bin 16581 -> 0 bytes app/images/sprint-empty.png | Bin 17029 -> 0 bytes .../discover-search/discover-search.jade | 4 +-- .../profile-contacts/profile-contacts.jade | 7 ++-- .../profile-favs.controller.coffee | 3 ++ .../profile/profile-favs/profile-favs.jade | 15 ++++++-- app/modules/profile/profile.jade | 7 ++-- app/partials/backlog/backlog.jade | 4 +-- .../components/empty-search-results.jade | 2 +- .../includes/modules/issues-table.jade | 4 +-- .../includes/modules/search-result-table.jade | 8 ++--- app/partials/includes/modules/sprints.jade | 4 +-- app/styles/components/empty.scss | 34 ++++++++++++++++++ app/styles/modules/backlog/backlog-table.scss | 20 ----------- app/styles/modules/issues/issues-table.scss | 16 --------- .../modules/search/search-result-table.scss | 16 --------- 28 files changed, 73 insertions(+), 71 deletions(-) delete mode 100644 app/images/backlog-empty.png create mode 100644 app/images/empty/empty_contact.png create mode 100644 app/images/empty/empty_des.png create mode 100644 app/images/empty/empty_field.png create mode 100644 app/images/empty/empty_like.png create mode 100644 app/images/empty/empty_mex.png create mode 100644 app/images/empty/empty_moon.png create mode 100644 app/images/empty/empty_sprint.png create mode 100644 app/images/empty/empty_tex.png create mode 100644 app/images/empty/empty_upvote.png create mode 100644 app/images/empty/empty_watch.png delete mode 100644 app/images/issues-empty.png delete mode 100644 app/images/search-empty.png delete mode 100644 app/images/sprint-empty.png create mode 100644 app/styles/components/empty.scss diff --git a/app/images/backlog-empty.png b/app/images/backlog-empty.png deleted file mode 100644 index a7179c86ccfe1b8b428b71c8b09857fbdd29086a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23914 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

IQHPqUhh)j9k`HipYGr9kr;k|BU5+fXN@1W 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 P1O!lsmP&<^dGP-Ma|ALh diff --git a/app/images/empty/empty_contact.png b/app/images/empty/empty_contact.png new file mode 100644 index 0000000000000000000000000000000000000000..2868e3bbb12653d94e95128bc2013e1db08fe7c5 GIT binary patch literal 730 zcmeAS@N?(olHy`uVBq!ia0y~yV7S1*z!1T~#=yYP;$C@>fq`kEr;B4q#hkZy?XyK3 zW!Msw%LI?u_x5Uvi0HIL-1s5*XGa~2G&i$YQ!G`f8Akc$X0U@5K!Q_Gvhad!pC1{1Ft`h-)kfD>zA4U z3kwGm6APnkhxglWb9W!z_0A_#-KbGP0j&PbPO(1Cuk)6BUY)!A>6a(z8{fTkfopl* zsIlkwkEIdz^`8n}KaQ4te)Qqy7Zr8$=ZnAQpKmAry1AMC=&v6?vNx`?4!m0#1lK3~ zqRZRbKmYhjYhUlxpRYvC%`g!LyMeK(f$zwYW$$dZhCSb?&ZN`9;ONkxP_U5s>pbxn zS9g_VeMt}5CjIx{H>tao^X%%jeEsQe40CDB3y1slf24jf?fqTQdvAO9##i5!?|x1w zdA@V!etB7ceV7dgTKTU0>RCPc!}@!E*1JBRIm^=20Ji4B9{CmD|Nr~?c<%Sa+4kwn zkN&!SGIC>F)bo`y!9l?xz_RPR%Zs-rFF$Y0TfBbm>bJjcZJ))-0&+EjW5T!h9&@Ys z*{-SHt^WP?;_oEe(&J_@x1HH%UUs*#MCsZQm}LgPD;G@LCA{_9+Tz^1TgvVRPOr;d z3ODPc?27QJ{hfTFMR9r7aEC8_?T~WqUS(PBA6Q5>-h9u#YVBX+AD6B^Kl$$d?$9;U z|NUO~-!5ynT^(H8qw~yL-?10HjavEr_v{lhV15z1-xxKm?tIOwP5*1M)0sd4t>7RK zQOo|y^p~~O@@1EGX2aa`;8dxC#7UUbKZu8Q-U@CIzmUbiz`)??>gTe~DWM4f`F260 literal 0 HcmV?d00001 diff --git a/app/images/empty/empty_des.png b/app/images/empty/empty_des.png new file mode 100644 index 0000000000000000000000000000000000000000..7600162bc69de682b584f43e2f07c20777f1745e GIT binary patch literal 1800 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 literal 0 HcmV?d00001 diff --git a/app/images/empty/empty_like.png b/app/images/empty/empty_like.png new file mode 100644 index 0000000000000000000000000000000000000000..4d0e7967c78d894a8454d4c51d1f3436ce9af4ba GIT binary patch literal 746 zcmeAS@N?(olHy`uVBq!ia0y~yV7S1*z!1T~#=yYP;$C@>fq`kGr;B4q#hkZyH)e?h zO0XsF24NayE7^UKZ<9}-L3ukXH6Z~5B25!znKzFv2ZXku{0VVuVuXO z==bUHy>joL=Y*}fE`Kk-YCk6nBV$togQEj`(#lJG#hIn1RsWCw{rlptsW3c3a$o#dR(4>0poq4#)!=%NNY@I}-TH@5<8m z=YowJK}rP_IC|DGUw!3wWo!Q2^VfL2-F0We0>$lqW7Jl={*cQHy{yh(g!@!|9rMJC zmABwwH|u+=^sN5}+e7cG@2+^4v+>*aykwY5B>vVeSogfKrflt%Uw?g5;ASj-?-1+v z>$~9GFZc6b!9^#(cUXJLR(P84a!Ghh9;mYSU|?YI MboFyt=akR{0Dojb9RL6T literal 0 HcmV?d00001 diff --git a/app/images/empty/empty_mex.png b/app/images/empty/empty_mex.png new file mode 100644 index 0000000000000000000000000000000000000000..1800ee7c2d4461dec4e2c62cc22fc3b1978efb2e GIT binary patch literal 2004 zcmb_dYgCeF9DkG&OS<1A7rN;rTc(MHD^M&8ou;OhCojXTye#MH0sekr0L)to0H9l20RRH?EuH{?E)VeY-AAvOly9zEwaeBj*4;DA*Y5{R$?8<6 zeFp~4hc30T*={@mwVNmjzTd3(Pi(m1aX}ifKrt`w~yb#cfbk7 zEgGA8q&%lu1ca^yeED{nB1)o{MjKvTRMb2y{zyq@TJU(+wsz1qoJXu~NR?zVOr09N z)WfzU3z{;_+{er@;SffdoAlZIcc75_aPTELo<&ox1Rzo@C-8ZAFX^nl0Z}><=48tD zx3B;J{Ic>LNJOyKN{G#=XHO3jD|=o*yjT57r z-UQ{RvmejorQd1TAn}2nzJ56vse>)R&!V^VL$5SJO}Npw z49k5|;~a1GU!C*$T=$d$@nHT{5f0iPd2!-%zR?f-OLbdr^?%9w%#^~oY7 zQG+|-w@P)eF^l2TscVMwPAJIf!h{AB$4Fz8BJE9+$t`=ofS*a^MLvizCCQMPxGtn- zWKm7Z?i@2Z*JoPgh)-+?uhTW9N>-(XN%l_~hSrirLY$Gt=zpw6m)UG|fuDttc{HZ! zK%mLgcdCZUQPdhqFRF0s%h0N7 z{27;G?0lt?6U59cLkn?zs?(w)h??{ld*PnlET~!o_bLm5jV+lte<-B#^-N;DCRMA7 zVYzqb>buOW-IVF|$>KfK(~O5z(QDGD#3(GfP4^@oS5e6hOVhum-&8AVW%giG%oT~3 zt?P;lo@v!wjSuzkyVwVYe`vxZ63Y4qeu*9#&Ua<~@K0w93?r-8XwPSF8&~z=+~OS0 zmZ8HFoT8yL(F9@MC|8|`XC=rD6pn@gn>oBIc$bWfj8>8zHjK_q{uFU z2s3N%xd`maJ(kA?MiLfO_c**XFa^qp^4!p7PG?Z|8CNn6On1?F&i%L`rkA+{!&vK! zSONY!X6d@W(YSP#ioKG~dcs*wiK8TB#c4B!~Zmv}?my|xm2?aXkg87F$O zt#;h*c~Oopg8cGZk^A-6?V1r4kx0IkEae5bGso`2QN)wpxHePYtYJc@tCy-oh^q8# zKmMQ&zG`4))MeuM=RVTd1&;NMcdFv?YFRRAbu=;kC^`J$x8s-`MHxC5gKDgTb=}BF zV{$U-*4A<6(MjVlq!l7AsarCfAB=TZFhV`d=(5Rv@yVV2ghVkVo5!p>)S~0LH5Jxc ziZ3C^(C|}rtLe^xi+s~dB^vKq@m;7n*G=%bp@q2s2$Prt-0roc5r@rY=5%yXE`Lt2 z03dtWwU0cJ^0}w)`)3pgl%S^~ z{Anf;g=ZkZbdmR@Gt<6a9|K43?+z&3zzs`<1U;^DVcF1Z{O!{0^z^-7w>pNm| F{sxOzA$9-& literal 0 HcmV?d00001 diff --git a/app/images/empty/empty_moon.png b/app/images/empty/empty_moon.png new file mode 100644 index 0000000000000000000000000000000000000000..376f7510d6fe1f3a53c239a305cc11483e9d36d9 GIT binary patch literal 1854 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 literal 0 HcmV?d00001 diff --git a/app/images/empty/empty_watch.png b/app/images/empty/empty_watch.png new file mode 100644 index 0000000000000000000000000000000000000000..e69c543a23739c342f06481abad3544c69535d33 GIT binary patch literal 722 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 diff --git a/app/images/search-empty.png b/app/images/search-empty.png deleted file mode 100644 index d95477dc0aa3626f430449428680d771b54ff518..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16581 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 QxBvhE07*qoM6N<$f;Po}y8r+H diff --git a/app/modules/discover/discover-search/discover-search.jade b/app/modules/discover/discover-search/discover-search.jade index 1290ab83..d605246c 100644 --- a/app/modules/discover/discover-search/discover-search.jade +++ b/app/modules/discover/discover-search/discover-search.jade @@ -6,9 +6,9 @@ div(tg-discover-search) on-change="vm.onChangeFilter(filter, q)" ) - .empty-discover-results(ng-if="!vm.searchResult.size && !vm.loadingGlobal && !vm.loadingList") + .empty-large(ng-if="!vm.searchResult.size && !vm.loadingGlobal && !vm.loadingList") img( - src="/#{v}/images/issues-empty.png", + src="/#{v}/images/empty_tex.png", alt="{{ DISCOVER.EMPTY | translate }}" ) p.title(translate="DISCOVER.EMPTY") diff --git a/app/modules/profile/profile-contacts/profile-contacts.jade b/app/modules/profile/profile-contacts/profile-contacts.jade index 5f8d2ef6..16c7d71e 100644 --- a/app/modules/profile/profile-contacts/profile-contacts.jade +++ b/app/modules/profile/profile-contacts/profile-contacts.jade @@ -3,8 +3,11 @@ section.profile-contacts div.spin img(src="/#{v}/svg/spinner-circle.svg", alt="Loading...") - div.empty-tab(ng-if="vm.contacts && !vm.contacts.size") - tg-svg(svg-icon="icon-unwatch") + div.empty-small(ng-if="vm.contacts && !vm.contacts.size") + img( + src="/#{v}/images/empty/empty_contact.png" + alt="{{ 'USER.PROFILE.CONTACTS_EMPTY' | translate }}" + ) div(ng-if="!vm.isCurrentUser") p(translate="USER.PROFILE.CONTACTS_EMPTY", translate-values="{username: vm.user.get('full_name_display')}") diff --git a/app/modules/profile/profile-favs/profile-favs.controller.coffee b/app/modules/profile/profile-favs/profile-favs.controller.coffee index 5bd5917d..f47f7823 100644 --- a/app/modules/profile/profile-favs/profile-favs.controller.coffee +++ b/app/modules/profile/profile-favs/profile-favs.controller.coffee @@ -131,6 +131,7 @@ class ProfileLikedController extends FavsBaseController constructor: (@userService) -> super() + @.tabName = 'likes' @.enableFilterByAll = false @.enableFilterByProjects = false @.enableFilterByUserStories = false @@ -154,6 +155,7 @@ class ProfileVotedController extends FavsBaseController constructor: (@userService) -> super() + @.tabName = 'upvotes' @.enableFilterByAll = true @.enableFilterByProjects = false @.enableFilterByUserStories = true @@ -179,6 +181,7 @@ class ProfileWatchedController extends FavsBaseController constructor: (@userService) -> super() + @.tabName = 'watchers' @._getItems = @userService.getWatched diff --git a/app/modules/profile/profile-favs/profile-favs.jade b/app/modules/profile/profile-favs/profile-favs.jade index 880c25e6..0317836b 100644 --- a/app/modules/profile/profile-favs/profile-favs.jade +++ b/app/modules/profile/profile-favs/profile-favs.jade @@ -87,9 +87,20 @@ section.profile-favs alt="{{ 'COMMON.LOADING'|translate }}" ) - .empty-search-results(ng-if="vm.hasNoResults && !vm.isLoading") + .empty-small(ng-if="vm.hasNoResults && !vm.isLoading") img( - src="/#{v}/images/search-empty.png" + ng-if="vm.tabName === 'likes'" + src="/#{v}/images/empty/empty_like.png" + alt="{{ 'USER.PROFILE_FAVS.EMPTY_TITLE' | translate }}" + ) + img( + ng-if="vm.tabName === 'upvotes'" + src="/#{v}/images/empty/empty_upvote.png" + alt="{{ 'USER.PROFILE_FAVS.EMPTY_TITLE' | translate }}" + ) + img( + ng-if="vm.tabName === 'watchers'" + src="/#{v}/images/empty/empty_watch.png" alt="{{ 'USER.PROFILE_FAVS.EMPTY_TITLE' | translate }}" ) p.title {{ 'USER.PROFILE_FAVS.EMPTY_TITLE' | translate }} diff --git a/app/modules/profile/profile.jade b/app/modules/profile/profile.jade index 3b6caf8e..79ba4add 100644 --- a/app/modules/profile/profile.jade +++ b/app/modules/profile/profile.jade @@ -1,7 +1,10 @@ div.profile.centered(ng-if="vm.user") - div(tg-profile-bar, user="vm.user", isCurrentUser="vm.isCurrentUser") + tg-profile-bar( + user="vm.user" + isCurrentUser="vm.isCurrentUser" + ) div.main - div.timeline-wrapper(tg-profile-tabs) + tg-profile-tabs.timeline-wrapper div( tg-profile-tab="{{'USER.PROFILE.TABS.ACTIVITY_TAB' | translate}}" tab-title="{{'USER.PROFILE.TABS.ACTIVITY_TAB_TITLE' | translate}}" diff --git a/app/partials/backlog/backlog.jade b/app/partials/backlog/backlog.jade index 5faa48b1..69330b95 100644 --- a/app/partials/backlog/backlog.jade +++ b/app/partials/backlog/backlog.jade @@ -78,9 +78,9 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", section.backlog-table(ng-class="{'hidden': !userstories.length}") include ../includes/modules/backlog-table - div.empty-backlog(ng-class="{'hidden': userstories === undefined || userstories.length}") + div.empty-large(ng-class="{'hidden': userstories === undefined || userstories.length}") img( - src="/#{v}/images/backlog-empty.png" + src="/#{v}/images/empty/empty_mex.png" alt="{{'BACKLOG.EMPTY' | translate}}" ) p.title(translate="BACKLOG.EMPTY") diff --git a/app/partials/includes/components/empty-search-results.jade b/app/partials/includes/components/empty-search-results.jade index f3248bd3..47634e21 100644 --- a/app/partials/includes/components/empty-search-results.jade +++ b/app/partials/includes/components/empty-search-results.jade @@ -1,5 +1,5 @@ img( - src="/#{v}/images/search-empty.png" + src="/#{v}/images/empty/empty_tex.png" alt="{{ 'SEARCH.EMPTY_TITLE' | translate }}" ) p.title {{ 'SEARCH.EMPTY_TITLE' | translate }} diff --git a/app/partials/includes/modules/issues-table.jade b/app/partials/includes/modules/issues-table.jade index 186629c8..c1a5fcfe 100644 --- a/app/partials/includes/modules/issues-table.jade +++ b/app/partials/includes/modules/issues-table.jade @@ -80,9 +80,9 @@ section.issues-table.basic-table(ng-class="{empty: !issues.length}") svg-icon="icon-arrow-down" ) -section.empty-issues(ng-if="issues != undefined && issues.length == 0") +section.empty-large(ng-if="issues != undefined && issues.length == 0") img( - src="/#{v}/images/issues-empty.png", + src="/#{v}/images/empty/empty_moon.png", alt="{{ISSUES.TABLE.EMPTY.TITLE | translate }}" ) p.title(translate="ISSUES.TABLE.EMPTY.TITLE") diff --git a/app/partials/includes/modules/search-result-table.jade b/app/partials/includes/modules/search-result-table.jade index f6550aa5..4b5df259 100644 --- a/app/partials/includes/modules/search-result-table.jade +++ b/app/partials/includes/modules/search-result-table.jade @@ -18,7 +18,7 @@ script(type="text/ng-template", id="search-issues") div.status(tg-listitem-issue-status="issue") div.assigned-to(tg-listitem-assignedto="issue") - div.empty-search-results(ng-class="{'hidden': issues.length}") + div.empty-large(ng-class="{'hidden': issues.length}") include ../components/empty-search-results @@ -45,7 +45,7 @@ script(type="text/ng-template", id="search-userstories") div.status(tg-listitem-us-status="us") div.points(tg-bo-bind="us.total_points") - div.empty-search-results(ng-class="{'hidden': userstories.length}") + div.empty-large(ng-class="{'hidden': userstories.length}") include ../components/empty-search-results script(type="text/ng-template", id="search-tasks") @@ -66,7 +66,7 @@ script(type="text/ng-template", id="search-tasks") div.status(tg-listitem-task-status="task") div.assigned-to(tg-listitem-assignedto="task") - div.empty-search-results(ng-class="{'hidden': tasks.length}") + div.empty-large(ng-class="{'hidden': tasks.length}") include ../components/empty-search-results script(type="text/ng-template", id="search-wikipages") @@ -81,5 +81,5 @@ script(type="text/ng-template", id="search-wikipages") a(href="", tg-nav="project-wiki-page:project=project.slug,slug=wikipage.slug", tg-bo-bind="wikipage.slug") - div.empty-search-results(ng-class="{'hidden': wikipages.length}") + div.empty-large(ng-class="{'hidden': wikipages.length}") include ../components/empty-search-results diff --git a/app/partials/includes/modules/sprints.jade b/app/partials/includes/modules/sprints.jade index 0b52dd40..8c0b856b 100644 --- a/app/partials/includes/modules/sprints.jade +++ b/app/partials/includes/modules/sprints.jade @@ -15,9 +15,9 @@ section.sprints ) tg-svg(svg-icon="icon-add") - div.sprints-empty(ng-if="totalMilestones === 0") + div.empty-small(ng-if="totalMilestones === 0") img( - src="/#{v}/images/sprint-empty.png" + src="/#{v}/images/empty/empty_sprint.png" alt="{{'BACKLOG.SPRINTS.EMPTY' | translate}}" ) p.title(translate="BACKLOG.SPRINTS.EMPTY") diff --git a/app/styles/components/empty.scss b/app/styles/components/empty.scss new file mode 100644 index 00000000..23fe77c7 --- /dev/null +++ b/app/styles/components/empty.scss @@ -0,0 +1,34 @@ +%empty { + margin-top: 4rem; + text-align: center; + img { + margin-bottom: 1rem; + width: 100%; + } + .title { + @include font-size(large); + text-transform: uppercase; + } + p { + @include font-type(light); + margin: 0; + } + a { + @include font-type(light); + color: $primary; + } +} + +.empty-small { + @extend %empty; + img { + max-width: 175px; + } +} + +.empty-large { + @extend %empty; + img { + max-width: 800px; + } +} diff --git a/app/styles/modules/backlog/backlog-table.scss b/app/styles/modules/backlog/backlog-table.scss index 9a9d2b9e..99f22b12 100644 --- a/app/styles/modules/backlog/backlog-table.scss +++ b/app/styles/modules/backlog/backlog-table.scss @@ -284,23 +284,3 @@ } } } - -.empty-backlog { - @include font-type(light); - padding: 2rem; - text-align: center; - .row { - display: none; - } - img { - margin-bottom: 1rem; - } - .title { - @include font-size(large); - margin-bottom: .5rem; - text-transform: uppercase; - } - a { - color: $primary; - } -} diff --git a/app/styles/modules/issues/issues-table.scss b/app/styles/modules/issues/issues-table.scss index be04768d..2eddaf5b 100644 --- a/app/styles/modules/issues/issues-table.scss +++ b/app/styles/modules/issues/issues-table.scss @@ -175,19 +175,3 @@ display: inline-block; } } - -.empty-issues { - margin-top: 4rem; - text-align: center; - img { - margin-bottom: 1rem; - } - .title { - @include font-size(large); - text-transform: uppercase; - } - p { - @include font-type(light); - margin: 0; - } -} diff --git a/app/styles/modules/search/search-result-table.scss b/app/styles/modules/search/search-result-table.scss index 79571ccd..0f9109e5 100644 --- a/app/styles/modules/search/search-result-table.scss +++ b/app/styles/modules/search/search-result-table.scss @@ -85,19 +85,3 @@ .search-result-table-header { @include font-type(bold); } - -.empty-search-results { - margin-top: 4rem; - text-align: center; - img { - margin-bottom: 1rem; - } - .title { - @include font-size(large); - text-transform: uppercase; - } - p { - @include font-type(light); - margin: 0; - } -} From ed49295880a542ec23aed0e06387df06852f1b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 16 Sep 2016 12:54:18 +0200 Subject: [PATCH 133/315] Fix discover --- app/modules/discover/discover-search/discover-search.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/discover/discover-search/discover-search.jade b/app/modules/discover/discover-search/discover-search.jade index d605246c..e15ce454 100644 --- a/app/modules/discover/discover-search/discover-search.jade +++ b/app/modules/discover/discover-search/discover-search.jade @@ -8,7 +8,7 @@ div(tg-discover-search) .empty-large(ng-if="!vm.searchResult.size && !vm.loadingGlobal && !vm.loadingList") img( - src="/#{v}/images/empty_tex.png", + src="/#{v}/images/empty/empty_tex.png", alt="{{ DISCOVER.EMPTY | translate }}" ) p.title(translate="DISCOVER.EMPTY") From 36ebd8805dfe8be6608861f04f1b29a639af3373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 16 Sep 2016 13:09:03 +0200 Subject: [PATCH 134/315] Remove unused discover code --- .../discover/discover-search/discover-search.scss | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/app/modules/discover/discover-search/discover-search.scss b/app/modules/discover/discover-search/discover-search.scss index fa6eee89..8e50ad3c 100644 --- a/app/modules/discover/discover-search/discover-search.scss +++ b/app/modules/discover/discover-search/discover-search.scss @@ -105,18 +105,3 @@ } } } - -.empty-discover-results { - @include centered; - margin-top: 4rem; - text-align: center; - img { - margin-bottom: 1rem; - } - .title { - @include font-size(large); - @include font-type(light); - margin: 0; - text-transform: uppercase; - } -} From 1697e433287fb4d404b0fbe1a12702e2f5cdba85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 20 Sep 2016 08:37:43 +0200 Subject: [PATCH 135/315] Add placeholder for empty tags --- app/partials/includes/modules/admin/project-tags.jade | 10 ++++++---- app/styles/layout/admin-project-tags.scss | 5 ----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index 485e6326..2e5ca58e 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -39,10 +39,12 @@ section svg-icon="icon-close" ) - p.tags-empty( - ng-if="!projectTagsAll.length && !ctrl.loading" - translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY" - ) + .empty-large.tags-empty(ng-if="!projectTagsAll.length && !ctrl.loading") + img( + src="/#{v}/images/empty/empty_field.png" + alt="{{'BACKLOG.EMPTY' | translate}}" + ) + p.title(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY") .table-header.table-tags-editor( ng-if="projectTagsAll.length" diff --git a/app/styles/layout/admin-project-tags.scss b/app/styles/layout/admin-project-tags.scss index 0fdeb511..3880e826 100644 --- a/app/styles/layout/admin-project-tags.scss +++ b/app/styles/layout/admin-project-tags.scss @@ -1,8 +1,3 @@ -.tags-empty { - padding: 10vh 0 0; - text-align: center; -} - .add-tag-container { align-items: center; background: $mass-white; From 91d70577b78c449f15dbae3bec66b1d893669a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 20 Sep 2016 08:46:35 +0200 Subject: [PATCH 136/315] Remove wiki links page if there are no wiki pages --- app/partials/wiki/wiki-nav.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/partials/wiki/wiki-nav.jade b/app/partials/wiki/wiki-nav.jade index c2b97e46..30564de7 100644 --- a/app/partials/wiki/wiki-nav.jade +++ b/app/partials/wiki/wiki-nav.jade @@ -48,7 +48,7 @@ a.add-button( span(translate="WIKI.NAVIGATION.ACTION_ADD_LINK") <% } %> -ul.wiki-link-container.wiki-all-links +ul.wiki-link-container.wiki-all-links(ng-if="wikiLinks.length") li.wiki-link.fixed-link a.link-title( href="" From 718500685016738426485401d767e70fde5b78f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 20 Sep 2016 10:38:02 +0200 Subject: [PATCH 137/315] Fix contrib forms --- app/styles/modules/admin/contrib.scss | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/styles/modules/admin/contrib.scss b/app/styles/modules/admin/contrib.scss index 2db94d29..ec951f1d 100644 --- a/app/styles/modules/admin/contrib.scss +++ b/app/styles/modules/admin/contrib.scss @@ -51,3 +51,23 @@ } } } + +.contrib-form-wrapper { + align-items: center; + display: flex; + margin-bottom: 1rem; + input { + margin: 0; + } + .contrib-input { + border: 0; + flex: 5; + margin: 0; + } + .contrib-test { + border: 0; + flex: 1; + margin: 0; + margin-left: 1rem; + } +} From 0e4437722b062e7d5f2ecc9cd68ac87759fadc5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Tue, 20 Sep 2016 11:46:05 +0200 Subject: [PATCH 138/315] Allow to force re-login through url --- app/coffee/modules/auth.coffee | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/coffee/modules/auth.coffee b/app/coffee/modules/auth.coffee index a2e1df0c..def65a3c 100644 --- a/app/coffee/modules/auth.coffee +++ b/app/coffee/modules/auth.coffee @@ -37,12 +37,13 @@ class LoginPage constructor: (currentUserService, $location, $navUrls, $routeParams) -> if currentUserService.isAuthenticated() - url = $navUrls.resolve("home") - if $routeParams['next'] - url = $routeParams['next'] - $location.search('next', null) + if not $routeParams['force_login'] + url = $navUrls.resolve("home") + if $routeParams['next'] + url = decodeURIComponent($routeParams['next']) + $location.search('next', null) - $location.path(url) + $location.url(url) module.controller('LoginPage', LoginPage) From 8b8b1077ec4db1a353fcdb46ea5021ff71c48f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 20 Sep 2016 13:18:30 +0200 Subject: [PATCH 139/315] Add filter warning in filters button --- app/locales/taiga/locale-en.json | 1 + app/partials/kanban/kanban.jade | 3 ++- app/partials/taskboard/taskboard.jade | 2 +- app/styles/components/buttons.scss | 12 ++++++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 96549209..4b40b16b 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -205,6 +205,7 @@ "TITLE_ACTION_SEARCH": "Search", "ACTION_SAVE_CUSTOM_FILTER": "save as custom filter", "PLACEHOLDER_FILTER_NAME": "Write the filter name and press enter", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Type", "STATUS": "Status", diff --git a/app/partials/kanban/kanban.jade b/app/partials/kanban/kanban.jade index 885fb31e..2e65ebe2 100644 --- a/app/partials/kanban/kanban.jade +++ b/app/partials/kanban/kanban.jade @@ -29,9 +29,10 @@ div.wrapper(tg-kanban, ng-controller="KanbanController as ctrl" ) button.button-filter.e2e-open-filter( - ng-class="{'button-filters-applied': !!ctrl.selectedFilters.length}" ng-click="ctrl.openFilter = !ctrl.openFilter" + title="{{ctrl.selectedFilters.length}} {{'COMMON.FILTERS.APPLIED_FILTERS_NUM' | translate}}" ) + span.filter-num(ng-if="ctrl.selectedFilters.length") {{ctrl.selectedFilters.length}} tg-svg(svg-icon="icon-filters") include ../includes/modules/kanban-table diff --git a/app/partials/taskboard/taskboard.jade b/app/partials/taskboard/taskboard.jade index 81274531..d9e85720 100644 --- a/app/partials/taskboard/taskboard.jade +++ b/app/partials/taskboard/taskboard.jade @@ -29,9 +29,9 @@ div.wrapper(tg-taskboard, ng-controller="TaskboardController as ctrl", on-zoom-change="ctrl.setZoom(zoomLevel, zoom)" ) button.button-filter.e2e-open-filter( - ng-class="{'button-filters-applied': !!ctrl.selectedFilters.length}" ng-click="ctrl.openFilter = !ctrl.openFilter" ) + span.filter-num(ng-if="ctrl.selectedFilters.length") {{ctrl.selectedFilters.length}} tg-svg(svg-icon="icon-filters") .taskboard-inner diff --git a/app/styles/components/buttons.scss b/app/styles/components/buttons.scss index 2a6d9e27..96dbb3b5 100755 --- a/app/styles/components/buttons.scss +++ b/app/styles/components/buttons.scss @@ -161,8 +161,20 @@ a.button-gray { background: $whitish; margin-left: 1rem; padding: .4rem .5rem; + position: relative; &:hover { background: $gray-light; fill: $whitish; } + .filter-num { + @include font-size(small); + @include font-type(medium); + background: $red; + border-radius: 50%; + height: 1rem; + left: -.5rem; + position: absolute; + top: -.5rem; + width: 1rem; + } } From 34613d447f748c607e1c0abbb9975df28b957cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 20 Sep 2016 13:25:58 +0200 Subject: [PATCH 140/315] [i18n] Add norwegian Bokmal (nb) translation --- CHANGELOG.md | 2 + app/locales/taiga/locale-nb.json | 1713 ++++++++++++++++++++++++++++++ 2 files changed, 1715 insertions(+) create mode 100644 app/locales/taiga/locale-nb.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 5169a577..8f28be9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ - Add Wiki history - Third party integrations: - Included gogs as builtin integration. +- i18n: + - Add norwegian Bokmal (nb) translation. ### Misc - Lots of small and not so small bugfixes. diff --git a/app/locales/taiga/locale-nb.json b/app/locales/taiga/locale-nb.json new file mode 100644 index 00000000..90958db1 --- /dev/null +++ b/app/locales/taiga/locale-nb.json @@ -0,0 +1,1713 @@ +{ + "COMMON": { + "YES": "Ja", + "NO": "Nei", + "OR": "eller", + "LOADING": "Laster...", + "LOADING_PROJECT": "Laster prosjekt...", + "DATE": "DD MMM YYYY", + "DATETIME": "DD MMM YYYY HH:mm", + "SAVE": "Lagre", + "CANCEL": "Avbryt", + "ACCEPT": "Aksepter", + "DELETE": "Slett", + "CREATE": "Opprett", + "ADD": "Legg til", + "COPY_TO_CLIPBOARD": "Kopier til utklippstavlen: Ctrl+C", + "EDIT": "Endre", + "DRAG": "Dra", + "TAG_LINE": "Ditt smidige, gratis og åpen kildekode prosjektstyringsverktøy", + "TAG_LINE_2": "ELSK DITT PROSJEKT", + "BLOCK": "Blokker", + "BLOCK_TITLE": "Blokker elementet for eksempel hvis det har en avhengighet som ikke kan oppfylles", + "BLOCKED": "Blokkert", + "UNBLOCK": "Opphev blokkeringen", + "UNBLOCK_TITLE": "Avblokker dette elementet", + "BLOCKED_NOTE": "Hvorfor er dette blokkert?", + "BLOCKED_REASON": "Venligst forklar årsaken", + "CREATED_BY": "Opprettet av {{fullDisplayName}}", + "FROM": "fra", + "TO": "til", + "CLOSE": "lukk", + "GO_HOME": "Led meg hjem", + "PLUGINS": "Programtillegg", + "BETA": "Vi er i beta!", + "ONE_ITEM_LINE": "En enhet per linje...", + "NEW_BULK": "Ny sett (mange)", + "RELATED_TASKS": "Relaterte oppgaver", + "PREVIOUS": "Previous", + "NEXT": "Neste", + "LOGOUT": "Logg ut", + "EXTERNAL_USER": "en ekstern bruker", + "GENERIC_ERROR": "En av våre Oompa Loompaer sier {{error}}.", + "IOCAINE_TEXT": "Føler du deg litt overveldet av en oppgave? Sørg for at andre vet om det ved å klikke på \"Iocane\" når du redigerer en oppgave. Det er mulig å bli immun mot denne (fiktive) dødelige giften ved å konsumere små mengder over tid, akkurat som det er mulig å bli bedre på det du gjør ved av og til å ta på deg ekstra utfordringer!", + "CLIENT_REQUIREMENT": "Klientkravet er nytt krav som tidligere ikke var forventet, og det er nødt til å være en del av prosjektet", + "TEAM_REQUIREMENT": "TeamBehov er et behov som må eksistere i prosjektet, men som ikke har noen kostnad for klienten", + "OWNER": "Prosjekteier", + "CAPSLOCK_WARNING": "Vær forsiktig! Du bruker blokkbokstaver i et felt som er for store og små bokstaver.", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, + "FORM_ERRORS": { + "DEFAULT_MESSAGE": "Denne verdien virker å være ugyldig", + "TYPE_EMAIL": "Dette skal være en gyldig epost.", + "TYPE_URL": "Denne verdien skal være en URL", + "TYPE_URLSTRICT": "Denne verdien skal være en URL", + "TYPE_NUMBER": "Denne verdien skal være et gyldig tall.", + "TYPE_DIGITS": "Denne verdien skal være sifre.", + "TYPE_DATEISO": "Denne verdien skal være en gyldig dato (YYYY-MM-DD).", + "TYPE_ALPHANUM": "Denne verdien skal være alfanumerisk.", + "TYPE_PHONE": "Denne verdien skal være et gyldig telefonnummer.", + "NOTNULL": "Denne verdien skal ikke være \"null\"", + "NOT_BLANK": "Denne verdien skal ikke være blank.", + "REQUIRED": "Denne verdien er nødvendig.", + "REGEXP": "Denne verdien virker å være ugyldig", + "MIN": "Denne verdien skal være større eller lik %s.", + "MAX": "Denne verdien skal være mindre eller lik %s.", + "RANGE": "Denne verdien skal være mellom %s og %s.", + "MIN_LENGTH": "Denne verdien er for kort. Den skal ha %s eller flere bokstaver.", + "MAX_LENGTH": "Denne verdien er for lang. Den skal ha %s eller færre bokstaver.", + "RANGE_LENGTH": "Lengden på denne verdien er ikke gydlig. Den skal være mellom %s og %s teng lang.", + "MIN_CHECK": "Du må velge minst %s valg.", + "MAX_CHECK": "Du må velge %s valg eller mindre.", + "RANGE_CHECK": "Du må velge mellom %s og %s valg.", + "EQUAL_TO": "Denne verdien skal være den samme.", + "LINEWIDTH": "En eller flere av linjene er kanskje for lang. Prøv å hold de under %s tegn.", + "PIKADAY": "Ugyldig datoformat , kan du bruke DD MMM YYYY (eks: 23 mars 1984)" + }, + "PICKERDATE": { + "FORMAT": "DD MMM YYYY", + "IS_RTL": "falsk", + "FIRST_DAY_OF_WEEK": "1", + "PREV_MONTH": "Forrige Måned", + "NEXT_MONTH": "Neste Måned", + "MONTHS": { + "JAN": "Januar", + "FEB": "Februar", + "MAR": "Mars", + "APR": "April", + "MAY": "Mai", + "JUN": "Juni", + "JUL": "Juli", + "AUG": "August", + "SEP": "September", + "OCT": "Oktober", + "NOV": "November", + "DEC": "Desember" + }, + "WEEK_DAYS": { + "SUN": "Søndag", + "MON": "Mandag", + "TUE": "Tirsdag", + "WED": "Onsdag", + "THU": "Torsdag", + "FRI": "Fredag", + "SAT": "Lørdag" + }, + "WEEK_DAYS_SHORT": { + "SUN": "Man", + "MON": "Man", + "TUE": "Tirs", + "WED": "Ons", + "THU": "Tors", + "FRI": "Fre", + "SAT": "Lør" + } + }, + "SEE_USER_PROFILE": "Se See {{username }} profil", + "USER_STORY": "Brukerhistorie", + "TASK": "Oppgave", + "ISSUE": "Hendelse", + "EPIC": "Epic", + "TAGS": { + "PLACEHOLDER": "Jeg har den! Ta meg...", + "DELETE": "Slett etikett", + "ADD": "Legg til etikett" + }, + "DESCRIPTION": { + "EMPTY": "Tomme felter er så kjedelig... kom igjen, vær kreativ...", + "NO_DESCRIPTION": "Ingen beskrivelse enda" + }, + "FIELDS": { + "SUBJECT": "Subjekt", + "NAME": "Navn", + "URL": "URL", + "DESCRIPTION": "Beskrivelse", + "VALUE": "Verdi", + "SLUG": "Slug", + "COLOR": "Farge", + "IS_CLOSED": "Er lukket?", + "STATUS": "Status", + "TYPE": "Type", + "SEVERITY": "Alvorlighetsgrad", + "PRIORITY": "Prioritet", + "ASSIGNED_TO": "Tildelt til", + "POINTS": "Poeng", + "BLOCKED_NOTE": "blokkert notat", + "IS_BLOCKED": "er blokkert", + "REF": "Ref", + "VOTES": "Stemmer", + "SPRINT": "Sprint" + }, + "ROLES": { + "ALL": "Alle" + }, + "ASSIGNED_TO": { + "NOT_ASSIGNED": "Ikke tildelt", + "ASSIGN": "Tildel", + "DELETE_ASSIGNMENT": "Slett tildeling", + "REMOVE_ASSIGNED": "Fjern tildeling", + "TOO_MANY": "...for mange brukere, forstett å filtrere", + "CONFIRM_UNASSIGNED": "Er du sikker på at du vil la den være ikke tildelt?", + "TITLE_ACTION_EDIT_ASSIGNMENT": "Rediger tildeling", + "SELF": "Tildel til meg" + }, + "STATUS": { + "CLOSED": "Lukket", + "OPEN": "Åpen" + }, + "WATCHERS": { + "WATCHERS": "Følgere", + "ADD": "Legg til følgere", + "TITLE_ADD": "Legg til et prosjektmedlem til følgerlisten", + "DELETE": "Slett følger", + "TITLE_LIGHTBOX_DELETE_WARTCHER": "Slett følger..." + }, + "WATCH_BUTTON": { + "WATCH": "Følg", + "WATCHING": "Følger med på", + "UNWATCH": "Ikke overvåk", + "WATCHERS": "Følgere", + "BUTTON_TITLE": "Følg/Ikke følg denne enheten", + "COUNTER_TITLE": "{total, plural, one{en følger} other{# følgere}}" + }, + "VOTE_BUTTON": { + "UPVOTE": "Stem på", + "UPVOTED": "Stemt på", + "DOWNVOTE": "Stem ned", + "VOTERS": "Stemmegivere", + "BUTTON_TITLE": "Stem opp / Stem ned dette elementet", + "COUNTER_TITLE": "{total, plural, one{en stemme} other{# stemmer}}" + }, + "CUSTOM_ATTRIBUTES": { + "CUSTOM_FIELDS": "Egendefinerte felter", + "SAVE": "Lagre Egendefinert Felt", + "EDIT": "Endre Egendefinert Felt", + "DELETE": "Slett egendefinert egenskap", + "CONFIRM_DELETE": "Husk at alle verdier i dette egendefinerte feltet vil bli slettet.\nEr du sikker på at du vil fortsette?" + }, + "FILTERS": { + "TITLE": "Filtre", + "INPUT_PLACEHOLDER": "Subjekt eller referanse", + "TITLE_ACTION_FILTER_BUTTON": "Søk", + "INPUT_SEARCH_PLACEHOLDER": "Subjekt eller referanse", + "TITLE_ACTION_SEARCH": "Søk", + "ACTION_SAVE_CUSTOM_FILTER": "lagre som egendefinert filter", + "PLACEHOLDER_FILTER_NAME": "Skriv filternavnet og trykk enter", + "CATEGORIES": { + "TYPE": "Type", + "STATUS": "Status", + "SEVERITY": "Alvorlighetsgrad", + "PRIORITIES": "Prioriteter", + "TAGS": "Etiketter", + "ASSIGNED_TO": "Tildelt til", + "CREATED_BY": "Laget av", + "CUSTOM_FILTERS": "Egendefinert filtre" + }, + "CONFIRM_DELETE": { + "TITLE": "Slett egendefinert filter", + "MESSAGE": "det egendefinerte filteret '{{customFilterName}}'" + } + }, + "WYSIWYG": { + "H1_BUTTON": "Første Nivå Overskrift", + "H1_SAMPLE_TEXT": "Din tittel her...", + "H2_BUTTON": "Andre Nivå Overskrift", + "H2_SAMPLE_TEXT": "Din tittel her...", + "H3_BUTTON": "Tredje Nivå Overskrift", + "H3_SAMPLE_TEXT": "Din tittel her...", + "BOLD_BUTTON": "Fet", + "BOLD_BUTTON_SAMPLE_TEXT": "Din tekst her...", + "ITALIC_BUTTON": "Kursiv", + "ITALIC_SAMPLE_TEXT": "Din tekst her...", + "STRIKE_BUTTON": "Gjennomstrek", + "STRIKE_SAMPLE_TEXT": "Din tekst her...", + "BULLETED_LIST_BUTTON": "Punktliste", + "BULLETED_LIST_SAMPLE_TEXT": "Din tekst her...", + "NUMERIC_LIST_BUTTON": "Numerisk liste", + "NUMERIC_LIST_SAMPLE_TEXT": "Din tekst her...", + "PICTURE_BUTTON": "Bilde", + "PICTURE_SAMPLE_TEXT": "Din alternative tekst til bildet her...", + "LINK_BUTTON": "Lenke", + "LINK_SAMPLE_TEXT": "Din tekst til lenken her...", + "QUOTE_BLOCK_BUTTON": "Sitatblokk", + "QUOTE_BLOCK_SAMPLE_TEXT": "Din tekst her...", + "CODE_BLOCK_BUTTON": "Kodeblokk", + "CODE_BLOCK_SAMPLE_TEXT": "Din tekst her...", + "PREVIEW_BUTTON": "Forhåndsvisning", + "EDIT_BUTTON": "Endre", + "ATTACH_FILE_HELP": "Legg ved filer ved å dra og slippe på tekstområdet ovenfor.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", + "MARKDOWN_HELP": "Markdown syntaks hjelp" + }, + "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, + "SPRINTS": { + "NAME": "Sprinter", + "VIEW_SPRINTS": "Se sprinter", + "ADD_SPRINTS": "Legg til sprinter", + "MODIFY_SPRINTS": "Rediger sprinter", + "DELETE_SPRINTS": "Slett sprinter" + }, + "USER_STORIES": { + "NAME": "Brukerhistorie", + "VIEW_USER_STORIES": "Se på brukerhistorier", + "ADD_USER_STORIES": "Legg til brukerhistorier", + "MODIFY_USER_STORIES": "Rediger brukerhistorier", + "COMMENT_USER_STORIES": "Comment user stories", + "DELETE_USER_STORIES": "Slett brukerhistorier" + }, + "TASKS": { + "NAME": "Oppgaver", + "VIEW_TASKS": "Se på oppgaver", + "ADD_TASKS": "Legg til oppgave", + "MODIFY_TASKS": "Rediger oppgaver", + "COMMENT_TASKS": "Comment tasks", + "DELETE_TASKS": "Slett oppgaver" + }, + "ISSUES": { + "NAME": "Hendelser", + "VIEW_ISSUES": "Vis hendelser", + "ADD_ISSUES": "Legg til hendelser", + "MODIFY_ISSUES": "Endre hendelser", + "COMMENT_ISSUES": "Comment issues", + "DELETE_ISSUES": "Slett hendelser" + }, + "WIKI": { + "NAME": "Wiki", + "VIEW_WIKI_PAGES": "Se wiki-sider", + "ADD_WIKI_PAGES": "Legg til wiki-sider", + "MODIFY_WIKI_PAGES": "Endre wiki-sider", + "DELETE_WIKI_PAGES": "Slett wiki-sider", + "VIEW_WIKI_LINKS": "Se wiki-lenker", + "ADD_WIKI_LINKS": "Legg til wiki-lenker", + "DELETE_WIKI_LINKS": "Slett wiki-lenker" + } + }, + "META": { + "PAGE_TITLE": "Taiga", + "PAGE_DESCRIPTION": "Taiga er en prosjektstyringsplatform for oppstartsbedrifter og agile utviklere & designere som vil ha et enkelt, nydelig verktøy som gjør arbeidet virkelig hyggelig." + } + }, + "LOGIN": { + "PAGE_TITLE": "Login - Taiga", + "PAGE_DESCRIPTION": "Logg på Taiga, prosjektledelsesplattformen for startups, smidige utviklere og designere som ønsker et enkelt og pent verktøy som gjør arbeidet en fornøyelse." + }, + "AUTH": { + "INVITED_YOU": "har invitert deg til å delta i prosjektet", + "NOT_REGISTERED_YET": "Ikke registrert?", + "REGISTER": "Registrer", + "CREATE_ACCOUNT": "opprett gratis konto her" + }, + "LOGIN_COMMON": { + "HEADER": "Jeg har allerede en konto", + "PLACEHOLDER_AUTH_NAME": "Brukernavn eller e-post (case sensitiv)", + "LINK_FORGOT_PASSWORD": "Glemte det?", + "TITLE_LINK_FORGOT_PASSWORD": "Har du glemt ditt passord?", + "ACTION_ENTER": "Enter", + "ACTION_SIGN_IN": "Login", + "PLACEHOLDER_AUTH_PASSWORD": "Passord (case sensitiv)" + }, + "LOGIN_FORM": { + "ERROR_AUTH_INCORRECT": "Ifølge våre Oompa Loompaer er ditt brukernavn, e-post eller passord feil.", + "SUCCESS": "Våre Oompa Loompaer er glade, velkommen til Taiga ." + }, + "REGISTER": { + "PAGE_TITLE": "Registrer - Taiga", + "PAGE_DESCRIPTION": "Opprett en konto i Taiga , prosjektledelsesplattformen for startups, smidige utviklere og designere som ønsker et enkelt og pent verktøy som gjør arbeidet en fornøyelse." + }, + "REGISTER_FORM": { + "TITLE": "Registrer en ny Taiga konto (gratis)", + "PLACEHOLDER_NAME": "Velg et brukernavn (case sensitiv)", + "PLACEHOLDER_FULL_NAME": "Skriv inn fullt navn", + "PLACEHOLDER_EMAIL": "Din e-post", + "PLACEHOLDER_PASSWORD": "Set et passord (case sensitiv)", + "ACTION_SIGN_UP": "Registrer", + "TITLE_LINK_LOGIN": "Logg inn", + "LINK_LOGIN": "Er du allerede registrert? Logg inn" + }, + "FORGOT_PASSWORD": { + "PAGE_TITLE": "Glemt passord - Taiga", + "PAGE_DESCRIPTION": "Skriv inn brukernavn eller e-post for å få et nytt passord, og du kan få tilgang til Taiga igjen." + }, + "FORGOT_PASSWORD_FORM": { + "TITLE": "Obs, har du glemt passordet ditt?", + "SUBTITLE": "Skriv inn ditt brukernavn eller e-post for å få et nytt", + "PLACEHOLDER_FIELD": "Brukernavn eller e-post", + "ACTION_RESET_PASSWORD": "Nullstill passord", + "LINK_CANCEL": "Eh, ta meg tilbake. Jeg tror jeg husker det.", + "SUCCESS_TITLE": "Sjekk innboksen din!", + "SUCCESS_TEXT": "\nVi har sendte deg en e-post med instruksjoner for å sette et nytt passord", + "ERROR": "I følge våre Oompa Loompas har du ikke registert deg ennå." + }, + "CHANGE_PASSWORD": { + "PAGE_TITLE": "Endre ditt passord - Taiga", + "PAGE_DESCRIPTION": "Angi et nytt passord for din Taiga konto og hei!, det kan være lurt å spise litt mer jern-rik mat, det er godt for hjernen din :P", + "SECTION_NAME": "Endre passord", + "FIELD_CURRENT_PASSWORD": "Nåværende passord", + "PLACEHOLDER_CURRENT_PASSWORD": "Din nåværende passord (eller tomt hvis du ikke har ett)", + "FIELD_NEW_PASSWORD": "Nytt passord", + "PLACEHOLDER_NEW_PASSWORD": "Skriv inn nytt passord", + "FIELD_RETYPE_PASSWORD": "Gjenta nytt passord", + "PLACEHOLDER_RETYPE_PASSWORD": "Skriv inn passordet på nytt", + "ERROR_PASSWORD_MATCH": "Passordene er ikke like" + }, + "CHANGE_PASSWORD_RECOVERY_FORM": { + "TITLE": "Opprett ett nytt Taiga pass", + "SUBTITLE": "Og hei, det kan være lurt å spise litt mer jern-rik mat, det er godt for hjernen din :P", + "PLACEHOLDER_NEW_PASSWORD": "Nytt passord", + "PLACEHOLDER_RE_TYPE_NEW_PASSWORD": "Skriv inn passord på nytt", + "ACTION_RESET_PASSWORD": "Nullstill passord", + "ERROR": "Våre Oompa Loompaer finner ikke forespørselen om å gjenopprette passordet ditt. Prøv å å gjennopprette en gang til.", + "SUCCESS": "Oompa Loompaene våre lagret ditt nye passord.
Prøv å logge inn med det." + }, + "INVITATION": { + "PAGE_TITLE": "Invitasjonsbekreftelse - Taiga", + "PAGE_DESCRIPTION": "Godta invitasjonen til å delta i et prosjekt i Taiga, prosjektledelsesplattformen for startups, smidige utviklere og designere som ønsker et enkelt og pent verktøy som gjør arbeidet fornøyelig." + }, + "INVITATION_LOGIN_FORM": { + "NOT_FOUND": "Våre Oompa Loompas can ikke finne invitasjonen din.", + "SUCCESS": "Du er nå sluttet til prosjektet {{project_name}}, velkommen!", + "ERROR": "I følge våre Oompa Loompas har du ikke registert deg ennå eller skrevet et ugyldig passord" + }, + "HOME": { + "PAGE_TITLE": "Hjem - Taiga", + "PAGE_DESCRIPTION": "Taiga hjemmeside med dine viktigste prosjekter og alle dine tildelte og overvåkede brukerhistorier, oppgaver og hendelser", + "EMPTY_WORKING_ON": "Det føles tom, gjør det ikke? Start å jobbe med Taiga og du vil se her brukerhistorier, opggaver og hendelser du jobber med.", + "EMPTY_WATCHING": "Følg Brukerhistorier, Oppgaver, Hendelser i dine projekter og bli varslet når noe blir endret :)", + "EMPTY_PROJECT_LIST": "Du har ikke noen prosjekter enda", + "WORKING_ON_SECTION": "Arbeider med", + "WATCHING_SECTION": "Følger med på", + "DASHBOARD": "Prosjektoversikt" + }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Ikke tildelt" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Stemmer", + "NAME": "Navn", + "PROJECT": "Prosjekt", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Status", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Blokkert", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, + "PROJECTS": { + "PAGE_TITLE": "Mine prosjekter - Taiga", + "PAGE_DESCRIPTION": "Liste med alle dine prosjekter du kan endre rekkefølgen på eller opprette et nytt.", + "MY_PROJECTS": "Mine prosjekter" + }, + "ATTACHMENT": { + "SECTION_NAME": "vedlegg", + "TITLE": "{{ fileName }} lastet opp {{ date }}", + "LIST_VIEW_MODE": "Listevisnings-modus", + "GALLERY_VIEW_MODE": "Gallerivisningsmodus", + "DESCRIPTION": "Skriv en kort beskrivelse", + "DEPRECATED": "(avviklet)", + "DEPRECATED_FILE": "Avviklet?", + "ADD": "Legg til nytt vedlegg. {{maxFileSizeMsg}}", + "DROP": "Slipp vedlegg her!", + "SHOW_DEPRECATED": "Vis foreldete vedlegg", + "HIDE_DEPRECATED": "- skjul foreldete vedlegg", + "COUNT_DEPRECATED": "({{ counter }} avviklet)", + "MAX_UPLOAD_SIZE": "Maksimal størrelse for opplastning er {{maxFileSize}}", + "DATE": "DD MMM YYYY [at] hh:mm", + "ERROR_UPLOAD_ATTACHMENT": "Vi var ikke kapable til å laste opp '{{fileName}}'. {{errorMessage}}", + "TITLE_LIGHTBOX_DELETE_ATTACHMENT": "Slett vedlegg...", + "MSG_LIGHTBOX_DELETE_ATTACHMENT": "vedlegget '{{fileName}}'", + "ERROR_DELETE_ATTACHMENT": "Vi hadde ikke mulighet til å slette: {{errorMessage}}", + "ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) er for stor for våre Oompa Loompaer. Prøv med en mindre enn ({{maxFileSize}})", + "FIELDS": { + "IS_DEPRECATED": "Er foreldet" + } + }, + "PAGINATION": { + "PREVIOUS": "Forrige", + "NEXT": "Neste" + }, + "ADMIN": { + "COMMON": { + "TITLE_ACTION_EDIT_VALUE": "Rediger verdi", + "TITLE_ACTION_DELETE_VALUE": "Slett verdi", + "TITLE_ACTION_DELETE_TAG": "Slett etikett" + }, + "HELP": "Trenger du hjelp? Sjekk ut vår hjelpeside!", + "PROJECT_DEFAULT_VALUES": { + "TITLE": "Standard verdier", + "SUBTITLE": "Sett standard verdi for alle valgte felter." + }, + "MEMBERSHIPS": { + "TITLE": "Konfigurer medlemmer", + "PAGE_TITLE": "Medlemskap - {{projectName}}", + "ADD_BUTTON": "+ Nytt medlem", + "ADD_BUTTON_TITLE": "Legg til nytt medlem", + "LIMIT_USERS_WARNING_MESSAGE_FOR_ADMIN": "Dessverre, dette prosjektet har nådd sin grense på ({{members}}) tillatte medlemmer.", + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Dette prosjektet har nådd sin grense for ({{members}}) tillatte medlemmer. Dersom du ønsker å øke den grensen, venligst kontakt en administrator." + }, + "PROJECT_EXPORT": { + "TITLE": "Eksport", + "SUBTITLE": "Eksportér prosjektet for å lagre en backup, eller for å lage et nytt en basert på dette.", + "EXPORT_BUTTON": "Eksport", + "EXPORT_BUTTON_TITLE": "Eksporter ditt prosjekt", + "LOADING_TITLE": "Vi genererer din eksportfil", + "DUMP_READY": "Din data-dump-fil er klar!", + "LOADING_MESSAGE": "Vennligst ikke lukk denne siden.", + "ASYNC_MESSAGE": "Vi sender deg en epost når det er klart.", + "SYNC_MESSAGE": "Hvis nedlastningen ikke starter automatisk, klikk
her.", + "ERROR": "Våre Oompa Loompaer har problemer med å generere din data dump. Vennligst prøv på nytt.", + "ERROR_BUSY": "Beklager, våre Oompa Loomper er svært opptatt akkuratt nå. Venligst prøv igjen om noen få minutter.", + "ERROR_MESSAGE": "Våre Oompa Loompaer har problemer med å generere din dump: {{message}}" + }, + "MODULES": { + "TITLE": "Moduler", + "ENABLE": "Aktiver", + "DISABLE": "Deaktiver", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", + "BACKLOG": "Backlog", + "BACKLOG_DESCRIPTION": "Hold styr på dine brukerhistorier for å vedlikeholde en organisert visning over kommende og prioritert arbeid.", + "NUMBER_SPRINTS": "Antatt antall sprinter", + "NUMBER_SPRINTS_HELP": "0 for et ubestemt antall", + "NUMBER_US_POINTS": "Antatt total av brukerhistoriepoeng", + "NUMBER_US_POINTS_HELP": "0 for et ubestemt antall", + "KANBAN": "Kanban", + "KANBAN_DESCRIPTION": "Organiser prosjektet ditt på en avabalansert måte med dette panelet.", + "ISSUES": "Hendelser", + "ISSUES_DESCRIPTION": "Forfølg feil, spørsmål og forbedringer relatert til ditt prosjekt. Ikke gå glipp av noe!", + "WIKI": "Wiki", + "WIKI_DESCRIPTION": "Legg til, endre eller slette innhold i samarbeid med andre. Dette er det rette stedet for din prosjektdokumentasjon.", + "MEETUP": "Møtes", + "MEETUP_DESCRIPTION": "Velg ditt videokonferansesystem", + "SELECT_VIDEOCONFERENCE": "Velg et videokonferansesystem", + "SALT_CHAT_ROOM": "Legg et prefiks til navnet til chatterommet", + "JITSI_CHAT_ROOM": "Jitsl", + "APPEARIN_CHAT_ROOM": "Vises", + "TALKY_CHAT_ROOM": "Talky", + "CUSTOM_CHAT_ROOM": "Egendefinert", + "URL_CHAT_ROOM": "URL til ditt chatrom" + }, + "PROJECT_PROFILE": { + "PAGE_TITLE": "{{sectionName}} - Prosjektprofil - {{projectName}}", + "PROJECT_DETAILS": "Prosjektdetaljer", + "PROJECT_NAME": "Prosjektnavn", + "PROJECT_SLUG": "Prosjekt slug", + "TAGS": "Etiketter", + "DESCRIPTION": "Beskrivelse", + "RECRUITING": "Leter dette prosjektet etter folk?", + "RECRUITING_MESSAGE": "Hva leter du etter?", + "RECRUITING_PLACEHOLDER": "Definer profilene du leter etter", + "PUBLIC_PROJECT": "Offentlig prosjekt", + "PRIVATE_PROJECT": "Privat prosjekt", + "PRIVATE_OR_PUBLIC": "Hva er forskjellen mellom offentlige og private prosjekt?", + "DELETE": "Slett dette prosjektet", + "LOGO_HELP": "Bildet vil bli skalert til 80x80px.", + "CHANGE_LOGO": "Endre logo", + "ACTION_USE_DEFAULT_LOGO": "Bruk standardbilde", + "MAX_PRIVATE_PROJECTS": "Du har nådd maksimalt antall private prosjekter som tillates med din gjeldende plan", + "MAX_PRIVATE_PROJECTS_MEMBERS": "Det maksimale antall medlemmer for private prosjekter er overskredet", + "MAX_PUBLIC_PROJECTS": "Dessverre , du har nådd det maksimale antallet offentlige prosjekter som tillates av din gjeldende plan", + "MAX_PUBLIC_PROJECTS_MEMBERS": "Prosjektet overskrider maksimalt antall medlemmer for offentlige prosjekter", + "PROJECT_OWNER": "Prosjekteier", + "REQUEST_OWNERSHIP": "Be om eierskap", + "REQUEST_OWNERSHIP_CONFIRMATION_TITLE": "Ønsker du å bli den nye prosjekteieren?", + "REQUEST_OWNERSHIP_DESC": "Be om at den nåværende prosjekteieren {{name}} overfører eierskapet av dette prosjektet til deg.", + "REQUEST_OWNERSHIP_BUTTON": "Be om", + "REQUEST_OWNERSHIP_SUCCESS": "Vi vil varsle prosjekteieren", + "CHANGE_OWNER": "Endre eier", + "CHANGE_OWNER_SUCCESS_TITLE": "Ok, din henvendelse har blitt sendt!", + "CHANGE_OWNER_SUCCESS_DESC": "Vi vil varsle deg via epost hvis anmodningen om eierskap til prosjektet ble godtatt eller avslått" + }, + "REPORTS": { + "TITLE": "Rapporter", + "SUBTITLE": "Eksporter dine prosjektdata i CSV-format og lag dine egne rapporter.", + "DESCRIPTION": "Last ned en CSV fil eller kopier den genererte URL'en og åpne den i din favoritt tekstbehandler for å lage dine egne prosjektdatarapporter. Du vil kunne visualisere og analysere alle dine data enkelt.", + "HELP": "Hvordan bruke dette i mitt eget regneark?", + "REGENERATE_TITLE": "Endre URL", + "REGENERATE_SUBTITLE": "Dette vil endre CSV datatilgangsURLen. Den forrige URLen vil bli deaktivert. Er du sikker?" + }, + "CSV": { + "SECTION_TITLE_EPIC": "epics reports", + "SECTION_TITLE_US": "brukerhistorie-rapporter", + "SECTION_TITLE_TASK": "oppgaverapport", + "SECTION_TITLE_ISSUE": "Hendelsesrapport", + "DOWNLOAD": "Last ned CSV", + "URL_FIELD_PLACEHOLDER": "Venligst regenerer CSV url", + "TITLE_REGENERATE_URL": "Regenerer CSV url", + "ACTION_GENERATE_URL": "Generer URL", + "ACTION_REGENERATE": "Generer på nytt" + }, + "CUSTOM_FIELDS": { + "TITLE": "Egendefinerte felter", + "SUBTITLE": "Spesifiser egendefinerte felter for dine brukerhistorier, oppgaver og hendelser", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", + "US_DESCRIPTION": "Brukerhistorier tilpassede felter", + "US_ADD": "Legg til et egendefinert felt i Brukerhistorier", + "TASK_DESCRIPTION": "Oppgaver egendefinerte felter", + "TASK_ADD": "Legg til et egendefinert felt i flere oppgaver", + "ISSUE_DESCRIPTION": "Hendelser egendefinerte felter", + "ISSUE_ADD": "Legg til et egendefinert felt i Hendelser", + "FIELD_TYPE_TEXT": "Tekst", + "FIELD_TYPE_MULTI": "Flere linjer", + "FIELD_TYPE_DATE": "Dato", + "FIELD_TYPE_URL": "Url" + }, + "PROJECT_VALUES": { + "PAGE_TITLE": "{{sectionName}} - Prosjekt verdier - {{projectName}}", + "REPLACEMENT": "Alle elementer med denne verdien vil bli endre til", + "ERROR_DELETE_ALL": "Du kan ikke slette alle verdier." + }, + "PROJECT_VALUES_POINTS": { + "TITLE": "Poeng", + "SUBTITLE": "Spesifiser poengene dine brukerhistorier kan estimeres som", + "US_TITLE": "BH poeng", + "ACTION_ADD": "Legg til nytt poeng" + }, + "PROJECT_VALUES_PRIORITIES": { + "TITLE": "Prioriteter", + "SUBTITLE": "Angi prioriteringen som dine hendelser vil ha", + "ISSUE_TITLE": "Hendelse prioriteringer", + "ACTION_ADD": "Legg til ny prioritet" + }, + "PROJECT_VALUES_SEVERITIES": { + "TITLE": "Alvorlighetsgrad", + "SUBTITLE": "Spesifiser alvorlighetsgraden som hendelsen vil ha", + "ISSUE_TITLE": "Hendelse alvorlighetsgrad", + "ACTION_ADD": "Legg til ny alvorlighetsgrad" + }, + "PROJECT_VALUES_STATUS": { + "TITLE": "Status", + "SUBTITLE": "Angi statusene som dine brukerhistorier, oppgaver og hendelser vil følge", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", + "TASK_TITLE": "Oppgave Statuser", + "ISSUE_TITLE": "Hendelsesstatuser" + }, + "PROJECT_VALUES_TYPES": { + "TITLE": "Typer", + "SUBTITLE": "Angi hvilke typer dine hendelser kan være", + "ISSUE_TITLE": "Hendelsestyper", + "ACTION_ADD": "Legg til {{objName}}" + }, + "PROJECT_VALUES_TAGS": { + "TITLE": "Etiketter", + "SUBTITLE": "View and edit the color of your tags", + "EMPTY": "Currently there are no tags", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Legg til etikett", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" + }, + "ROLES": { + "PAGE_TITLE": "Roller - {{projectName}}", + "WARNING_NO_ROLE": "Vær forsiktig, ingen roller i ditt prosjekt vil kunne estimere poengverdier for brukerhistorier", + "HELP_ROLE_ENABLED": "Når aktivert; medlemmer tildelt denne rollen vil kunne estimere poengverdier for brukerhistorier", + "DISABLE_COMPUTABLE_ALERT_TITLE": "Er du sikker på at du vil deaktivere denne rollens beregninger?", + "DISABLE_COMPUTABLE_ALERT_SUBTITLE": "Hvis du deaktiverer estimeringstillatelser for rollen {{roleName}} vil alle tidligere anslag gjort av denne rollen bli fjernet", + "COUNT_MEMBERS": "{{ role.members_count }} medlemmer med denne rollen", + "TITLE_DELETE_ROLE": "Slett rolle", + "REPLACEMENT_ROLE": "Alle brukerene med denne rollen vil bli flyttet til", + "WARNING_DELETE_ROLE": "Vær forsiktig! Alle rolle-estimatene vil bli fjernet", + "ERROR_DELETE_ALL": "Du kan ikke slette alle verdier", + "EXTERNAL_USER": "Ekstern bruker" + }, + "THIRD_PARTIES": { + "SECRET_KEY": "Hemmelig nøkkel", + "PAYLOAD_URL": "Nyttelast URL", + "VALID_IPS": "Gyldige opprinnelses IP-adresser (atskilt med,)" + }, + "BITBUCKET": { + "SECTION_NAME": "Bitbucket", + "PAGE_TITLE": "Bitbucket - {{projectName}}", + "INFO_VERIFYING_IP": "Bitbucket forespørsler er ikke signert så den beste måten å verifisere opphavet på er med IP. Hvis feltet er tomt blir det ingen IP validering." + }, + "GITLAB": { + "SECTION_NAME": "Gitlab", + "PAGE_TITLE": "Gitlab - {{projectName}}", + "INFO_VERIFYING_IP": "GitHub-forespørsler er ikke signert så den beste måten å verifisere opprinnelsen på er med IP. Hvis feltet er tomt blir det ingen IP validering." + }, + "GITHUB": { + "SECTION_NAME": "Github", + "PAGE_TITLE": "Github - {{projectName}}" + }, + "GOGS": { + "SECTION_NAME": "Gogs", + "PAGE_TITLE": "Gogs - {{projectName}}" + }, + "WEBHOOKS": { + "PAGE_TITLE": "Webhooks - {{projectName}}", + "SECTION_NAME": "Webkoblinger", + "SUBTITLE": "Webkoblinger varsler eksterne tjenester om hendelser i Taiga, som kommentarer, brukerhistorier ....", + "ADD_NEW": "Legg til en ny webkobling", + "TYPE_NAME": "Skriv inn tjenestenavn", + "TYPE_PAYLOAD_URL": "Skriv inn tjenestens payload url", + "TYPE_SERVICE_SECRET": "Skriv inn tjenesten hemmelige nøkkel", + "SAVE": "Lagre webkobling", + "CANCEL": "Fjern Webkobling", + "SHOW_HISTORY": "(Vis historie)", + "TEST": "Test Webkobling", + "EDIT": "Rediger Webkobling", + "DELETE": "Slett Webkobling", + "REQUEST": "Be om", + "RESEND_REQUEST": "Send forespørsel på nytt", + "HEADERS": "Overskrifter", + "PAYLOAD": "Payload", + "RESPONSE": "Respons", + "DATE": "DD MMM YYYY [at] hh:mm:ss", + "ACTION_HIDE_HISTORY": "(Skjul historikk)", + "ACTION_HIDE_HISTORY_TITLE": "Skjul historikk detaljer", + "ACTION_SHOW_HISTORY": "(Vis historie)", + "ACTION_SHOW_HISTORY_TITLE": "Vis historiedetaljer", + "WEBHOOK_NAME": "Webkobling \"{{name}}\"" + }, + "CUSTOM_ATTRIBUTES": { + "PAGE_TITLE": "{{Seksjon navn}} - Egendefinerte felter - {{prosjekt Name}}", + "ADD": "Legg til et egendefinert felt", + "EDIT": "Endre Egendefinert Felt", + "DELETE": "Slett Egendefinert felt", + "SAVE_TITLE": "Lagre Egendefinert Felt", + "CANCEL_TITLE": "Avbryt opprettelsen", + "SET_FIELD_NAME": "Sett navnet til ditt egendefinerte felt", + "SET_FIELD_DESCRIPTION": "Gi en beskrivelse til ditt egendefinerte felt", + "FIELD_TYPE_DEFAULT": "-- velg en --", + "ACTION_UPDATE": "Oppdater Egendefinert Felt", + "ACTION_CANCEL_EDITION": "Avbryt redigering" + }, + "MEMBERSHIP": { + "COLUMN_MEMBER": "Medlem", + "COLUMN_ADMIN": "Admin", + "COLUMN_ROLE": "Rolle", + "COLUMN_STATUS": "Status", + "STATUS_ACTIVE": "Aktiv", + "STATUS_PENDING": "Avventer", + "DELETE_MEMBER": "Slett medlem", + "RESEND": "Send igjen", + "SUCCESS_SEND_INVITATION": "Vi har sendt invitasjonen på nytt til '{{email}}'.", + "ERROR_SEND_INVITATION": "Vi har ikke sendt invitasjonen.", + "SUCCESS_DELETE": "Vi har slettet {{message}}.", + "ERROR_DELETE": "Vi har ikke vært i stand til å slette {{message}}.", + "DEFAULT_DELETE_MESSAGE": "invitasjonen til {{epost}}" + }, + "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", + "LABEL_POINTS": "Standardverdi for poengvelger", + "LABEL_TASK_STATUS": "Standardverdi for oppgave statusvelger", + "LABEL_ISSUE_TYPE": "Standard verdi for hendelses type-velger", + "LABEL_ISSUE_STATUS": "Standard verdi for hendelse statusvelger", + "LABEL_PRIORITY": "Standardverdi for prioritetsvelger", + "LABEL_SEVERITY": "Standardverdi for alvorlighetsgradsvelgeren" + }, + "STATUS": { + "PLACEHOLDER_WRITE_STATUS_NAME": "Skriv et navn til den nye statusen" + }, + "TYPES": { + "PLACEHOLDER_WRITE_NAME": "Skriv et navn til det nye elementet" + }, + "US_STATUS": { + "ACTION_ADD_STATUS": "Legg til ny status", + "IS_ARCHIVED_COLUMN": "Er arkivert?", + "WIP_LIMIT_COLUMN": "WIP Grense", + "PLACEHOLDER_WRITE_NAME": "Skriv et navn til den nye statusen" + }, + "MENU": { + "TITLE": "Admin", + "PROJECT": "Prosjekt", + "ATTRIBUTES": "Egenskaper", + "MEMBERS": "Medlemmer", + "PERMISSIONS": "Tilganger", + "INTEGRATIONS": "Integrasjoner", + "PLUGINS": "Programtillegg" + }, + "SUBMENU_PROJECT_ATTRIBUTES": { + "TITLE": "Egenskaper" + }, + "SUBMENU_PROJECT_VALUES": { + "STATUS": "Status", + "POINTS": "Poeng", + "PRIORITIES": "Prioriteter", + "SEVERITIES": "Alvorlighetsgrad", + "TYPES": "Typer", + "CUSTOM_FIELDS": "Egendefinerte felter", + "TAGS": "Etiketter" + }, + "SUBMENU_PROJECT_PROFILE": { + "TITLE": "Prosjektprofil" + }, + "SUBMENU_ROLES": { + "TITLE": "Roller", + "ACTION_NEW_ROLE": "+ Ny rolle", + "TITLE_ACTION_NEW_ROLE": "Legg til ny rolle" + }, + "SUBMENU_THIDPARTIES": { + "TITLE": "Tjenester" + }, + "PROJECT_TRANSFER": { + "DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "Har du lyst til å bli den nye prosjekteieren?", + "PRIVATE": "Privat", + "ACCEPTED_PROJECT_OWNERNSHIP": "Gratulerer! Du er nå den nye prosjekteieren.", + "REJECTED_PROJECT_OWNERNSHIP": "OK. Vi kontakter den nåværende prosjekteieren", + "ACCEPT": "Aksepter", + "REJECT": "Avvis", + "PROPOSE_OWNERSHIP": "{{owner}}, den nåværende eiere av prosjekt {{project}} har spurt om du vil bli den nye prosjekteieren.", + "ADD_COMMENT": "Har du lyst til å gi en kommentar til prosjekteieren?", + "UNLIMITED_PROJECTS": "Ubegrenset", + "OWNER_MESSAGE": { + "PRIVATE": "Husk, du kan eie opp til {{maxProjects}} private prosjekter. Du eier for tiden {{currentProjects}} private prosjekter", + "PUBLIC": "Husk, du kan eie opp til {{maxProjects}} offentlige prosjekter. Du eier foreløpig {{currentProjects}} offentlige prosjekter" + }, + "CANT_BE_OWNED": "For øyeblikket kan du ikke bli eier av et prosjekt av denne typen. Dersom du ønsker å bli eier av dette prosjektet, venligst kontakt en administrator så de kan endre dine kontoinstillinger slik at du kan eie et prosjekt.", + "CHANGE_MY_PLAN": "Endre min plan" + } + }, + "USER": { + "PROFILE": { + "PAGE_TITLE": "{{userFullName}} (@{{userUsername}})", + "EDIT": "Endre profil", + "FOLLOW": "Følg", + "CLOSED_US": "Lukket historie", + "PROJECTS": "Prosjekter", + "PROJECTS_EMPTY": "{{username}} har ikke prosjekter enda", + "CONTACTS": "Kontakter", + "CONTACTS_EMPTY": "{{username}} har ingen kontakter enda", + "CURRENT_USER_CONTACTS_EMPTY": "Du har ingen kontakter enda", + "CURRENT_USER_CONTACTS_EMPTY_EXPLAIN": "Personene som du jobber sammen med i Taiga vil automatisk bli dine kontakter", + "REPORT": "Rapporter misbruk", + "TABS": { + "ACTIVITY_TAB": "Tidslinje", + "ACTIVITY_TAB_TITLE": "Vis all aktiviteten til denne brukeren", + "PROJECTS_TAB": "Prosjekter", + "PROJECTS_TAB_TITLE": "Liste over alle prosjekter som brukeren er medlem av", + "LIKES_TAB": "Liker", + "LIKES_TAB_TITLE": "List alle \"liker\" gjort av denne brukeren", + "VOTES_TAB": "Stemmer", + "VOTES_TAB_TITLE": "List alle stemmer gitt av denne brukeren", + "WATCHED_TAB": "Overvåket", + "WATCHED_TAB_TITLE": "List alle element overvåket av denne brukeren", + "CONTACTS_TAB": "Kontakter", + "CONTACTS_TAB_TITLE": "List alle kontakter laget av denne brukeren" + } + }, + "PROFILE_SIDEBAR": { + "TITLE": "Din profil", + "DESCRIPTION": "Alle kan se alt du gjør og hva du jobber med. Legg til en informativ biografi av deg selv.", + "ADD_INFO": "Endre bio" + }, + "PROFILE_FAVS": { + "FILTER_INPUT_PLACEHOLDER": "Skriv noe...", + "FILTER_TYPE_ALL": "Alle", + "FILTER_TYPE_ALL_TITLE": "Vis alle", + "FILTER_TYPE_PROJECTS": "Prosjekter", + "FILTER_TYPE_PROJECT_TITLES": "Vis kun prosjekter", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", + "FILTER_TYPE_USER_STORIES": "Historier", + "FILTER_TYPE_USER_STORIES_TITLES": "Vis kun brukerhistorier", + "FILTER_TYPE_TASKS": "Oppgaver", + "FILTER_TYPE_TASK_TITLES": "Vis kun oppgaver", + "FILTER_TYPE_ISSUES": "Hendelser", + "FILTER_TYPE_ISSUES_TITLE": "Vis kun hendelser", + "EMPTY_TITLE": "Det ser ut som om det ikke er noe å vise her." + } + }, + "PROJECT": { + "PAGE_TITLE": "{{projectName}}", + "WELCOME": "Velkommen", + "SECTION_PROJECTS": "Prosjekter", + "HELP": "Reorganiser prosjektene dine etter de mest brukte.
De topp 10 mest brukte prosjektene vil vises i navigasjonslenken på toppen.", + "PRIVATE": "Privat prosjekt", + "LOOKING_FOR_PEOPLE": "Dette prosjekter søker etter mennesker", + "FANS_COUNTER_TITLE": "{total, plural, one{en fan} other{# fans}}", + "WATCHERS_COUNTER_TITLE": "{total, plural, one{en følger} other{# følgere}}", + "MEMBERS_COUNTER_TITLE": "{total, plural, one{ett medlem} other{# medlemmer}}", + "BLOCKED_PROJECT": { + "BLOCKED": "Blokkert prosjekt", + "THIS_PROJECT_IS_BLOCKED": "Dette prosjektet er midlertidig blokkert", + "TO_UNBLOCK_CONTACT_THE_ADMIN_STAFF": "For å avblokkere dine prosjekter, kontakt administratoren" + }, + "STATS": { + "PROJECT": "prosjekt
poeng", + "DEFINED": "definerte
poeng", + "ASSIGNED": "tildelte
poeng", + "CLOSED": "lukkede
poeng" + }, + "SECTION": { + "SEARCH": "Søk", + "TIMELINE": "Tidslinje", + "BACKLOG": "Backlog", + "KANBAN": "Kanban", + "ISSUES": "Hendelser", + "WIKI": "Wiki", + "TEAM": "Team", + "MEETUP": "Møtes", + "ADMIN": "Admin" + }, + "NAVIGATION": { + "SECTION_TITLE": "Dine prosjekter", + "PLACEHOLDER_SEARCH": "Søk i...", + "ACTION_CREATE_PROJECT": "Opprett prosjekt", + "ACTION_IMPORT_PROJECT": "Importer prosjekt", + "MANAGE_PROJECTS": "Håndter prosjekter", + "TITLE_CREATE_PROJECT": "Opprett prosjekt", + "TITLE_IMPORT_PROJECT": "Importer prosjekt", + "TITLE_PRVIOUS_PROJECT": "Vis forrige prosjekter", + "TITLE_NEXT_PROJECT": "Vis neste prosjekter", + "HELP_TITLE": "Taiga Brukerstøtte", + "HELP": "Hjelp", + "HOMEPAGE": "Hjemmeside", + "FEEDBACK_TITLE": "Gi tilbakemelding", + "FEEDBACK": "Tilbakemelding", + "NOTIFICATIONS_TITLE": "Endre dine varslingsinstillinger", + "NOTIFICATIONS": "Varsler", + "ORGANIZATIONS_TITLE": "Rediger dine organisasjoner", + "ORGANIZATIONS": "Endre organisasjoner", + "SETTINGS_TITLE": "Endre dine instillinger", + "SETTINGS": "Instillinger", + "VIEW_PROFILE_TITLE": "Vis Profil", + "VIEW_PROFILE": "Vis Profil", + "EDIT_PROFILE_TITLE": "Rediger din profil", + "EDIT_PROFILE": "Rediger Profil", + "CHANGE_PASSWORD_TITLE": "Endre passord", + "CHANGE_PASSWORD": "Endre passord", + "DASHBOARD_TITLE": "Dashbord", + "DISCOVER_TITLE": "Oppdag populære prosjekter", + "NEW_ITEM": "Ny", + "DISCOVER": "Oppdag", + "ACTION_REORDER": "Dra & slipp for å omorganisere" + }, + "IMPORT": { + "TITLE": "Importer prosjekt", + "UPLOADING_FILE": "Laster opp dump-fil", + "DESCRIPTION": "Denne prosessen kan ta en stund, vennligst hold vinduet åpent.", + "ASYNC_IN_PROGRESS_TITLE": "Våre Oompa Loompaer importerer ditt prosjekt", + "ASYNC_IN_PROGRESS_MESSAGE": "Denne prosessen kan ta noen minutter
Vi vil sende deg en e-post når den er klar", + "UPLOAD_IN_PROGRESS_MESSAGE": "Opplastet {{uploadedSize}} av {{totalSize}}", + "ERROR": "Våre Oompa Loompaer har problemer med å importere din data-dump. Vennligst prøv på nytt.", + "ERROR_TOO_MANY_REQUEST": "Beklager, våre Oompa Loomper er svært opptatt akkuratt nå. Venligst prøv igjen om noen få minutter.", + "ERROR_MESSAGE": "Våre Oompa Loompaer har problemer med å importere din data-dump: {{error_message}}", + "ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) er for stor for våre Oompa Loompaer. Prøv med en mindre enn ({{maxFileSize}})", + "SYNC_SUCCESS": "Importen av ditt prosjekt var vellykket", + "PROJECT_RESTRICTIONS": { + "PROJECT_MEMBERS_DESC": "Prosjektet du prøver å importere har {{members}} medlemmer. Dessverre, din nåværende plan tillater kun {{max_memberships}} medlemmer per prosjekt. Hvis du ønsker å øke denne grensen, vennligst kontakt administratoren.", + "PRIVATE_PROJECTS_SPACE": { + "TITLE": "Dessverre, din nåværende plan tillater ikke private prosjekter", + "DESC": "Prosjektet du prøver å importere er privat. Dessverre tillater ikke din gjeldende flere private prosjekter." + }, + "PUBLIC_PROJECTS_SPACE": { + "TITLE": "Dessverre din gjeldende plan tillatee ikke flere offentlige prosjekter", + "DESC": "Prosjektet du prøver å importere er offentlig. Dessverre, din nåværende plan tillater ikke offentlige prosjekter." + }, + "PRIVATE_PROJECTS_MEMBERS": { + "TITLE": "Din nåværende plan tillater et maksimalt antall av {{max_memberships}} medlemmer per private prosjekt" + }, + "PUBLIC_PROJECTS_MEMBERS": { + "TITLE": "Din nåværende plan tillater maksimum {{max_memberships}} medlemmer per offentlige prosjekt." + }, + "PRIVATE_PROJECTS_SPACE_MEMBERS": { + "TITLE": "Dessverre, din nåværende plan tillater ikke flere private prosjekter eller mer enn {{max_memberships}} medlemmer per private prosjekt", + "DESC": "Prosjektet som du prøver å importere er privat og har {{members}} medlemmer." + }, + "PUBLIC_PROJECTS_SPACE_MEMBERS": { + "TITLE": "Dessverre, din nåværende plan tillater ikke flere offentlige prosjekter eller en økning til fler enn {{max_memberships}} medlemmer per offentlige prosjekt", + "DESC": "Prosjektet du prøver å importere er offentlig og har fler enn {{members}} medlemmer." + } + } + }, + "LIKE_BUTTON": { + "LIKE": "Liker", + "LIKED": "Likt", + "UNLIKE": "Ikke lik", + "BUTTON_TITLE": "Like eller ikke like dette prosjektet", + "COUNTER_TITLE": "{total, plural, one{en fan} other{# fans}}" + }, + "WATCH_BUTTON": { + "BUTTON_TITLE": "Overvåk dette prosjektet og sett varslingsstrategi", + "WATCH": "Følg", + "WATCHING": "Følger med på", + "COUNTER_TITLE": "{total, plural, one{en følger} other{# følgere}}", + "OPTIONS": { + "NOTIFY_ALL": "Motta alle varsler", + "NOTIFY_ALL_TITLE": "Motta alle varsler for dette prosjektet", + "NOTIFY_INVOLVED": "Kun involvert", + "NOTIFY_INVOLVED_TITLE": "Motta varsler kun når du er involvert", + "UNWATCH": "Ikke overvåk", + "UNWATCH_TITLE": "Ikke overvåk dette prosjektet" + } + } + }, + "LIGHTBOX": { + "DELETE_ACCOUNT": { + "SECTION_NAME": "Slett din Taiga konto", + "CONFIRM": "Er du sikker på at du vil slette din Taiga konto?", + "NEWSLETTER_LABEL_TEXT": "Jeg vil ikke motta flere nyhetsbrev", + "CANCEL": "Tilbake til instillinger", + "ACCEPT": "Slett konto", + "BLOCK_PROJECT": "Merk at alle prosjektene der du står som eier vil bli blokkert etter at du sletter din konto. Dersom du ikke ønsker dette, overfør eierskapet til et annet medlem før du sletter kontoen.", + "SUBTITLE": "Det er synd å se at du forlater oss. Vi er her, skulle du noen sinne vurdere oss igjen! :(" + }, + "DELETE_PROJECT": { + "TITLE": "Slett prosjekt", + "QUESTION": "Er du sikker på at du vil slette dette prosjektet?", + "SUBTITLE": "All prosjekt-data (burkerhistorier, oppgaver, saker, sprinter og wiki-sider) vil gå tapt! :(", + "CONFIRM": "Ja, jeg er virkelig sikker" + }, + "ASSIGNED_TO": { + "SELECT": "Velg ansvarlig", + "SEARCH": "Søk etter brukere" + }, + "ADD_MEMBER": { + "TITLE": "Nytt medlem", + "HELP_TEXT": "Hvis brukere allerede er registrerte på Taiga, vil de bli lagt til automatisk. Ellers vil de motta en invitasjon." + }, + "CREATE_ISSUE": { + "TITLE": "Legg til hendelse" + }, + "FEEDBACK": { + "TITLE": "Fortell oss noe...", + "COMMENT": "...en feil, noen forslag, noe kult... eller til og med ditt værste mareritt med Taiga", + "ACTION_SEND": "Gi tilbakemelding" + }, + "SEARCH": { + "TITLE": "Søk", + "PLACEHOLDER_SEARCH": "Hva ser du etter?" + }, + "ADD_EDIT_SPRINT": { + "TITLE": "Ny sprint", + "PLACEHOLDER_SPRINT_NAME": "sprint navn", + "PLACEHOLDER_SPRINT_START": "Estimert Start", + "PLACEHOLDER_SPRINT_END": "Estimert Slutt", + "ACTION_DELETE_SPRINT": "Vil du slette denne sprinten?", + "TITLE_ACTION_DELETE_SPRINT": "slett sprint", + "LAST_SPRINT_NAME": "siste sprinten er {{lastSprint}} ;-) " + }, + "CREATE_EDIT_TASK": { + "TITLE": "Ny oppgave", + "PLACEHOLDER_SUBJECT": "Subjekt til en oppgave", + "PLACEHOLDER_STATUS": "Oppgavestatus", + "OPTION_UNASSIGNED": "Ikke tildelt", + "PLACEHOLDER_SHORT_DESCRIPTION": "Skriv en kort beskrivelse", + "ACTION_EDIT": "Rediger oppgave" + }, + "CREATE_EDIT_US": { + "TITLE": "Ny BH", + "PLACEHOLDER_DESCRIPTION": "Vennligst legg til en beskrivende tekst for bedre å hjelpe andre til å forstå denne BH", + "NEW_US": "Ny brukerhistorie", + "EDIT_US": "Rediger brukerhistorie" + }, + "DELETE_SPRINT": { + "TITLE": "Slett sprint" + }, + "CREATE_MEMBER": { + "PLACEHOLDER_INVITATION_TEXT": "(Valgfritt) Legg til en egen tekst til invitasjonen. Fortell dine nye medlemmer noe fantastisk ;-)", + "PLACEHOLDER_TYPE_EMAIL": "Skriv en epost", + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." + }, + "LEAVE_PROJECT_WARNING": { + "TITLE": "Dessverre, dette prosjektet kan ikke stå uten en eier", + "CURRENT_USER_OWNER": { + "DESC": "Du er den nåværende eieren av dette prosjektet. Før du forlater, vennligst overfør eierskapet til noen andre.", + "BUTTON": "Endre eieren av prosjektet" + }, + "OTHER_USER_OWNER": { + "DESC": "Dessverre, du kan ikke slette et medlem som også er den nåværende prosjekteieren. Vennligst tildel eierskapet til et annet medlem først.", + "BUTTON": "Be om endring av prosjekteier" + } + }, + "CHANGE_OWNER": { + "TITLE": "Hvem vil du at skal være den nye eieren av prosjektet?", + "ADD_COMMENT": "Legg til kommentar", + "BUTTON": "Spør dette prosjektetmedlemet om å bli den nye prosjekteieren" + } + }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "Vi hadde ikke mulighet til å slette: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "Ny brukerhistorie", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subjekt", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, + "US": { + "PAGE_TITLE": "{{userStorySubject}} - Brukerhistorie {{userStoryRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Ferdigstilt: {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} av {{userStoryTotalTasks}} lukkede hendelser). Poeng: {{userStoryPoints}}. Beskrivelse: {{userStoryDescription}}", + "SECTION_NAME": "Brukerhistorie", + "LINK_TASKBOARD": "Oppgavetavle", + "TITLE_LINK_TASKBOARD": "Gå til oppgavetavlen", + "TOTAL_POINTS": "totale poeng", + "ADD": "+ Legg til en ny brukerhistorie", + "ADD_BULK": "Legg til mange nye Brukerhistorier", + "PROMOTED": "Denne Brukerhistorien har blitt oppgradert fra Hendelse:", + "TITLE_LINK_GO_TO_ISSUE": "Gå til hendelse", + "EXTERNAL_REFERENCE": "Denne BH ble opprettet av", + "GO_TO_EXTERNAL_REFERENCE": "Gå til opphav", + "BLOCKED": "Denne brukerhistorien er blokkert", + "TITLE_DELETE_ACTION": "Slett Brukerhistorie", + "LIGHTBOX_TITLE_BLOKING_US": "Blokkerer oss", + "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} oppgaver ferdigstilt", + "ASSIGN": "Tildel Brukerhistorie", + "NOT_ESTIMATED": "Ikke estimert", + "TOTAL_US_POINTS": "Total BH poeng", + "TRIBE": { + "PUBLISH": "Publish as Gig in Taiga Tribe", + "PUBLISH_INFO": "Mer info", + "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "EDIT_LINK": "Edit link", + "CLOSE": "Close", + "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" + }, + "FIELDS": { + "TEAM_REQUIREMENT": "Team behov", + "CLIENT_REQUIREMENT": "Klientkrav", + "FINISH_DATE": "Sluttdato" + } + }, + "COMMENTS": { + "DELETED_INFO": "Comment deleted by {{user}}", + "TITLE": "Kommentarer", + "COMMENTS_COUNT": "{{comments}} Comments", + "ORDER": "Order", + "OLDER_FIRST": "Older first", + "RECENT_FIRST": "Recent first", + "COMMENT": "Kommentar", + "EDIT_COMMENT": "Edit comment", + "EDITED_COMMENT": "Edited:", + "SHOW_HISTORY": "View historic", + "TYPE_NEW_COMMENT": "Skriv en ny kommentar her", + "SHOW_DELETED": "Vis slettede kommentarer", + "HIDE_DELETED": "Skjul slettede kommentarer", + "DELETE": "Slett kommentar", + "RESTORE": "Gjennoprett kommentarer", + "HISTORY": { + "TITLE": "Aktivitet" + } + }, + "ACTIVITY": { + "SHOW_ACTIVITY": "Vis aktivitet", + "DATETIME": "DD MMM YYYY HH:mm", + "SHOW_MORE": "+ Vis tidligere innlegg ({{showMore}} mer)", + "TITLE": "Aktivitet", + "ACTIVITIES_COUNT": "{{activities}} Activities", + "REMOVED": "fjernet", + "ADDED": "lagt til", + "TAGS_ADDED": "tags added:", + "TAGS_REMOVED": "tags removed:", + "US_POINTS": "{{role}} points", + "NEW_ATTACHMENT": "new attachment:", + "DELETED_ATTACHMENT": "deleted attachment:", + "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", + "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", + "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", + "SIZE_CHANGE": "Gjorde {size, plural, one{en endring} other{# endringer}}", + "BECAME_DEPRECATED": "became deprecated", + "BECAME_UNDEPRECATED": "became undeprecated", + "TEAM_REQUIREMENT": "Team behov", + "CLIENT_REQUIREMENT": "Klientkrav", + "BLOCKED": "Blokkert", + "VALUES": { + "YES": "ja", + "NO": "nei", + "EMPTY": "tomt", + "UNASSIGNED": "ikke tildelt" + }, + "FIELDS": { + "SUBJECT": "subjekt", + "NAME": "navn", + "DESCRIPTION": "beskrivelse", + "CONTENT": "innhold", + "STATUS": "status", + "IS_CLOSED": "er lukket", + "FINISH_DATE": "Sluttdato", + "TYPE": "type", + "PRIORITY": "prioritet", + "SEVERITY": "Alvorlighetsgrad", + "ASSIGNED_TO": "tildelt til", + "WATCHERS": "følgere", + "MILESTONE": "sprint", + "USER_STORY": "brukerhistorie", + "PROJECT": "prosjekt", + "IS_BLOCKED": "er blokkert", + "BLOCKED_NOTE": "blokkert notat", + "POINTS": "poeng", + "CLIENT_REQUIREMENT": "klientkrav", + "TEAM_REQUIREMENT": "team behov", + "IS_IOCAINE": "Er Iocaine", + "TAGS": "etiketter", + "ATTACHMENTS": "vedlegg", + "IS_DEPRECATED": "Er foreldet", + "IS_NOT_DEPRECATED": "is not deprecated", + "ORDER": "rekkefølge", + "BACKLOG_ORDER": "backlog rekkefølge", + "SPRINT_ORDER": "sprint rekkefølge", + "KANBAN_ORDER": "kanban rekkefølge", + "TASKBOARD_ORDER": "Oppgavetavle rekkefølge", + "US_ORDER": "BH rekkefølge", + "COLOR": "farge" + } + }, + "BACKLOG": { + "PAGE_TITLE": "Backlog - {{projectName}}", + "PAGE_DESCRIPTION": "Backlogpanelet, med brukerhistorier og sprinter for prosjektet {{projectName}}: {{projectDescription}}", + "SECTION_NAME": "Backlog", + "CUSTOMIZE_GRAPH": "Tilpass din backlog-graf", + "CUSTOMIZE_GRAPH_TEXT": "For å ha en flott graf som hjelper deg å følge utviklingen av prosjektet må du sette opp poeng og sprinter gjennom", + "CUSTOMIZE_GRAPH_ADMIN": "Admin", + "CUSTOMIZE_GRAPH_TITLE": "Sett opp poengene og sprintene gjennom Admin-panelet", + "MOVE_US_TO_CURRENT_SPRINT": "Gå til nåværende sprint", + "MOVE_US_TO_LATEST_SPRINT": "Flytt til siste Sprint", + "SHOW_FILTERS": "Vis filtre", + "SHOW_TAGS": "Vis etiketter", + "EMPTY": "Backlogen er tom!", + "CREATE_NEW_US": "Opprett en ny BH", + "CREATE_NEW_US_EMPTY_HELP": "Det kan være lurt å opprette en ny brukerhistorie", + "EXCESS_OF_POINTS": "Overskudd av poeng", + "PENDING_POINTS": "Ventende poeng", + "CLOSED_POINTS": "lukket", + "COMPACT_SPRINT": "Kompakt Sprint", + "GO_TO_TASKBOARD": "Gå til oppgavepanelet for {{::name}}", + "EDIT_SPRINT": "Rediger Sprint", + "TOTAL_POINTS": "total", + "STATUS_NAME": "Status Navn", + "SORTABLE_FILTER_ERROR": "Du kan ikke slippe på backlog når filtere er åpen", + "DOOMLINE": "Prosjektomfang [Dommedagslinje]", + "CHART": { + "XAXIS_LABEL": "Sprinter", + "YAXIS_LABEL": "Poeng", + "OPTIMAL": "Optimal mengde avventede poeng for sprint \"{{sprintName}}\" skal være {{value}}", + "REAL": "Ekte avventende poeng for sprint \"{{sprintName}}\" er {{value}}", + "INCREMENT_TEAM": "Inkrementelle poeng ved gruppekrav for sprint \"{{sprintName}}\" er {{value}}", + "INCREMENT_CLIENT": "Inkrementelle poeng med klientkrav for sprint \"{{sprintNam}}\" er {{verdi}}" + }, + "TAGS": { + "TOGGLE": "Endre synligheten til etiketter", + "SHOW": "Vis etiketter", + "HIDE": "Skjul etiketter" + }, + "TABLE": { + "COLUMN_US": "Brukerhistorie", + "TITLE_COLUMN_POINTS": "Velg visning per Rolle" + }, + "SPRINT_SUMMARY": { + "TOTAL_POINTS": "total
poeng", + "COMPLETED_POINTS": "fullførte
poeng", + "OPEN_TASKS": "åpne
oppgaver", + "CLOSED_TASKS": "lukkede
oppgaver", + "IOCAINE_DOSES": "iocaine
doser", + "SHOW_STATISTICS_TITLE": "Vis statistikker", + "TOGGLE_BAKLOG_GRAPH": "Vis/Skjul Burndown Graf", + "POINTS_PER_ROLE": "Points per role" + }, + "SUMMARY": { + "PROJECT_POINTS": "prosjekt
poeng", + "DEFINED_POINTS": "definerte
poeng", + "CLOSED_POINTS": "lukkede
poeng", + "POINTS_PER_SPRINT": "poeng /
sprint" + }, + "FILTERS": { + "TOGGLE": "Veksle filteres synlighet", + "TITLE": "Filter", + "REMOVE": "Fjern Filtere", + "HIDE": "Skjul Filtere", + "SHOW": "Vis Filtere" + }, + "SPRINTS": { + "TITLE": "SPRINTER", + "DATE": "DD MMM YYYY", + "LINK_TASKBOARD": "Sprint Oppgavepanel", + "TITLE_LINK_TASKBOARD": "Gå til Oppgavepanel for \"{{name}}\"", + "NUMBER_SPRINTS": "
sprinter", + "EMPTY": "Det er ingen sprinter enda", + "WARNING_EMPTY_SPRINT_ANONYMOUS": "Denne sprinten har ingen Brukerhistorier", + "WARNING_EMPTY_SPRINT": "Slipp Brukerhistorier her fra din backlog for å starte en ny sprint", + "TITLE_ACTION_NEW_SPRINT": "Legg til ny sprint", + "TEXT_ACTION_NEW_SPRINT": "Det kan være lurt å lage en ny sprint i prosjektet ditt", + "ACTION_SHOW_CLOSED_SPRINTS": "Vis lukkede sprinter", + "ACTION_HIDE_CLOSED_SPRINTS": "Skjul lukkede sprinter" + } + }, + "ERROR": { + "TEXT1": "Noe skjedde og våre Oompa Loompaer jobber med det.", + "NOT_FOUND": "Ikke funnet", + "NOT_FOUND_TEXT": "Error 404. Siden du ser etter eksisterer ikke lenger. Kanskje du kan returnere til TAIGA-hjemmesiden og se om du kan finne det du leter etter.", + "PERMISSION_DENIED": "Tilgang nektet", + "PERMISSION_DENIED_TEXT": "Du har ikke tilgang til å aksessere denne siden", + "VERSION_ERROR": "Noen andre som bruker Taiga har endret dette før, og våre Oompa Loompaer kan ikke lagre dine endringer. Vennligst last om og lagre dine endringer igjen (de vil gå tapt)." + }, + "TASKBOARD": { + "PAGE_TITLE": "{{sprintName}} - Sprint oppgavepanel - {{projectName}}", + "PAGE_DESCRIPTION": "Sprint {{sprintName}} (from {{startDate}} til {{endDate}}) med {{projectName}}. Fullført {{completedPercentage}}% ({{completedPoints}} av {{totalPoints}} poeng). {{openTasks}} åpne oppgaver av {{totalTasks}}.", + "SECTION_NAME": "Oppgavetavle", + "TITLE_ACTION_ADD": "Legg til en ny oppgave", + "TITLE_ACTION_ADD_BULK": "Legg til noen nye Oppgaver samlet", + "TITLE_ACTION_ASSIGN": "Tildel oppgave", + "TITLE_ACTION_EDIT": "Rediger oppgave", + "PLACEHOLDER_CARD_TITLE": "Dette kunne vært en oppgave", + "PLACEHOLDER_CARD_TEXT": "Splitt historier inn i oppgaver og følg dem separat", + "TABLE": { + "COLUMN": "Brukerhistorie", + "TITLE_ACTION_FOLD": "Slå sammen kolonne", + "TITLE_ACTION_UNFOLD": "Brett ut kolonne", + "TITLE_ACTION_FOLD_ROW": "Brett Rad", + "TITLE_ACTION_UNFOLD_ROW": "Brett ut Rad", + "FIELD_POINTS": "poeng", + "ROW_UNASSIGED_TASKS_TITLE": "\nIkke tildelte oppgaver" + }, + "CHARTS": { + "XAXIS_LABEL": "Dager", + "YAXIS_LABEL": "Poeng", + "OPTIMAL": "Optimal mengde ventende poeng for dagen {{formattedDate}} bør være {{roundedValue}}", + "REAL": "Reelle ventende poeng for dagen {{formattedDate}} er {{roundedValue}}", + "DATE": "DD MMMM YYYY" + } + }, + "TASK": { + "PAGE_TITLE": "{{taskSubject}} - Oppgave {{taskRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{taskStatus }}. Beskrivelse: {{taskDescription}}", + "SECTION_NAME": "Oppgave", + "LINK_TASKBOARD": "Oppgavetavle", + "TITLE_LINK_TASKBOARD": "Gå til oppgavetavlen", + "PLACEHOLDER_SUBJECT": "Skriv inn subjektet til den nye oppgaven", + "TITLE_SELECT_STATUS": "Status Navn", + "OWNER_US": "Denne oppgaven tilhører", + "TITLE_LINK_GO_OWNER": "Gå til brukerhistorie", + "ORIGIN_US": "Denne oppgaven har blitt opprettet fra", + "TITLE_LINK_GO_ORIGIN": "Gå til brukerhistorie", + "BLOCKED": "Denne oppgaven er blokkert", + "TITLE_DELETE_ACTION": "Slett oppgave", + "LIGHTBOX_TITLE_BLOKING_TASK": "Blokkerende oppgave", + "FIELDS": { + "MILESTONE": "Sprint", + "USER_STORY": "Brukerhistorie", + "IS_IOCAINE": "Er Iocaine" + }, + "ACTION_IOCAINE": "Iocaine", + "TITLE_ACTION_IOCAINE": "Føler du deg litt overveldet av en oppgave? Sørg for at andre vet om det ved å klikke på \"Iocane\" når du redigerer en oppgave. Det er mulig å bli immun mot denne (fiktive) dødelige giften ved å konsumere små mengder over tid, akkurat som det er mulig å bli bedre på det du gjør ved av og til å ta på deg ekstra utfordringer!" + }, + "NOTIFICATION": { + "OK": "Alt er ok", + "WARNING": "Opps, noe skjedde...", + "WARNING_TEXT": "Våre Oompa Loompaer er lei seg :-( , dine endringer ble ikke lagret!", + "SAVED": "Våre Oompa Loompaer har lagret alle endringer!", + "CLOSE": "Lukk melding", + "MAIL": "Varsler per Epost", + "ASK_DELETE": "Er du sikker på at du vil slette?" + }, + "CANCEL_ACCOUNT": { + "TITLE": "Kanseler din konto", + "SUBTITLE": "Vi er lei oss for at du forlater Taiga, vi håper du nøt oppholdet :)", + "PLACEHOLDER_INPUT_TOKEN": "kanseler konto token", + "ACTION_LEAVING": "Ja, jeg går!", + "SUCCESS": "Våre Oompa Loompaer har slettet din konto" + }, + "CHANGE_EMAIL_FORM": { + "TITLE": "Bytt din epost", + "SUBTITLE": "Ett klikk til og din epost vil være oppdatert!", + "PLACEHOLDER_INPUT_TOKEN": "skift epost token", + "ACTION_CHANGE_EMAIL": "Endre epost", + "SUCCESS": "Våre Oompa Loompaer oppdaterte din epost" + }, + "ISSUES": { + "PAGE_TITLE": "Hendelser - {{projectName}}", + "PAGE_DESCRIPTION": "Hendelsespanelet for prosjekt {{projectName}}: {{projectDescription}}", + "LIST_SECTION_NAME": "Hendelser", + "SECTION_NAME": "Hendelse", + "ACTION_NEW_ISSUE": "+ NY HENDELSE", + "ACTION_PROMOTE_TO_US": "Oppgrader til Brukerhistorie", + "PROMOTED": "Denne hendelsen har blitt oppgradert til BH", + "EXTERNAL_REFERENCE": "Denne hendelsen har blitt opprettet av", + "GO_TO_EXTERNAL_REFERENCE": "Gå til opphav", + "BLOCKED": "Denne hendelsen er blokkert", + "ACTION_DELETE": "Slett hendelse", + "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blokker hendelse", + "FIELDS": { + "PRIORITY": "Prioritet", + "SEVERITY": "Alvorlighetsgrad", + "TYPE": "Type" + }, + "CONFIRM_PROMOTE": { + "TITLE": "Oppgrader denne henelsen til en ny brukerhistorie", + "MESSAGE": "Er du sikker på at du vil lage en ny BH fra denne hendelsen?" + }, + "TABLE": { + "COLUMNS": { + "TYPE": "Type", + "SEVERITY": "Alvorlighetsgrad", + "PRIORITY": "Prioritet", + "SUBJECT": "Subjekt", + "VOTES": "Stemmer", + "STATUS": "Status", + "CREATED": "Opprettet", + "ASSIGNED_TO": "Tildelt til" + }, + "TITLE_ACTION_CHANGE_STATUS": "Endre status", + "TITLE_ACTION_ASSIGNED_TO": "Tildelt til", + "BLOCKED": "Blokkert", + "EMPTY": { + "TITLE": "Det er ingen hendelser å jobbe med :-)", + "SUBTITLE": "Oppdaget du en hendelse?" + } + } + }, + "ISSUE": { + "PAGE_TITLE": "{{issueSubject}} - Hendelse {{issueRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{issueStatus }}. Type: {{issueType}}, Prioritet: {{issuePriority}}. Alvorlighetsgrad: {{issueSeverity}}. Beskrivelse: {{issueDescription}}" + }, + "KANBAN": { + "PAGE_TITLE": "Kanban - {{projectName}}", + "PAGE_DESCRIPTION": "Kanbanpanelet, med brukerhistorier til prosjektet {{projectName}}: {{projectDescription}}", + "SECTION_NAME": "Kanban", + "TITLE_ACTION_FOLD": "Slå sammen kolonne", + "TITLE_ACTION_UNFOLD": "Brett ut kolonne", + "TITLE_ACTION_FOLD_CARDS": "Brett kort", + "TITLE_ACTION_UNFOLD_CARDS": "Brett ut kort", + "TITLE_ACTION_ADD_US": "Legg til ny Brukerhistorie", + "TITLE_ACTION_ADD_BULK": "Legg til ny samling", + "ACTION_SHOW_ARCHIVED": "Vis arkiverte", + "ACTION_HIDE_ARCHIVED": "Skjul arkivert", + "HIDDEN_USER_STORIES": "Brukerhistoriene med denne statusen er skjult som standard", + "ARCHIVED": "Du har arkivert", + "UNDO_ARCHIVED": "Dra & slipp igjen for å angre", + "PLACEHOLDER_CARD_TITLE": "Dette er dine Brukerhistorier", + "PLACEHOLDER_CARD_TEXT": "Historier kan også ha underoppgaver med egne krav" + }, + "SEARCH": { + "PAGE_TITLE": "Søk - {{projectName}}", + "PAGE_DESCRIPTION": "Søk etter hva som helst, brukerhistorier, hendelser, oppgaver or wiki-sider i prosjektet {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", + "FILTER_USER_STORIES": "Brukerhistorie", + "FILTER_ISSUES": "Hendelser", + "FILTER_TASKS": "Oppgaver", + "FILTER_WIKI": "Wiki-sider", + "PLACEHOLDER_SEARCH": "Søk i...", + "TITLE_ACTION_SEARCH": "søk", + "EMPTY_TITLE": "Det ser ut som ingenting ble funnet med søkekriteriene dine", + "EMPTY_DESCRIPTION": "Kanskje prøve en av fanene ovenfor eller søk på nytt" + }, + "TEAM": { + "PAGE_TITLE": "Team - {{projectName}}", + "PAGE_DESCRIPTION": "Teampanelet for å vise alle medlemmene i prosjektet {{projectName}}: {{projectDescription}}", + "SECTION_NAME": "Team", + "APP_TITLE": "TEAM - {{projectName}}", + "PLACEHOLDER_INPUT_SEARCH": "Søk på fult navn---", + "COLUMN_MR_WOLF": "Mr. Wolf", + "EXPLANATION_COLUMN_MR_WOLF": "Lukkede hendelser", + "COLUMN_IOCAINE": "Iocaine Drikker", + "EXPLANATION_COLUMN_IOCAINE": "Iocainedoser inntatt", + "COLUMN_CERVANTES": "Cervantes", + "EXPLANATION_COLUMN_CERVANTES": "Wiki-sider redigert", + "COLUMN_BUG_HUNTER": "Bug Jeger", + "EXPLANATION_COLUMN_BUG_HUNTER": "Hendelser som er medlt inn", + "COLUMN_NIGHT_SHIFT": "Nattevakt", + "EXPLANATION_COLUMN_NIGHT_SHIFT": "Oppgaver lukket", + "COLUMN_TOTAL_POWER": "Total Styrke", + "EXPLANATION_COLUMN_TOTAL_POWER": "Totalt Antall Poeng", + "SECTION_TITLE_TEAM": "Team >", + "SECTION_FILTER_ALL": "Alle", + "CONFIRM_LEAVE_PROJECT": "Er du sikker på at du vil forlate prosjektet?", + "ACTION_LEAVE_PROJECT": "Forlat dette prosjektet" + }, + "USER_SETTINGS": { + "AVATAR_MAX_SIZE": "[Max. størrelse: {{maxFileSize}}]", + "MENU": { + "SECTION_TITLE": "Brukerinstillinger", + "USER_PROFILE": "Brukerprofil", + "CHANGE_PASSWORD": "Endre passord", + "EMAIL_NOTIFICATIONS": "Epost-varsler" + }, + "NOTIFICATIONS": { + "SECTION_NAME": "E-postvarsler", + "COLUMN_PROJECT": "Prosjekt", + "COLUMN_RECEIVE_ALL": "Motta alle", + "COLUMN_ONLY_INVOLVED": "Kun involvert", + "COLUMN_NO_NOTIFICATIONS": "Ingen varsler", + "OPTION_ALL": "Alle", + "OPTION_INVOLVED": "Involvert", + "OPTION_NONE": "Ingen" + }, + "POPOVER": { + "USER_PROFILE": "Brukerprofil", + "CHANGE_PASSWORD": "Endre passord", + "NOTIFICATIONS": "Varsler", + "FEEDBACK": "Tilbakemelding", + "TITLE_AVATAR": "Brukerpreferanser" + } + }, + "USER_PROFILE": { + "IMAGE_HELP": "Bildet vil bli skalert til 80x80px.", + "ACTION_CHANGE_IMAGE": "Endre", + "ACTION_USE_GRAVATAR": "Bruk standardbilde", + "ACTION_DELETE_ACCOUNT": "Slett Taiga-konto", + "CHANGE_EMAIL_SUCCESS": "Sjekk din innboks!
Vi har sent en epost til din konto
med instruksjonene for å velge ny epostadresse.", + "CHANGE_PHOTO": "Endre bilde", + "FIELD": { + "USERNAME": "Brukernavn", + "EMAIL": "Epost", + "FULL_NAME": "Fullt navn", + "PLACEHOLDER_FULL_NAME": "Skriv ditt fulle navn (f.eks: Ola Nordmann)", + "BIO": "Bio (max. 210 tegn)", + "PLACEHOLDER_BIO": "Fortell oss noe om deg selv", + "LANGUAGE": "Språk", + "LANGUAGE_DEFAULT": "-- bruk standard språk --", + "THEME": "Tema", + "THEME_DEFAULT": "-- bruk standard tema --" + } + }, + "WIZARD": { + "SECTION_TITLE_CREATE_PROJECT": "Opprett prosjekt", + "CREATE_PROJECT_TEXT": "Friskt og stilrent. Så spennende!", + "CHOOSE_TEMPLATE": "Hvilken mal passer ditt prosjekt best?", + "CHOOSE_TEMPLATE_TITLE": "Mer info om prosjektmaler", + "CHOOSE_TEMPLATE_INFO": "Mer info", + "PROJECT_DETAILS": "Prosjektdetaljer", + "PUBLIC_PROJECT": "Offentlig Prosjekt", + "PRIVATE_PROJECT": "Privat Prosjekt", + "CREATE_PROJECT": "Opprett prosjekt", + "MAX_PRIVATE_PROJECTS": "Du har nådd maksimalt antall private prosjekter", + "MAX_PUBLIC_PROJECTS": "Dessverre, du har nådd maksimalt antall offentlige prosjekter", + "CHANGE_PLANS": "endre planer" + }, + "WIKI": { + "PAGE_TITLE": "{{wikiPageName}} - Wiki - {{projectName}}", + "PAGE_DESCRIPTION": "Siste versjon: {{lastModifiedDate}} ({{totalEditions}} versjoner totalt) Innhold: {{ wikiPageContent }}", + "DATETIME": "DD MMM YYYY HH:mm", + "PLACEHOLDER_PAGE": "Skriv din wiki-side", + "REMOVE": "Fjern denne wiki-siden", + "DELETE_LIGHTBOX_TITLE": "Slett wiki-siden", + "DELETE_LINK_TITLE": "Slett wiki-lenke", + "NAVIGATION": { + "HOME": "Main Page", + "SECTION_NAME": "BOOKMARKS", + "ACTION_ADD_LINK": "Add bookmark", + "ALL_PAGES": "All wiki pages" + }, + "SUMMARY": { + "TIMES_EDITED": "ganger
redigert", + "LAST_EDIT": "siste
endring", + "LAST_MODIFICATION": "siste endring" + }, + "SECTION_PAGES_LIST": "All pages", + "PAGES_LIST_COLUMNS": { + "TITLE": "Title", + "EDITIONS": "Editions", + "CREATED": "Opprettet", + "MODIFIED": "Modified", + "CREATOR": "Creator", + "LAST_MODIFIER": "Last modifier" + } + }, + "HINTS": { + "SECTION_NAME": "Tips", + "LINK": "Hvis du vil vite hvordan du bruker den, besøk vår brukerstøtte", + "LINK_TITLE": "Besøk vår brukerstøtte", + "HINT1_TITLE": "Viste du at du kan importere og eksportere prosjekter?", + "HINT1_TEXT": "Dette gir deg muligheten til å ta ut alle dine data fra en Taiga og flytte den til en annen.", + "HINT2_TITLE": "Viste du at du kan opprette egendefinerte felter?", + "HINT2_TEXT": "Team kan nå lage egendefinerte felter som en fleksibel måte å legge til spesifik data nyttig for deres egne arbeidsflyt.", + "HINT3_TITLE": "Endre rekkefølge på dine prosjekter for å fremvise de som er mest relevante for deg.", + "HINT3_TEXT": "De 10 prosjektene er listet opp i navigasjonslinjen på toppen.", + "HINT4_TITLE": "Glemte du det du arbeidet med?", + "HINT4_TEXT": "Ikke fortvil, på ditt dashboard finner du åpne oppgaver, hendelser og brukerhistorier i den rekkefølgen du jobbet med de." + }, + "TIMELINE": { + "UPLOAD_ATTACHMENT": "{{username}} har lastet opp ett nytt vedlegg i {{obj_name}}", + "US_CREATED": "{{username}} har opprettet en ny BH {{obj_name}} i {{project_name}}", + "ISSUE_CREATED": "{{username}} har opprettet en ny hendelse {{obj_name}} i {{project_name}}", + "TASK_CREATED": "{{username}} har opprettet en ny oppgave {{obj_name}} i {{project_name}}", + "TASK_CREATED_WITH_US": "{{username}} har opprettet en ny oppgave {{obj_name}} i {{project_name}} som tilhører BH {{us_name}}", + "WIKI_CREATED": "{{username}} har opprettet en ny wiki-side {{obj_name}} i {{project_name}}", + "MILESTONE_CREATED": "{{username}} har opprettet en ny sprint {{obj_name}} i {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", + "NEW_PROJECT": "{{username}} opprettet prosjektet {{project_name}}", + "MILESTONE_UPDATED": "{{username}} har oppdatert sprint {{obj_name}}", + "US_UPDATED": "{{username}} har oppdatert egenskapen \"{{field_name}}\" til BH {{obj_name}}", + "US_UPDATED_WITH_NEW_VALUE": "{{username}} har oppdatert egenskapen \"{{field_name}}\" til BH {{obj_name}} til {{new_value}}", + "US_UPDATED_POINTS": "{{username}} har oppdatert '{{role_name}}' poeng for BH {{obj_name}} til {{new_value}}", + "ISSUE_UPDATED": "{{username}} har oppdatert egenskapen \"{{field_name}}\" til hendelsen {{obj_name}}", + "ISSUE_UPDATED_WITH_NEW_VALUE": "{{username}} har oppdatert egenskapen \"{{field_name}}\" til hendelsen {{obj_name}} til {{new_value}}", + "TASK_UPDATED": "{{username}} har oppdatert egenskapen \"{{field_name}}\" til oppgave {{obj_name}} til {{new_value}}", + "TASK_UPDATED_WITH_NEW_VALUE": "{{username}} har oppdatert egenskapen \"{{field_name}}\" til oppgave {{obj_name}} til {{new_value}}", + "TASK_UPDATED_WITH_US": "{{username}} har oppdatert egenskapen \"{{field_name}}\" til oppgave {{obj_name}} som tilhører BH {{us_name}}", + "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} har oppdatert egenskapen \"{{field_name}}\" til oppgaven {{obj_name}} som tilhører BH {{us_name}} til {{new_value}}", + "WIKI_UPDATED": "{{username}} har oppdatert wiki-siden {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", + "NEW_COMMENT_US": "{{username}} har kommentert på BH {{obj_name}}", + "NEW_COMMENT_ISSUE": "{{username}} har kommentert på hendelsen {{obj_name}}", + "NEW_COMMENT_TASK": "{{username}} har kommentert på oppgave {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", + "NEW_MEMBER": "{{project_name}} har et nytt medlem", + "US_ADDED_MILESTONE": "{{username}} har lagt til BH {{obj_name}} til {{sprint_name}}", + "US_MOVED": "{{username}} har flyttet BH {{obj_name}}", + "US_REMOVED_FROM_MILESTONE": "{{username}} har lagt til BH {{obj_name}} til backlogen", + "BLOCKED": "{{username}} har blokkert {{obj_name}}", + "UNBLOCKED": "{{username}} har avblokkert {{obj_name}}", + "NEW_USER": "{{username}} har sluttet seg til Taiga" + }, + "LEGAL": { + "TERMS_OF_SERVICE_AND_PRIVACY_POLICY_AD": "Når du lager en ny konto godtar du våre
brukervilkår og personvernregler." + }, + "EXTERNAL_APP": { + "PAGE_TITLE": "En ekstern app krever autentisering", + "PAGE_DESCRIPTION": "En ekstern app krever autentisering", + "AUTHORIZATION_REQUEST": "Tillat {{application}} til å bruke din Taiga konto?", + "LOGIN_WITH_ANOTHER_USER": "Logg inn med en annen bruker", + "AUTHORIZE_APP": "Godkjenn app", + "CANCEL": "Avbryt" + }, + "JOYRIDE": { + "NAV": { + "NEXT": "Neste", + "BACK": "Tilbake", + "SKIP": "Hopp over", + "DONE": "Ferdig" + }, + "DASHBOARD": { + "STEP1": { + "TITLE": "Ditt prosjekt", + "TEXT": "Velkommen! Her finner du prosjektene du er involvert i." + }, + "STEP2": { + "TITLE": "Arbeider med", + "TEXT": "Her vil du finne Brukerhistorier, Oppgaver og Hendelser som du jobber med." + }, + "STEP3": { + "TITLE": "Følger med på", + "TEXT1": "Og akkuratt her vil du finne de i prosjektet ditt som du vil vite om.", + "TEXT2": "Du arbeider allerede med Taiga ;)" + }, + "STEP4": { + "TITLE": "La oss begynne", + "TEXT1": "Du kan starte med å opprette ditt første Taiga prosjekt", + "TEXT2": "Lykke til!" + } + }, + "BACKLOG": { + "STEP1": { + "TITLE": "Prosjektsammendrag", + "TEXT1": "Her vil du se tilstanden til ditt prosjekt.", + "TEXT2": "Du kan endre alle prosjektegenskaper gjennom administrasjonpanelet" + }, + "STEP2": { + "TITLE": "Produkt backlog", + "TEXT": "Backlogen er listen over kravene (Brukerhistorier) til prosjektet. Her kan du planlegge dine sprinter." + }, + "STEP3": { + "TITLE": "Sprinter", + "TEXT": "Sprinter er korte tidsperioder (vanligvis 2 uker) der spesifikt arbeide må ferdigstilles og leveres." + }, + "STEP4": { + "TITLE": "Brukerhistorie", + "TEXT": "Dette er kravene på et overordnet nivå. Du kan legge dem til backlogen og dra dem inn i den sprinten der det skal leveres." + } + }, + "KANBAN": { + "STEP1": { + "TITLE": "Tilpass din arbeidsflyt", + "TEXT": "Sett opp kolonnene du trenger for å kartlegge statusene til din arbeidsflyt gjennom administrasjonspanelet." + }, + "STEP2": { + "TITLE": "Brukerhistorier & Oppgaver", + "TEXT": "Brukerhistorier er krav på et overordnet nivå. Du kan dra de inn i ulike kolonner." + }, + "STEP3": { + "TITLE": "Legger til Brukerhistorier", + "TEXT1": "Det kan være lurt å legge til en Brukerhistorie (legg til BH ikon) eller en samling av dem (bulk ikon)", + "TEXT2": "Lykke til!" + } + } + }, + "DISCOVER": { + "PAGE_TITLE": "Oppdag prosjekter -Taiga", + "PAGE_DESCRIPTION": "Søkbar katalog med offentlige prosjekter i Taiga. Utforsk backlogger, tidslinjer, hendelser og team. Sjekk ut de mest likte og mest aktive prosjektene. Filtrer etter Kanban og Scrum.", + "DISCOVER_TITLE": "Oppdag prosjekter", + "DISCOVER_SUBTITLE": "{projects, plural, one{Ett offentlig prosjekt å oppdage} other{# offentlige prosjekt å oppdage}}", + "MOST_ACTIVE": "Mest aktiv", + "MOST_ACTIVE_EMPTY": "Det er ingen AKTIVE prosjekter enda", + "MOST_LIKED": "Mest likt", + "MOST_LIKED_EMPTY": "Det er ingen LIKTE prosjekter enda", + "VIEW_MORE": "Vis mer", + "RECRUITING": "Dette prosjekter søker etter mennesker", + "FEATURED": "Utvalgte Prosjekter", + "EMPTY": "Det er ingen prosjekter å vise med dette søkekriteriet.
Prøv igjen!", + "FILTERS": { + "ALL": "Alle", + "KANBAN": "Kanban", + "SCRUM": "Scrum", + "PEOPLE": "Søker etter folk", + "WEEK": "Forrige uke", + "MONTH": "Forrige måned", + "YEAR": "Forrige år", + "ALL_TIME": "All tid", + "CLEAR": "Fjern filtrene" + }, + "SEARCH": { + "PAGE_TITLE": "Søk - Oppdag prosjekter - Taiga", + "PAGE_DESCRIPTION": "Søkbar katalog med offentlige prosjekter i Taiga. Utforsk backlogger, tidslinjer, hendelser og team. Sjekk ut de mest likte og mest aktive prosjektene. Filtrer etter Kanban og Scrum.", + "INPUT_PLACEHOLDER": "Skriv noe...", + "ACTION_TITLE": "Søk", + "RESULTS": "Søkeresultater" + } + } +} \ No newline at end of file From eea5ea4fe9458ebd16f0c7c08587c0def15fb454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 22 Sep 2016 10:54:56 +0200 Subject: [PATCH 141/315] Admin tags page --- .../includes/modules/admin/project-tags.jade | 21 +++++++++++++----- app/styles/layout/admin-project-tags.scss | 22 +++++++++++++------ app/styles/modules/common/colors-table.scss | 2 +- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index 2e5ca58e..2692089e 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -54,25 +54,28 @@ section .status-name(translate="COMMON.FIELDS.NAME") .color-filter input.e2e-tags-filter( + id="filter-tags-input" type="text" name="name" ng-model="tagsFilter.name" ng-model-options="{debounce: 200}" ) - tg-svg( - svg-icon="icon-search" - ) + label(for="filter-tags-input") + tg-svg(svg-icon="icon-search") .table-main.table-admin-tags( ng-if="projectTagsAll.length" ) div(ng-show="!mixingTags.toTag") - .admin-attributes-section-wrapper-empty( + .empty-large.admin-attributes-section-wrapper-empty( ng-show="!projectTags.length" tg-loading="ctrl.loading" ) + img( + src="/#{v}/images/empty/empty_moon.png" + alt="{{'BACKLOG.EMPTY' | translate}}" + ) p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY_SEARCH") - p lalalaal div( ng-repeat="tag in projectTags" @@ -154,7 +157,13 @@ section form(tg-bind-scope) .row.mixing-row.table-main.visualization(class="{{ ctrl.mixingClass(tag) }}") .color-column - .current-color(ng-style="{background: tag.color}") + .current-color( + ng-if="tag.color" + ng-style="{background: tag.color}" + ) + .current-color.empty-color( + ng-if="!tag.color" + ) .status-name span(tg-bo-html="tag.name") diff --git a/app/styles/layout/admin-project-tags.scss b/app/styles/layout/admin-project-tags.scss index 3880e826..731eb430 100644 --- a/app/styles/layout/admin-project-tags.scss +++ b/app/styles/layout/admin-project-tags.scss @@ -50,16 +50,24 @@ padding-left: 1rem; } } - .color-filter { - align-items: center; - display: flex; - flex-grow: 1; - padding: 0 10px; - position: relative; + } + .color-filter { + align-items: center; + display: flex; + flex-grow: 1; + padding: 0 10px; + position: relative; + &:hover { input { - padding: 0; + border-bottom: 1px solid $whitish; } } + input { + padding: 0; + } + label { + cursor: pointer; + } } .row { &.tag-row { diff --git a/app/styles/modules/common/colors-table.scss b/app/styles/modules/common/colors-table.scss index 42df79c6..fda0e53d 100644 --- a/app/styles/modules/common/colors-table.scss +++ b/app/styles/modules/common/colors-table.scss @@ -117,7 +117,7 @@ } .current-color { - background-color: $gray-light; + background-color: $whitish; border-radius: 2px; height: 40px; width: 40px; From c9ef7b5f42e486881ae3c9557d912fb19b2973c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 23 Sep 2016 11:59:44 +0200 Subject: [PATCH 142/315] Fix backlog buttoin visualization --- app/partials/backlog/backlog.jade | 10 +++++----- app/styles/layout/backlog.scss | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/partials/backlog/backlog.jade b/app/partials/backlog/backlog.jade index 69330b95..721d5cb0 100644 --- a/app/partials/backlog/backlog.jade +++ b/app/partials/backlog/backlog.jade @@ -36,7 +36,7 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", div.backlog-menu div.backlog-table-options - a.trans-button.move-to-current-sprint.move-to-sprint.e2e-move-to-sprint( + a.trans-button.menu-button.move-to-current-sprint.move-to-sprint.e2e-move-to-sprint( ng-if="currentSprint" href="" title="{{'BACKLOG.MOVE_US_TO_CURRENT_SPRINT' | translate}}" @@ -44,7 +44,7 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", ) tg-svg(svg-icon="icon-move") span.text(translate="BACKLOG.MOVE_US_TO_CURRENT_SPRINT") - a.trans-button.move-to-latest-sprint.move-to-sprint.e2e-move-to-sprint( + a.trans-button.menu-button.move-to-latest-sprint.move-to-sprint.e2e-move-to-sprint( ng-if="!currentSprint" href="" title="{{'BACKLOG.MOVE_US_TO_LATEST_SPRINT' | translate}}" @@ -52,21 +52,21 @@ div.wrapper(tg-backlog, ng-controller="BacklogController as ctrl", ) tg-svg(svg-icon="icon-move") span.text(translate="BACKLOG.MOVE_US_TO_LATEST_SPRINT") - a.trans-button.e2e-open-filter( + a.trans-button.menu-button.e2e-open-filter.ng-animate-disabled( ng-if="!ctrl.activeFilters" href="" title="{{'BACKLOG.FILTERS.TOGGLE' | translate}}" id="show-filters-button" translate="BACKLOG.FILTERS.SHOW" ) - a.trans-button.active.e2e-open-filter( + a.trans-button.menu-button.active.e2e-open-filter.ng-animate-disabled( ng-if="ctrl.activeFilters" href="" title="{{'BACKLOG.FILTERS.HIDE' | translate}}" id="show-filters-button" translate="BACKLOG.FILTERS.HIDE" ) - a.trans-button( + a.trans-button.menu-button( ng-if="userstories.length" href="" title="{{'BACKLOG.TAGS.TOGGLE' | translate}}" diff --git a/app/styles/layout/backlog.scss b/app/styles/layout/backlog.scss index 310e5467..f59fda27 100644 --- a/app/styles/layout/backlog.scss +++ b/app/styles/layout/backlog.scss @@ -25,10 +25,11 @@ display: flex; justify-content: space-between; margin-bottom: 1rem; - .trans-button { + .menu-button { + border-radius: 0; color: $blackish; display: inline-block; - padding: .3rem 1.5rem; + padding: .4rem 1.5rem; &.active, &:hover { background: $whitish; From f1812f385973d8702e01953836180a7e8d7f197f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 23 Sep 2016 12:49:30 +0200 Subject: [PATCH 143/315] Update Enter Tag text --- app/locales/taiga/locale-en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 4b40b16b..f67ddd47 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -120,7 +120,7 @@ "TASK": "Task", "ISSUE": "Issue", "TAGS": { - "PLACEHOLDER": "I'm it! Tag me...", + "PLACEHOLDER": "Enter tag", "DELETE": "Delete tag", "ADD": "Add tag" }, From d61e5d33320ddb5d77c8b5ee6b2bd42df1b97244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 24 Jun 2016 13:11:25 +0200 Subject: [PATCH 144/315] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f28be9d..b823c12a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ## 3.0.0 ???? (Unreleased) ### Features +- Add Epics. - Add the tribe button to link stories from tree.taiga.io with gigs in tribe.taiga.io. - Show a confirmation notice when you exit edit mode by pressing ESC in the markdown inputs. - Errors (not found, server error, permissions and blocked project) don't change the current url. From 872755130627296c2c604b1fd2beae0b15453ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 20 Jul 2016 09:48:56 +0200 Subject: [PATCH 145/315] Epics layout --- app/coffee/app.coffee | 12 +++++++- app/coffee/modules/base.coffee | 1 + app/coffee/modules/epics.coffee | 25 +++++++++++++++++ app/locales/taiga/locale-en.json | 3 ++ .../components/project-menu/project-menu.jade | 16 +++++++++-- .../epics-dashboard.controller.coffee | 28 +++++++++++++++++++ .../epics/dashboard/epics-dashboard.jade | 1 + app/svg/sprite.svg | 6 +++- 8 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 app/coffee/modules/epics.coffee create mode 100644 app/modules/epics/dashboard/epics-dashboard.controller.coffee create mode 100644 app/modules/epics/dashboard/epics-dashboard.jade diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index ccc7247c..97c3efbd 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -124,7 +124,6 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) - $routeProvider.when("/blocked-project/:pslug/", { templateUrl: "projects/project/blocked-project.html", @@ -153,6 +152,16 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) + $routeProvider.when("/project/:pslug/epics", + { + section: "epics", + templateUrl: "epics/dashboard/epics-dashboard.html", + loader: true, + controller: "EpicsDashboardCtrl", + controllerAs: "vm" + } + ) + $routeProvider.when("/project/:pslug/backlog", { templateUrl: "backlog/backlog.html", @@ -792,6 +801,7 @@ modules = [ "taigaDiscover", "taigaHistory", "taigaWikiHistory", + 'taigaEpics', # template cache "templates", diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index c4448294..70d836c2 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -73,6 +73,7 @@ urls = { "project-taskboard": "/project/:project/taskboard/:sprint" "project-kanban": "/project/:project/kanban" "project-issues": "/project/:project/issues" + "project-epics": "/project/:project/epics" "project-search": "/project/:project/search" "project-userstories-detail": "/project/:project/us/:ref" diff --git a/app/coffee/modules/epics.coffee b/app/coffee/modules/epics.coffee new file mode 100644 index 00000000..15941253 --- /dev/null +++ b/app/coffee/modules/epics.coffee @@ -0,0 +1,25 @@ +### +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino Garcia +# Copyright (C) 2014-2016 David Barragán Merino +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Juan Francisco Alcántara +# Copyright (C) 2014-2016 Xavi Julian +# +# 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/projects.coffee +### + +module = angular.module("taigaEpics", []) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index f67ddd47..0cbd64e7 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -391,6 +391,9 @@ "WATCHING_SECTION": "Watching", "DASHBOARD": "Projects Dashboard" }, + "EPICS": { + "TITLE": "EPICS" + }, "PROJECTS": { "PAGE_TITLE": "My projects - Taiga", "PAGE_DESCRIPTION": "A list with all your projects, you can reorder or create a new one.", diff --git a/app/modules/components/project-menu/project-menu.jade b/app/modules/components/project-menu/project-menu.jade index e1bdd2d9..829854d8 100644 --- a/app/modules/components/project-menu/project-menu.jade +++ b/app/modules/components/project-menu/project-menu.jade @@ -15,15 +15,25 @@ nav.menu( tg-svg(svg-icon="icon-search") span.helper(translate="PROJECT.SECTION.SEARCH") - li#nav-timeline - a( + li#nav-timeline + a( tg-nav="project:project=vm.project.get('slug')" ng-class="{active: vm.active == 'project-timeline'}" aria-label="{{'PROJECT.SECTION.TIMELINE' | translate}}" tabindex="2" - ) + ) tg-svg(svg-icon="icon-timeline") span.helper(translate="PROJECT.SECTION.TIMELINE") + + li#nav-epics + a( + tg-nav="project-epics:project=vm.project.get('slug')" + ng-class="{active: vm.active == 'epics'}" + aria-label="{{'EPICS.TITLE' | translate}}" + tabindex="2" + ) + tg-svg(svg-icon="icon-epics") + span.helper(translate="EPICS.TITLE") li#nav-backlog( ng-if="vm.menu.get('backlog')" diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee new file mode 100644 index 00000000..529cbbc1 --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -0,0 +1,28 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: history.controller.coffee +### + +module = angular.module("taigaEpics") + +class EpicsDashboardController + @.$inject = [] + + constructor: () -> + console.log 'Hola' + +module.controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade new file mode 100644 index 00000000..d164ccf0 --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -0,0 +1 @@ +p Epics diff --git a/app/svg/sprite.svg b/app/svg/sprite.svg index 8fa6cc2a..f6f2022f 100644 --- a/app/svg/sprite.svg +++ b/app/svg/sprite.svg @@ -429,7 +429,6 @@ fill="#fff" d="M511.998 107.939c-222.856 0-404.061 181.204-404.061 404.061s181.205 404.061 404.061 404.061c222.856 0 404.061-181.203 404.061-404.061s-181.205-404.061-404.061-404.061zM511.998 158.447c88.671 0 169.621 32.484 231.616 86.222l-498.947 498.948c-53.74-61.998-86.223-142.945-86.223-231.617 0-195.561 157.992-353.553 353.553-353.553zM779.328 280.383c53.74 61.998 86.223 142.945 86.223 231.617 0 195.561-157.992 353.553-353.553 353.553-88.671 0-169.617-32.484-231.616-86.222l498.947-498.948z"> -<<<<<<< b929b5ecdaefcb0f2430ea5c8e41ce8fdcdbe761 Add user View more + Merge @@ -446,5 +446,9 @@ Fill + + Epics + + From 5a3185e69978a9bcabc34d11de4ffa99b5ac2bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 20 Jul 2016 15:46:41 +0200 Subject: [PATCH 146/315] Epics table --- app/locales/taiga/locale-en.json | 15 ++- .../epics-dashboard.controller.coffee | 22 ++++- .../epics/dashboard/epics-dashboard.jade | 20 +++- .../epics-table/epics-table.controller.coffee | 44 +++++++++ .../epics-table/epics-table.directive.coffee | 34 +++++++ .../dashboard/epics-table/epics-table.jade | 99 +++++++++++++++++++ .../dashboard/epics-table/epics-table.scss | 74 ++++++++++++++ 7 files changed, 302 insertions(+), 6 deletions(-) create mode 100644 app/modules/epics/dashboard/epics-table/epics-table.controller.coffee create mode 100644 app/modules/epics/dashboard/epics-table/epics-table.directive.coffee create mode 100644 app/modules/epics/dashboard/epics-table/epics-table.jade create mode 100644 app/modules/epics/dashboard/epics-table/epics-table.scss diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 0cbd64e7..b18c8d15 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -392,7 +392,20 @@ "DASHBOARD": "Projects Dashboard" }, "EPICS": { - "TITLE": "EPICS" + "TITLE": "EPICS", + "DASHBOARD": { + "ADD": "+ ADD EPIC" + }, + "TABLE": { + "VOTES": "Votes", + "NAME": "Name", + "PROJECT": "Project", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned to", + "STATUS": "Status", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + } }, "PROJECTS": { "PAGE_TITLE": "My projects - Taiga", diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 529cbbc1..267b7f38 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -14,15 +14,29 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -# File: history.controller.coffee +# File: epics.dashboard.controller.coffee ### module = angular.module("taigaEpics") class EpicsDashboardController - @.$inject = [] + @.$inject = [ + "$tgResources", + "$routeParams", + "tgErrorHandlingService" + ] - constructor: () -> - console.log 'Hola' + constructor: (@rs, @params, @errorHandlingService) -> + @.sectionName = "Epics" + @._loadProject() + + _loadProject: () -> + return @rs.projects.getBySlug(@params.pslug).then (project) => + if not project.is_epics_activated + @errorHandlingService.permissionDenied() + @project = project + + addNewEpic: () -> + console.log 'Add new Epic' module.controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index d164ccf0..0ac4a42c 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -1 +1,19 @@ -p Epics +doctype html + +.wrapper() + tg-project-menu + section.main(role="main") + header.header-with-actions + h1( + tg-main-title + project-name="vm.project.name" + i18n-section-name="{{ vm.sectionName }}" + ) + .action-buttons + button.button-green( + translate="EPICS.DASHBOARD.ADD" + title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", + ng-click="vm.addNewEpic()" + ) + + tg-epics-table diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee new file mode 100644 index 00000000..9c145472 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -0,0 +1,44 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: epics-table.controller.coffee +### + +module = angular.module("taigaEpics") + +class EpicsTableController + @.$inject = [] + + constructor: () -> + @.displayOptions = false + @.displayVotes = true + @.column = { + votes: true, + name: true, + project: true, + sprint: true, + assigned: true, + status: true, + progress: true + } + + toggleEpicTableOptions: () -> + @.displayOptions = !@.displayOptions + + updateEpicTableColumns: () -> + console.log @.column + +module.controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee new file mode 100644 index 00000000..0ceef836 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -0,0 +1,34 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: epics-table.directive.coffee +### + +module = angular.module('taigaEpics') + +EpicsTableDirective = () -> + + return { + templateUrl:"epics/dashboard/epics-table/epics-table.html", + controller: "EpicsTableCtrl", + controllerAs: "vm", + bindToController: true, + scope: {} + } + +EpicsTableDirective.$inject = [] + +module.directive("tgEpicsTable", EpicsTableDirective) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade new file mode 100644 index 00000000..aa0ecbd4 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -0,0 +1,99 @@ +mixin epicSwitch(name, model) + div.check + input.activate-input( + id= name + name= name + type="checkbox" + ng-checked= model + ng-model= model + ng-change="vm.updateEpicTableColumns()" + ) + div + span.check-text.check-yes(translate="COMMON.YES") + span.check-text.check-no(translate="COMMON.NO") + +.epics-table + .epics-table-header + .vote( + translate="EPICS.TABLE.VOTES" + ng-if="vm.column.votes" + ) + .name( + translate="EPICS.TABLE.NAME" + ng-if="vm.column.name" + ) + .project( + translate="EPICS.TABLE.PROJECT" + ng-if="vm.column.project" + ) + .sprint( + translate="EPICS.TABLE.SPRINT" + ng-if="vm.column.sprint" + ) + .assigned( + translate="EPICS.TABLE.ASSIGNED_TO" + ng-if="vm.column.assigned" + ) + .status( + translate="EPICS.TABLE.STATUS" + ng-if="vm.column.status" + ) + .progress( + translate="EPICS.TABLE.PROGRESS" + ng-if="vm.column.progress" + ) + .epics-table-options-wrapper(ng-mouseleave="vm.displayOptions = false") + button.epics-table-option-button(ng-click="vm.displayOptions = true") + span(translate="EPICS.TABLE.VIEW_OPTIONS") + tg-svg(svg-icon="icon-arrow-down") + form.epics-table-dropdown(ng-show="vm.displayOptions") + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.VOTES" + for="epicSwitch-votes" + ) + +epicSwitch('switch-votes', 'vm.column.votes') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.NAME" + for="switch-name" + ) + +epicSwitch('switch-name', 'vm.column.name') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.PROJECT" + for="switch-project" + ) + +epicSwitch('switch-project', 'vm.column.project') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.SPRINT" + for="switch-sprint" + ) + +epicSwitch('switch-sprint', 'vm.column.sprint') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.ASSIGNED_TO" + for="switch-assigned" + ) + +epicSwitch('switch-assigned', 'vm.column.assigned') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.STATUS" + for="switch-status" + ) + +epicSwitch('switch-status', 'vm.column.status') + .fieldset + label.epics-table-options-vote( + translate="EPICS.TABLE.PROGRESS" + for="switch-progress" + ) + +epicSwitch('switch-progress', 'vm.column.progress') + .epics-table-body + .vote + .name + .project + .sprint + .assigned + .status + .progress diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss new file mode 100644 index 00000000..13e17061 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -0,0 +1,74 @@ +.epics-table { + margin-top: 2rem; +} + +.epics-table-header, +.epics-table-body { + display: flex; + .assigned, + .vote { + flex-basis: 100px; + flex-grow: 0; + flex-shrink: 0; + } + .status, + .sprint { + flex-basis: 200px; + flex-grow: 0; + flex-shrink: 0; + } + .name, + .project, + .progress { + flex: 1; + } + .progress { + position: relative; + } +} + +.epics-table-header { + @include font-type(bold); + border-bottom: 1px solid $gray-light; + padding: .5rem; + position: relative; + .epics-table-options { + @include font-type(text); + @include font-size(small); + } +} + +.epics-table-options-wrapper { + position: absolute; + right: .5rem; + top: .5rem; +} + +.epics-table-option-button { + @include font-type(light); + background: none; + .icon { + @include svg-size(.7rem); + } +} + +.epics-table-dropdown { + background: $white; + box-shadow: 3px 3px 2px rgba($black, .1); + padding: .5rem; + position: absolute; + right: 0; + top: 1.25rem; + width: 250px; + .fieldset { + @include font-size(small); + border-bottom: 1px solid $whitish; + color: $gray-light; + display: flex; + justify-content: space-between; + padding: .5rem 0; + &:last-child { + border: 0; + } + } +} From cc02221a2de51a50e72f5f089a60efa21df38076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 21 Jul 2016 16:41:51 +0200 Subject: [PATCH 147/315] Epics dasboard --- app/coffee/modules/resources.coffee | 3 ++ app/locales/taiga/locale-en.json | 2 +- .../epic-row/epic-row.controller.coffee | 29 +++++++++++++++ .../epic-row/epic-row.directive.coffee | 36 ++++++++++++++++++ .../epics/dashboard/epic-row/epic-row.jade | 29 +++++++++++++++ .../epics/dashboard/epic-row/epic-row.scss | 31 ++++++++++++++++ .../epics-dashboard.controller.coffee | 2 +- .../epics/dashboard/epics-dashboard.jade | 5 ++- .../epics-table/epics-table.controller.coffee | 14 +++++-- .../epics-table/epics-table.directive.coffee | 4 +- .../dashboard/epics-table/epics-table.jade | 12 ++---- .../dashboard/epics-table/epics-table.scss | 36 +++++++++++++----- .../resources/epics-resource.service.coffee | 37 +++++++++++++++++++ app/modules/resources/resources.coffee | 3 +- 14 files changed, 217 insertions(+), 26 deletions(-) create mode 100644 app/modules/epics/dashboard/epic-row/epic-row.controller.coffee create mode 100644 app/modules/epics/dashboard/epic-row/epic-row.directive.coffee create mode 100644 app/modules/epics/dashboard/epic-row/epic-row.jade create mode 100644 app/modules/epics/dashboard/epic-row/epic-row.scss create mode 100644 app/modules/resources/epics-resource.service.coffee diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 59e8743a..df127c5e 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -92,6 +92,9 @@ urls = { # Milestones/Sprints "milestones": "/milestones" + # Epics + "epics": "/epics" + # User stories "userstories": "/userstories" "bulk-create-us": "/userstories/bulk_create" diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index b18c8d15..e8b17d53 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -401,7 +401,7 @@ "NAME": "Name", "PROJECT": "Project", "SPRINT": "Sprint", - "ASSIGNED_TO": "Assigned to", + "ASSIGNED_TO": "Assigned", "STATUS": "Status", "PROGRESS": "Progress", "VIEW_OPTIONS": "View options" diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee new file mode 100644 index 00000000..09ebcfd0 --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -0,0 +1,29 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: epics-table.controller.coffee +### + +module = angular.module("taigaEpics") + +class EpicRowController + @.$inject = [ + ] + + constructor: () -> + console.log @.epic.toJS() + +module.controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee new file mode 100644 index 00000000..cb80d678 --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -0,0 +1,36 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: epics-table.directive.coffee +### + +module = angular.module('taigaEpics') + +EpicRowDirective = () -> + + return { + templateUrl:"epics/dashboard/epic-row/epic-row.html", + controller: "EpicRowCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + epic: '=' + } + } + +EpicRowDirective.$inject = [] + +module.directive("tgEpicRow", EpicRowDirective) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade new file mode 100644 index 00000000..f14f6592 --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -0,0 +1,29 @@ +.epic-row + .vote(ng-class="{'is-voter': vm.epic.get('is_voter')}") + tg-svg(svg-icon='icon-upvote') + span {{::vm.epic.get('total_voters')}} + + .name() {{::vm.epic.get('subject')}} + + .project() {{::vm.epic.get('project')}} + .sprint( + translate="EPICS.TABLE.SPRINT" + ) + .assigned( + ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ) + img( + ng-src="{{vm.epic.getIn(['assigned_to_extra_info', 'photo'])}}" + alt="::vm.epic.getIn(['assigned_to_extra_info', 'name'])" + ) + .assigned( + ng-if="!vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ) Unassigned + .status( + ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" + ) + span {{::vm.epic.getIn(['status_extra_info', 'name'])}} + tg-svg(svg-icon="icon-arrow-down") + .progress + .progress-bar + .progress-status diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss new file mode 100644 index 00000000..2d3f294b --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -0,0 +1,31 @@ +.epic-row { + @include font-size(small); + align-items: center; + border-bottom: 1px solid $whitish; + display: flex; + .progress-bar, + .progress-status { + height: 1.5rem; + left: 0; + position: absolute; + top: .25rem; + } + .progress-bar { + background: $mass-white; + max-width: 40vw; + width: 100%; + } + .progress-status { + background: $primary-light; + width: 10vw; + } + .vote { + color: $gray; + } + .icon-upvote { + @include svg-size(.75rem); + fill: $gray; + margin-right: .25rem; + vertical-align: middle; + } +} diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 267b7f38..91fb2ecd 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -34,7 +34,7 @@ class EpicsDashboardController return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.is_epics_activated @errorHandlingService.permissionDenied() - @project = project + @.project = project addNewEpic: () -> console.log 'Add new Epic' diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 0ac4a42c..38b45593 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -16,4 +16,7 @@ doctype html ng-click="vm.addNewEpic()" ) - tg-epics-table + tg-epics-table( + ng-if="vm.project" + project="vm.project" + ) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee index 9c145472..717916dc 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -20,9 +20,11 @@ module = angular.module("taigaEpics") class EpicsTableController - @.$inject = [] + @.$inject = [ + "tgResources" + ] - constructor: () -> + constructor: (@rs) -> @.displayOptions = false @.displayVotes = true @.column = { @@ -34,11 +36,15 @@ class EpicsTableController status: true, progress: true } + @._loadEpics() toggleEpicTableOptions: () -> @.displayOptions = !@.displayOptions - updateEpicTableColumns: () -> - console.log @.column + _loadEpics: () -> + projectId = @.project.id + params = {} + promise = @rs.epics.listAll(projectId, params).then (epics) => + @.epics = epics module.controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee index 0ceef836..39c75a8f 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -26,7 +26,9 @@ EpicsTableDirective = () -> controller: "EpicsTableCtrl", controllerAs: "vm", bindToController: true, - scope: {} + scope: { + project: "=" + } } EpicsTableDirective.$inject = [] diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index aa0ecbd4..6bfe6eba 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -6,7 +6,6 @@ mixin epicSwitch(name, model) type="checkbox" ng-checked= model ng-model= model - ng-change="vm.updateEpicTableColumns()" ) div span.check-text.check-yes(translate="COMMON.YES") @@ -90,10 +89,7 @@ mixin epicSwitch(name, model) ) +epicSwitch('switch-progress', 'vm.column.progress') .epics-table-body - .vote - .name - .project - .sprint - .assigned - .status - .progress + .epics-table-body-row(tg-repeat="epic in vm.epics track by epic.get('id')") + tg-epic-row( + epic="epic" + ) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 13e17061..69b94f75 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -4,23 +4,40 @@ .epics-table-header, .epics-table-body { - display: flex; .assigned, + .project, + .vote, + .status, + .sprint, + .name, + .progress { + padding: 1rem .5rem; + } + + .assigned, + .project, .vote { - flex-basis: 100px; + flex-basis: 80px; flex-grow: 0; flex-shrink: 0; + flex-wrap: wrap; + text-align: center; } .status, .sprint { - flex-basis: 200px; + flex-basis: 150px; flex-grow: 0; flex-shrink: 0; + flex-wrap: wrap; } .name, - .project, .progress { flex: 1; + max-width: 40vw; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 90%; } .progress { position: relative; @@ -30,12 +47,9 @@ .epics-table-header { @include font-type(bold); border-bottom: 1px solid $gray-light; + display: flex; padding: .5rem; position: relative; - .epics-table-options { - @include font-type(text); - @include font-size(small); - } } .epics-table-options-wrapper { @@ -46,6 +60,7 @@ .epics-table-option-button { @include font-type(light); + @include font-size(small); background: none; .icon { @include svg-size(.7rem); @@ -54,11 +69,14 @@ .epics-table-dropdown { background: $white; + border-bottom: 1px solid rgba($black, .1); + border-left: 1px solid rgba($black, .1); + border-right: 1px solid rgba($black, .1); box-shadow: 3px 3px 2px rgba($black, .1); padding: .5rem; position: absolute; right: 0; - top: 1.25rem; + top: 1.3rem; width: 250px; .fieldset { @include font-size(small); diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee new file mode 100644 index 00000000..d8c039c9 --- /dev/null +++ b/app/modules/resources/epics-resource.service.coffee @@ -0,0 +1,37 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: epics-resource.service.coffee +### + +Resource = (urlsService, http) -> + service = {} + + service.listAll = (params) -> + url = urlsService.resolve("epics") + + httpOptions = {} + + return http.get(url, params, httpOptions).then (result) -> + return Immutable.fromJS(result.data) + + return () -> + return {"epics": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgEpicsResource", Resource) diff --git a/app/modules/resources/resources.coffee b/app/modules/resources/resources.coffee index 5fafa0bf..f55dd7cc 100644 --- a/app/modules/resources/resources.coffee +++ b/app/modules/resources/resources.coffee @@ -27,7 +27,8 @@ services = [ "tgExternalAppsResource", "tgAttachmentsResource", "tgStatsResource", - "tgWikiHistory" + "tgWikiHistory", + "tgEpicsResource" ] Resources = ($injector) -> From 7b124d597b509cc6ee051b2cff8532d00ea469ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 22 Jul 2016 08:37:37 +0200 Subject: [PATCH 148/315] Hide columns --- .../epic-row/epic-row.controller.coffee | 6 +++ .../epic-row/epic-row.directive.coffee | 4 +- .../epics/dashboard/epic-row/epic-row.jade | 40 ++++++++++++++----- .../epics/dashboard/epic-row/epic-row.scss | 20 ++++++++++ .../dashboard/epics-table/epics-table.jade | 2 + .../dashboard/epics-table/epics-table.scss | 7 +++- 6 files changed, 66 insertions(+), 13 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index 09ebcfd0..b943cc53 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -25,5 +25,11 @@ class EpicRowController constructor: () -> console.log @.epic.toJS() + @._calculateProgressBar() + + _calculateProgressBar: () -> + totalUs = @.epic.getIn(['user_stories_counts', 'closed']) + totalUsCompleted = @.epic.getIn(['user_stories_counts', 'opened']) + @.percentage = (totalUs * 100) / totalUsCompleted module.controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee index cb80d678..3332f917 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -27,7 +27,9 @@ EpicRowDirective = () -> controllerAs: "vm", bindToController: true, scope: { - epic: '=' + project: '=', + epic: '=', + column: '=' } } diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index f14f6592..e3bfffdd 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,29 +1,47 @@ -.epic-row - .vote(ng-class="{'is-voter': vm.epic.get('is_voter')}") +.epic-row( + ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed')}" +) + .vote( + ng-if="vm.column.votes" + ng-class="{'is-voter': vm.epic.get('is_voter')}" + ) tg-svg(svg-icon='icon-upvote') span {{::vm.epic.get('total_voters')}} - .name() {{::vm.epic.get('subject')}} + .name(ng-if="vm.column.name") + a( + tg-nav="project-epic-detail:project=vm.project.get('slug')" + ng-attr-title="{{::vm.epic.get('subject')}}" + ) {{::vm.epic.get('subject')}} - .project() {{::vm.epic.get('project')}} + .project(ng-if="vm.column.project") {{::vm.epic.get('project')}} .sprint( + ng-if="vm.column.sprint" translate="EPICS.TABLE.SPRINT" ) .assigned( - ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ng-if="vm.column.assigned && vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ) img( ng-src="{{vm.epic.getIn(['assigned_to_extra_info', 'photo'])}}" alt="::vm.epic.getIn(['assigned_to_extra_info', 'name'])" ) .assigned( - ng-if="!vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ng-if="vm.column.assigned && !vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ) Unassigned .status( + ng-if="vm.column.status" ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" - ) - span {{::vm.epic.getIn(['status_extra_info', 'name'])}} - tg-svg(svg-icon="icon-arrow-down") - .progress + ng-mouseleave="displayStatusList = false" + ) + button( + ng-click="displayStatusList = true" + ) + span {{::vm.epic.getIn(['status_extra_info', 'name'])}} + tg-svg(svg-icon="icon-arrow-down") + .progress(ng-if="vm.column.progress") .progress-bar - .progress-status + .progress-status( + ng-if="::vm.percentage" + ng-attr-width="::vm.percentage" + ) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 2d3f294b..1998624c 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -3,6 +3,26 @@ align-items: center; border-bottom: 1px solid $whitish; display: flex; + &.is-blocked { + background: rgba($red-light, .5); + } + &.is-closed { + .name { + color: $gray-light; + text-decoration: line-through; + } + } + .status { + cursor: pointer; + button { + background: none; + } + .icon { + @include svg-size(.7rem); + fill: $gray-light; + margin-left: .1rem; + } + } .progress-bar, .progress-status { height: 1.5rem; diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 6bfe6eba..2f64a270 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -92,4 +92,6 @@ mixin epicSwitch(name, model) .epics-table-body-row(tg-repeat="epic in vm.epics track by epic.get('id')") tg-epic-row( epic="epic" + project="vm.project" + column="vm.column" ) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 69b94f75..8aff604d 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -29,11 +29,15 @@ flex-grow: 0; flex-shrink: 0; flex-wrap: wrap; + max-width: 150px; } .name, .progress { flex: 1; max-width: 40vw; + } + .name, + .sprint { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -53,9 +57,9 @@ } .epics-table-options-wrapper { + bottom: 1rem; position: absolute; right: .5rem; - top: .5rem; } .epics-table-option-button { @@ -78,6 +82,7 @@ right: 0; top: 1.3rem; width: 250px; + z-index: 99; .fieldset { @include font-size(small); border-bottom: 1px solid $whitish; From 9c1a7013b80e012c65d083df4b72bb1224f6ac5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 22 Jul 2016 12:20:20 +0200 Subject: [PATCH 149/315] Epic statuses --- .../epic-row/epic-row.controller.coffee | 1 + .../epics/dashboard/epic-row/epic-row.jade | 13 +++++++-- .../epics/dashboard/epic-row/epic-row.scss | 29 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index b943cc53..640be052 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -25,6 +25,7 @@ class EpicRowController constructor: () -> console.log @.epic.toJS() + console.log @.project @._calculateProgressBar() _calculateProgressBar: () -> diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index e3bfffdd..20ccb2e1 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,6 +1,7 @@ .epic-row( ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed')}" ) + tg-svg(svg-icon="icon-drag") .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.epic.get('is_voter')}" @@ -31,14 +32,22 @@ ) Unassigned .status( ng-if="vm.column.status" - ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" ng-mouseleave="displayStatusList = false" ) button( ng-click="displayStatusList = true" + ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" ) span {{::vm.epic.getIn(['status_extra_info', 'name'])}} - tg-svg(svg-icon="icon-arrow-down") + tg-svg( + svg-icon="icon-arrow-down" + ) + + ul.epic-statuses(ng-show="displayStatusList") + li( + ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" + ng-click="vm.updateEpicStatus(status.name)" + ) {{status.name}} .progress(ng-if="vm.column.progress") .progress-bar .progress-status( diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 1998624c..2e5076cd 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -12,8 +12,12 @@ text-decoration: line-through; } } + .icon-drag { + fill: $gray-light; + } .status { cursor: pointer; + position: relative; button { background: none; } @@ -48,4 +52,29 @@ margin-right: .25rem; vertical-align: middle; } + .epic-statuses { + @include font-type(light); + @include font-size(small); + background: rgba($blackish, .9); + border-bottom: 1px solid $grayer; + box-shadow: 3px 3px 2px rgba($black, .1); + color: $white; + left: 0; + list-style-type: none; + margin: 0; + position: absolute; + top: 2.5rem; + width: 200px; + z-index: 99; + &:last-child { + border: 0; + } + li { + padding: .5rem; + &:hover { + color: $primary-light; + transition: color .3s linear; + } + } + } } From 6e6257f03954aa9b0fadaf0f8937c3ef6f620f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 22 Jul 2016 14:03:12 +0200 Subject: [PATCH 150/315] Update Epic Status --- .../epic-row/epic-row.controller.coffee | 25 ++++++++++++++++--- .../epic-row/epic-row.directive.coffee | 4 ++- .../epics/dashboard/epic-row/epic-row.jade | 14 ++++++++--- .../epics/dashboard/epic-row/epic-row.scss | 12 ++++++++- .../epics-table/epics-table.controller.coffee | 14 ++++++++--- .../dashboard/epics-table/epics-table.jade | 2 ++ .../resources/epics-resource.service.coffee | 13 ++++++++++ 7 files changed, 70 insertions(+), 14 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index 640be052..f1719faf 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -21,16 +21,33 @@ module = angular.module("taigaEpics") class EpicRowController @.$inject = [ + "tgResources", + "$tgConfirm" ] - constructor: () -> - console.log @.epic.toJS() - console.log @.project + constructor: (@rs, @confirm) -> @._calculateProgressBar() _calculateProgressBar: () -> totalUs = @.epic.getIn(['user_stories_counts', 'closed']) totalUsCompleted = @.epic.getIn(['user_stories_counts', 'opened']) - @.percentage = (totalUs * 100) / totalUsCompleted + @.percentage = totalUs * 100 / totalUsCompleted + + updateEpicStatus: (status) -> + id = @.epic.get('id') + version = @.epic.get('version') + patch = { + 'status': status, + 'version': version + } + + onSuccess = => + @.onUpdateEpicStatus() + + onError = (data) => + console.log data + @confirm.notify('error') + + return @rs.epics.patch(id, patch).then(onSuccess, onError) module.controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee index 3332f917..14b224c8 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -29,7 +29,9 @@ EpicRowDirective = () -> scope: { project: '=', epic: '=', - column: '=' + column: '=', + permissions: '=', + onUpdateEpicStatus: "&" } } diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 20ccb2e1..8e7adee3 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,7 +1,9 @@ .epic-row( ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed')}" ) - tg-svg(svg-icon="icon-drag") + tg-svg( + svg-icon="icon-drag" + ) .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.epic.get('is_voter')}" @@ -31,14 +33,18 @@ ng-if="vm.column.assigned && !vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ) Unassigned .status( - ng-if="vm.column.status" + ng-if="vm.column.status && !vm.permissions.canEdit" + ) + span {{vm.epic.getIn(['status_extra_info', 'name'])}} + .status( + ng-if="vm.column.status && vm.permissions.canEdit" ng-mouseleave="displayStatusList = false" ) button( ng-click="displayStatusList = true" ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" ) - span {{::vm.epic.getIn(['status_extra_info', 'name'])}} + span {{vm.epic.getIn(['status_extra_info', 'name'])}} tg-svg( svg-icon="icon-arrow-down" ) @@ -46,7 +52,7 @@ ul.epic-statuses(ng-show="displayStatusList") li( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" - ng-click="vm.updateEpicStatus(status.name)" + ng-click="vm.updateEpicStatus(status.id)" ) {{status.name}} .progress(ng-if="vm.column.progress") .progress-bar diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 2e5076cd..540450aa 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -2,7 +2,14 @@ @include font-size(small); align-items: center; border-bottom: 1px solid $whitish; + cursor: pointer; display: flex; + &:hover { + .icon-drag { + cursor: move; + opacity: 1; + } + } &.is-blocked { background: rgba($red-light, .5); } @@ -13,7 +20,10 @@ } } .icon-drag { - fill: $gray-light; + @include svg-size(.75rem); + fill: $whitish; + opacity: 0; + transition: opacity .1s; } .status { cursor: pointer; diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee index 717916dc..8d498366 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -36,15 +36,21 @@ class EpicsTableController status: true, progress: true } - @._loadEpics() + @.loadEpics() + @._checkPermissions() toggleEpicTableOptions: () -> @.displayOptions = !@.displayOptions - _loadEpics: () -> + _checkPermissions: () -> + @.permissions = { + canEdit: _.includes(@.project.my_permissions, 'modify_epic') + } + + loadEpics: () -> projectId = @.project.id - params = {} - promise = @rs.epics.listAll(projectId, params).then (epics) => + promise = @rs.epics.list(projectId).then (epics) => @.epics = epics + console.log @.epics module.controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 2f64a270..6761b966 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -94,4 +94,6 @@ mixin epicSwitch(name, model) epic="epic" project="vm.project" column="vm.column" + on-update-epic-status="vm.loadEpics()" + permissions="vm.permissions" ) diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index d8c039c9..ddc4fe40 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -28,6 +28,19 @@ Resource = (urlsService, http) -> return http.get(url, params, httpOptions).then (result) -> return Immutable.fromJS(result.data) + service.list = (projectId) -> + url = urlsService.resolve("epics") + + params = {project: projectId} + + return http.get(url, params) + .then (result) -> Immutable.fromJS(result.data) + + service.patch = (id, patch) -> + url = urlsService.resolve("epics") + "/#{id}" + + return http.patch(url, patch) + return () -> return {"epics": service} From ad0b59f0a917b091ff5e912aa0cbfc66f242bf21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 26 Jul 2016 11:09:40 +0200 Subject: [PATCH 151/315] Epics table layout --- app/locales/taiga/locale-en.json | 3 ++- .../epics/dashboard/epic-row/epic-row.jade | 16 ++++++++++++---- .../epics/dashboard/epic-row/epic-row.scss | 8 ++++++++ .../epics/dashboard/epics-table/epics-table.scss | 13 ++++++++++--- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index e8b17d53..cfd6397d 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -394,7 +394,8 @@ "EPICS": { "TITLE": "EPICS", "DASHBOARD": { - "ADD": "+ ADD EPIC" + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Unassigned" }, "TABLE": { "VOTES": "Votes", diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 8e7adee3..d260d601 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -23,15 +23,23 @@ translate="EPICS.TABLE.SPRINT" ) .assigned( - ng-if="vm.column.assigned && vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ng-if="vm.column.assigned && vm.epic.get('assigned_to')" ) img( + ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ng-src="{{vm.epic.getIn(['assigned_to_extra_info', 'photo'])}}" - alt="::vm.epic.getIn(['assigned_to_extra_info', 'name'])" + alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" + ) + img( + ng-if="!vm.epic.getIn(['assigned_to_extra_info', 'photo'])" + ng-src="https://www.gravatar.com/avatar/{{vm.epic.getIn(['assigned_to_extra_info', 'gravatar_id'])}}" + alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" ) .assigned( - ng-if="vm.column.assigned && !vm.epic.getIn(['assigned_to_extra_info', 'photo'])" - ) Unassigned + ng-if="vm.column.assigned && !vm.epic.get('assigned_to')" + ng-class="{'is-unassigned': !vm.epic.get('assigned_to')}" + translate="EPICS.DASHBOARD.UNASSIGNED" + ) .status( ng-if="vm.column.status && !vm.permissions.canEdit" ) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 540450aa..23b55494 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -56,12 +56,20 @@ .vote { color: $gray; } + .assigned { + img { + width: 40px; + } + } .icon-upvote { @include svg-size(.75rem); fill: $gray; margin-right: .25rem; vertical-align: middle; } + .is-unassigned { + color: $gray-light; + } .epic-statuses { @include font-type(light); @include font-size(small); diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 8aff604d..9342d0cd 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -4,7 +4,9 @@ .epics-table-header, .epics-table-body { - .assigned, + .assigned { + padding: .5rem; + } .project, .vote, .status, @@ -17,7 +19,7 @@ .assigned, .project, .vote { - flex-basis: 80px; + flex-basis: 100px; flex-grow: 0; flex-shrink: 0; flex-wrap: wrap; @@ -33,7 +35,9 @@ } .name, .progress { - flex: 1; + flex-basis: 20vw; + flex-grow: 1; + flex-shrink: 2; max-width: 40vw; } .name, @@ -54,6 +58,9 @@ display: flex; padding: .5rem; position: relative; + .assigned { + padding: 1rem .5rem; + } } .epics-table-options-wrapper { From c4d52616eefc651a085316bfb6c11aa4aaa01258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 26 Jul 2016 12:39:17 +0200 Subject: [PATCH 152/315] Sort Epics --- .../epics/dashboard/epic-row/epic-row.scss | 7 ++-- .../epic-sortable.directive.coffee | 35 +++++++++++++++++++ .../epics-table/epics-table.controller.coffee | 4 ++- .../dashboard/epics-table/epics-table.jade | 2 +- .../dashboard/epics-table/epics-table.scss | 2 +- 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 23b55494..a3d952e0 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -1,12 +1,14 @@ .epic-row { @include font-size(small); align-items: center; + background: $white; border-bottom: 1px solid $whitish; - cursor: pointer; + cursor: move; display: flex; + transition: background .2s; &:hover { + background: rgba($primary-light, .05); .icon-drag { - cursor: move; opacity: 1; } } @@ -21,6 +23,7 @@ } .icon-drag { @include svg-size(.75rem); + cursor: move; fill: $whitish; opacity: 0; transition: opacity .1s; diff --git a/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee b/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee new file mode 100644 index 00000000..b0d70468 --- /dev/null +++ b/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee @@ -0,0 +1,35 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: epic-sortable.directive.coffee +### + +EpicSortableDirective = ($parse) -> + link = (scope, el, attrs) -> + + drake = dragula([el[0]]) + + scope.$on "$destroy", -> + el.off() + drake.destroy() + + return { + link: link + } + +EpicSortableDirective.$inject = [] + +angular.module("taigaComponents").directive("tgEpicSortable", EpicSortableDirective) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee index 8d498366..fb22e4ed 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -51,6 +51,8 @@ class EpicsTableController projectId = @.project.id promise = @rs.epics.list(projectId).then (epics) => @.epics = epics - console.log @.epics + + reorderEpics: (epic, index) -> + console.log epic, index module.controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 6761b966..ef17423b 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -88,7 +88,7 @@ mixin epicSwitch(name, model) for="switch-progress" ) +epicSwitch('switch-progress', 'vm.column.progress') - .epics-table-body + .epics-table-body(tg-epic-sortable) .epics-table-body-row(tg-repeat="epic in vm.epics track by epic.get('id')") tg-epic-row( epic="epic" diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 9342d0cd..5e43af45 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -3,7 +3,7 @@ } .epics-table-header, -.epics-table-body { +.epic-row { .assigned { padding: .5rem; } From 4344b72f7a8f85e6eddafcc66e39a5986b93e4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 27 Jul 2016 11:02:04 +0200 Subject: [PATCH 153/315] User Story row --- app/locales/taiga/locale-en.json | 1 + .../epic-row/epic-row.controller.coffee | 18 +++++ .../epics/dashboard/epic-row/epic-row.jade | 44 +++++++--- .../epics/dashboard/epic-row/epic-row.scss | 37 +++++++-- .../dashboard/epics-table/epics-table.scss | 53 +----------- .../story-row/story-row.controller.coffee | 35 ++++++++ .../story-row/story-row.directive.coffee | 39 +++++++++ .../epics/dashboard/story-row/story-row.jade | 54 +++++++++++++ .../epics/dashboard/story-row/story-row.scss | 80 +++++++++++++++++++ .../userstories-resource.service.coffee | 12 +++ .../dependencies/mixins/epics-dashboard.scss | 57 +++++++++++++ 11 files changed, 361 insertions(+), 69 deletions(-) create mode 100644 app/modules/epics/dashboard/story-row/story-row.controller.coffee create mode 100644 app/modules/epics/dashboard/story-row/story-row.directive.coffee create mode 100644 app/modules/epics/dashboard/story-row/story-row.jade create mode 100644 app/modules/epics/dashboard/story-row/story-row.scss create mode 100644 app/styles/dependencies/mixins/epics-dashboard.scss diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index cfd6397d..012bb8a5 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -393,6 +393,7 @@ }, "EPICS": { "TITLE": "EPICS", + "EPIC": "EPIC", "DASHBOARD": { "ADD": "+ ADD EPIC", "UNASSIGNED": "Unassigned" diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index f1719faf..cb1e5989 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -26,6 +26,7 @@ class EpicRowController ] constructor: (@rs, @confirm) -> + @.displayUserStories = false @._calculateProgressBar() _calculateProgressBar: () -> @@ -50,4 +51,21 @@ class EpicRowController return @rs.epics.patch(id, patch).then(onSuccess, onError) + requestUserStories: (epic) -> + if @.displayUserStories == false + id = @.epic.get('id') + + onSuccess = (data) => + @.epicStories = data + console.log @.epicStories.toJS() + @.displayUserStories = true + @confirm.notify('success') + + onError = (data) => + @confirm.notify('error') + + return @rs.userstories.listInEpics(id).then(onSuccess, onError) + else + @.displayUserStories = false + module.controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index d260d601..1eadbc19 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,40 +1,49 @@ .epic-row( - ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed')}" + ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed'), 'unfold': vm.displayUserStories}" + ng-click="vm.requestUserStories(vm.epic)" ) - tg-svg( + tg-svg.icon-drag( svg-icon="icon-drag" ) .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.epic.get('is_voter')}" - ) + ) tg-svg(svg-icon='icon-upvote') span {{::vm.epic.get('total_voters')}} - - .name(ng-if="vm.column.name") + + .name(ng-if="vm.column.name") + - var hash = "#"; a( tg-nav="project-epic-detail:project=vm.project.get('slug')" ng-attr-title="{{::vm.epic.get('subject')}}" - ) {{::vm.epic.get('subject')}} - - .project(ng-if="vm.column.project") {{::vm.epic.get('project')}} + ) #{hash}{{::vm.epic.get('ref')}} {{::vm.epic.get('subject')}} + span.epic-pill( + ng-style="::{'background-color': vm.epic.get('color')}" + translate="EPICS.EPIC" + ) + tg-svg( + svg-icon="icon-arrow-down" + ng-if="vm.epic.getIn(['user_stories_counts', 'opened']) || vm.epic.getIn(['user_stories_counts', 'closed'])" + ) + + .project(ng-if="vm.column.project") .sprint( ng-if="vm.column.sprint" - translate="EPICS.TABLE.SPRINT" ) .assigned( ng-if="vm.column.assigned && vm.epic.get('assigned_to')" - ) + ) img( ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ng-src="{{vm.epic.getIn(['assigned_to_extra_info', 'photo'])}}" alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" - ) + ) img( ng-if="!vm.epic.getIn(['assigned_to_extra_info', 'photo'])" ng-src="https://www.gravatar.com/avatar/{{vm.epic.getIn(['assigned_to_extra_info', 'gravatar_id'])}}" alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" - ) + ) .assigned( ng-if="vm.column.assigned && !vm.epic.get('assigned_to')" ng-class="{'is-unassigned': !vm.epic.get('assigned_to')}" @@ -56,7 +65,7 @@ tg-svg( svg-icon="icon-arrow-down" ) - + ul.epic-statuses(ng-show="displayStatusList") li( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" @@ -68,3 +77,12 @@ ng-if="::vm.percentage" ng-attr-width="::vm.percentage" ) +.epic-stories-wrapper(ng-if="vm.displayUserStories && vm.epicStories") + + .epic-story(tg-repeat="story in vm.epicStories track by story.get('id')") + tg-story-row( + epic="vm.epic" + story="story" + project="vm.project" + column="vm.column" + ) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index a3d952e0..7760f8e0 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -1,9 +1,12 @@ +@import '../../../../styles/dependencies/mixins/epics-dashboard'; + .epic-row { + @include epics-table; @include font-size(small); align-items: center; background: $white; border-bottom: 1px solid $whitish; - cursor: move; + cursor: pointer; display: flex; transition: background .2s; &:hover { @@ -21,6 +24,19 @@ text-decoration: line-through; } } + &.unfold { + .name { + .icon { + transform: rotate(0deg); + } + } + } + .name { + .icon { + transform: rotate(180deg); + transition: all .2s; + } + } .icon-drag { @include svg-size(.75rem); cursor: move; @@ -28,17 +44,26 @@ opacity: 0; transition: opacity .1s; } + .epic-pill { + @include font-type(light); + @include font-size(xsmall); + background: $grayer; + border-radius: .25rem; + color: $white; + margin: 0 .5rem; + padding: .1rem .25rem; + } .status { cursor: pointer; position: relative; button { background: none; } - .icon { - @include svg-size(.7rem); - fill: $gray-light; - margin-left: .1rem; - } + } + .icon-arrow-down { + @include svg-size(.7rem); + fill: $gray-light; + margin-left: .1rem; } .progress-bar, .progress-status { diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 5e43af45..74e50c1a 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -1,58 +1,11 @@ +@import '../../../../styles/dependencies/mixins/epics-dashboard'; + .epics-table { margin-top: 2rem; } -.epics-table-header, -.epic-row { - .assigned { - padding: .5rem; - } - .project, - .vote, - .status, - .sprint, - .name, - .progress { - padding: 1rem .5rem; - } - - .assigned, - .project, - .vote { - flex-basis: 100px; - flex-grow: 0; - flex-shrink: 0; - flex-wrap: wrap; - text-align: center; - } - .status, - .sprint { - flex-basis: 150px; - flex-grow: 0; - flex-shrink: 0; - flex-wrap: wrap; - max-width: 150px; - } - .name, - .progress { - flex-basis: 20vw; - flex-grow: 1; - flex-shrink: 2; - max-width: 40vw; - } - .name, - .sprint { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - width: 90%; - } - .progress { - position: relative; - } -} - .epics-table-header { + @include epics-table; @include font-type(bold); border-bottom: 1px solid $gray-light; display: flex; diff --git a/app/modules/epics/dashboard/story-row/story-row.controller.coffee b/app/modules/epics/dashboard/story-row/story-row.controller.coffee new file mode 100644 index 00000000..42990d67 --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.controller.coffee @@ -0,0 +1,35 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: epics-table.controller.coffee +### + +module = angular.module("taigaEpics") + +class StoryRowController + @.$inject = [] + + constructor: () -> + @._calculateProgressBar() + + _calculateProgressBar: () -> + tasks = @.story.get('tasks').toJS() + totalTasks = @.story.get('tasks').size + areTasksCompleted = _.map(tasks, 'is_closed') + totalTasksCompleted = _.pull(areTasksCompleted, false).length + @.percentage = totalTasksCompleted * 100 / totalTasks + +module.controller("StoryRowCtrl", StoryRowController) diff --git a/app/modules/epics/dashboard/story-row/story-row.directive.coffee b/app/modules/epics/dashboard/story-row/story-row.directive.coffee new file mode 100644 index 00000000..338f676f --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.directive.coffee @@ -0,0 +1,39 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: epics-table.directive.coffee +### + +module = angular.module('taigaEpics') + +StoryRowDirective = () -> + + return { + templateUrl:"epics/dashboard/story-row/story-row.html", + controller: "StoryRowCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + epic: '=', + story: '=', + project: '=', + column: '=' + } + } + +StoryRowDirective.$inject = [] + +module.directive("tgStoryRow", StoryRowDirective) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade new file mode 100644 index 00000000..39a8dda4 --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -0,0 +1,54 @@ +.story-row( + ng-class="{'is-blocked': vm.story.is_blocked, 'is-closed': vm.story.is_closed}" +) + tg-svg.icon-drag( + svg-icon="icon-drag" + ) + .vote( + ng-if="vm.column.votes" + ng-class="{'is-voter': vm.story.get('is_voter')}" + ) + tg-svg(svg-icon='icon-upvote') + span {{::vm.story.get('total_voters')}} + + .name(ng-if="vm.column.name") + - var hash = "#"; + a( + tg-nav="project-userstories-detail:project=vm.project.slug,ref=vm.story.get('ref')" + ng-attr-title="{{::vm.story.get('subject')}}" + ) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}} + .story-pill(ng-style="::{'background-color': vm.epic.get('color')}") + .project( + ng-if="vm.column.project" + tg-nav="project:project=vm.story.getIn(['project_extra_info', 'slug'])" + ) + img( + tg-project-logo-small-src="::vm.story.get('project_extra_info')" + alt="{{::vm.story.getIn(['project_extra_info', 'name'])}}" + ) + .sprint(ng-if="vm.column.sprint") {{::vm.story.get('milestone_name')}} + .assigned( + ng-if="vm.column.assigned && vm.story.get('assigned_to')" + ) + img( + ng-if="vm.story.getIn(['assigned_to_extra_info', 'photo'])" + ng-src="{{vm.story.getIn(['assigned_to_extra_info', 'photo'])}}" + alt="{{::vm.story.getIn(['assigned_to_extra_info', 'full_name_display'])}}" + ) + img( + ng-if="!vm.story.getIn(['assigned_to_extra_info', 'photo'])" + ng-src="https://www.gravatar.com/avatar/{{vm.story.getIn(['assigned_to_extra_info', 'gravatar_id'])}}" + alt="{{::vm.story.getIn(['assigned_to_extra_info', 'full_name_display'])}}" + ) + .assigned( + ng-if="vm.column.assigned && !vm.story.get('assigned_to')" + ng-class="{'is-unassigned': !vm.story.get('assigned_to')}" + translate="EPICS.DASHBOARD.UNASSIGNED" + ) + .status(ng-if="vm.column.status") {{vm.story.getIn(['status_extra_info', 'name'])}} + .progress(ng-if="vm.column.progress") + .progress-bar + .progress-status( + ng-if="::vm.percentage" + ng-attr-width="::vm.percentage" + ) diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss new file mode 100644 index 00000000..0098b6fd --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -0,0 +1,80 @@ +@import '../../../../styles/dependencies/mixins/epics-dashboard'; + +.story-row { + @include font-size(small); + @include epics-table; + align-items: center; + background: $white; + border-bottom: 1px solid $whitish; + cursor: pointer; + display: flex; + margin-left: 2rem; + transition: background .2s; + &:hover { + background: rgba($primary-light, .05); + .icon-drag { + opacity: 1; + } + } + &.is-blocked { + background: rgba($red-light, .5); + } + &.is-closed { + .name { + color: $gray-light; + text-decoration: line-through; + } + } + .icon-drag { + @include svg-size(.75rem); + cursor: move; + fill: $whitish; + opacity: 0; + transition: opacity .1s; + } + .name { + flex-basis: 18vw; + } + .story-pill { + background: $grayer; + border-radius: 50%; + display: inline-block; + height: .5rem; + margin-left: .25rem; + width: .5rem; + } + .progress-bar, + .progress-status { + height: 1.5rem; + left: 0; + position: absolute; + top: .25rem; + } + .progress-bar { + background: $mass-white; + max-width: 40vw; + width: 100%; + } + .progress-status { + background: $primary-light; + width: 10vw; + } + .vote { + color: $gray; + } + .project, + .assigned { + img { + width: 40px; + } + } + .icon-upvote { + @include svg-size(.75rem); + fill: $gray; + margin-right: .25rem; + vertical-align: middle; + } + .is-unassigned { + color: $gray-light; + } +} diff --git a/app/modules/resources/userstories-resource.service.coffee b/app/modules/resources/userstories-resource.service.coffee index ce2b7cd4..f5b13f79 100644 --- a/app/modules/resources/userstories-resource.service.coffee +++ b/app/modules/resources/userstories-resource.service.coffee @@ -33,6 +33,18 @@ Resource = (urlsService, http) -> .then (result) -> return Immutable.fromJS(result.data) + service.listInEpics = (ids) -> + url = urlsService.resolve("userstories") + + params = { + 'epics': ids, + 'include_tasks': true + } + + return http.get(url, params) + .then (result) -> + return Immutable.fromJS(result.data) + return () -> return {"userstories": service} diff --git a/app/styles/dependencies/mixins/epics-dashboard.scss b/app/styles/dependencies/mixins/epics-dashboard.scss new file mode 100644 index 00000000..b472653d --- /dev/null +++ b/app/styles/dependencies/mixins/epics-dashboard.scss @@ -0,0 +1,57 @@ +@mixin epics-table { + .assigned { + padding: .5rem; + } + .project, + .vote, + .status, + .sprint, + .name, + .progress { + padding: 1rem .5rem; + } + .vote { + flex-basis: 60px; + flex-grow: 0; + flex-shrink: 0; + flex-wrap: wrap; + text-align: center; + } + .assigned, + .project { + flex-basis: 100px; + flex-grow: 0; + flex-shrink: 0; + flex-wrap: wrap; + text-align: center; + } + .status, + .sprint { + flex-basis: 150px; + flex-grow: 0; + flex-shrink: 0; + flex-wrap: wrap; + max-width: 150px; + text-align: center; + } + .name, + .progress { + flex-basis: 20vw; + flex-grow: 1; + flex-shrink: 1; + max-width: 40vw; + } + .progress { + flex-shrink: 3; + } + .name, + .sprint { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 90%; + } + .progress { + position: relative; + } +} From 99683684d68b9d8dac5673274416c780c16fbde9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 27 Jul 2016 16:55:42 +0200 Subject: [PATCH 154/315] Empty epics --- app/images/epics-empty.png | Bin 0 -> 3573 bytes app/locales/taiga/locale-en.json | 5 ++++ .../epic-row/epic-row.controller.coffee | 1 - .../epics-dashboard.controller.coffee | 9 +++++- .../epics/dashboard/epics-dashboard.jade | 24 ++++++++++++++-- .../epics/dashboard/epics-dashboard.scss | 26 ++++++++++++++++++ .../epics-table/epics-table.controller.coffee | 12 ++------ .../epics-table/epics-table.directive.coffee | 1 + 8 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 app/images/epics-empty.png create mode 100644 app/modules/epics/dashboard/epics-dashboard.scss diff --git a/app/images/epics-empty.png b/app/images/epics-empty.png new file mode 100644 index 0000000000000000000000000000000000000000..28363733acda0918a81fc6e69e7cc7ca99045647 GIT binary patch literal 3573 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 v4`8#|u(-HLJa>6{xvO literal 0 HcmV?d00001 diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 012bb8a5..15c3bf2e 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -398,6 +398,11 @@ "ADD": "+ ADD EPIC", "UNASSIGNED": "Unassigned" }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, "TABLE": { "VOTES": "Votes", "NAME": "Name", diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index cb1e5989..c1f034b2 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -59,7 +59,6 @@ class EpicRowController @.epicStories = data console.log @.epicStories.toJS() @.displayUserStories = true - @confirm.notify('success') onError = (data) => @confirm.notify('error') diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 91fb2ecd..477c288e 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -22,11 +22,12 @@ module = angular.module("taigaEpics") class EpicsDashboardController @.$inject = [ "$tgResources", + "tgResources", "$routeParams", "tgErrorHandlingService" ] - constructor: (@rs, @params, @errorHandlingService) -> + constructor: (@rs, @resources, @params, @errorHandlingService) -> @.sectionName = "Epics" @._loadProject() @@ -35,6 +36,12 @@ class EpicsDashboardController if not project.is_epics_activated @errorHandlingService.permissionDenied() @.project = project + @._loadEpics() + + _loadEpics: () -> + projectId = @.project.id + return @resources.epics.list(projectId).then (epics) => + @.epics = epics addNewEpic: () -> console.log 'Add new Epic' diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 38b45593..788cbdce 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -9,7 +9,7 @@ doctype html project-name="vm.project.name" i18n-section-name="{{ vm.sectionName }}" ) - .action-buttons + .action-buttons(ng-if="vm.epics.size") button.button-green( translate="EPICS.DASHBOARD.ADD" title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", @@ -17,6 +17,26 @@ doctype html ) tg-epics-table( - ng-if="vm.project" + ng-if="vm.project && vm.epics.size" project="vm.project" + epics="vm.epics" ) + + section.empty-epics(ng-if="!vm.epics.size") + img( + src="/#{v}/images/epics-empty.png" + ng-title="EPICS.EMPTY.HELP | translate" + ) + h1.title(translate="EPICS.EMPTY.TITLE") + p(translate="EPICS.EMPTY.EXPLANATION") + a( + translate="EPICS.EMPTY.HELP" + href="https://tree.taiga.io/support/frequently-asked-questions/who-is-taiga-for/" + target="_blank" + ng-title="EPICS.EMPTY.HELP | translate" + ) + button.create-epic.button-green( + translate="EPICS.DASHBOARD.ADD" + title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", + ng-click="vm.addNewEpic()" + ) diff --git a/app/modules/epics/dashboard/epics-dashboard.scss b/app/modules/epics/dashboard/epics-dashboard.scss new file mode 100644 index 00000000..b52027dc --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.scss @@ -0,0 +1,26 @@ +.empty-epics { + margin: 0 auto; + padding: 5vh; + text-align: center; + width: 650px; + .title { + @include font-type(normal); + @include font-size(larger); + color: $gray-light; + margin-bottom: .5rem; + text-transform: none; + } + img { + margin: 2rem auto; + text-align: center; + width: 6rem; + } + p { + color: $gray-light; + } + a { + color: $primary; + display: block; + margin-bottom: 2rem; + } +} diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee index fb22e4ed..6f02f06f 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -20,11 +20,9 @@ module = angular.module("taigaEpics") class EpicsTableController - @.$inject = [ - "tgResources" - ] + @.$inject = [] - constructor: (@rs) -> + constructor: () -> @.displayOptions = false @.displayVotes = true @.column = { @@ -36,7 +34,6 @@ class EpicsTableController status: true, progress: true } - @.loadEpics() @._checkPermissions() toggleEpicTableOptions: () -> @@ -47,11 +44,6 @@ class EpicsTableController canEdit: _.includes(@.project.my_permissions, 'modify_epic') } - loadEpics: () -> - projectId = @.project.id - promise = @rs.epics.list(projectId).then (epics) => - @.epics = epics - reorderEpics: (epic, index) -> console.log epic, index diff --git a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee index 39c75a8f..bc4793cb 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -27,6 +27,7 @@ EpicsTableDirective = () -> controllerAs: "vm", bindToController: true, scope: { + epics: "=" project: "=" } } From f973b752a435848a7056cebcd1aab6d630b3fad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 27 Jul 2016 17:41:16 +0200 Subject: [PATCH 155/315] Epics unassigned --- app/modules/epics/dashboard/epic-row/epic-row.jade | 5 ++++- app/modules/epics/dashboard/epics-table/epics-table.scss | 1 + app/modules/epics/dashboard/story-row/story-row.jade | 5 ++++- app/styles/dependencies/mixins/epics-dashboard.scss | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 1eadbc19..c5eaab92 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -47,8 +47,11 @@ .assigned( ng-if="vm.column.assigned && !vm.epic.get('assigned_to')" ng-class="{'is-unassigned': !vm.epic.get('assigned_to')}" - translate="EPICS.DASHBOARD.UNASSIGNED" ) + img( + src="/#{v}/images/unnamed.png" + alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + ) .status( ng-if="vm.column.status && !vm.permissions.canEdit" ) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 74e50c1a..2651dde4 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -11,6 +11,7 @@ display: flex; padding: .5rem; position: relative; + .project, .assigned { padding: 1rem .5rem; } diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 39a8dda4..05e93e09 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -43,8 +43,11 @@ .assigned( ng-if="vm.column.assigned && !vm.story.get('assigned_to')" ng-class="{'is-unassigned': !vm.story.get('assigned_to')}" - translate="EPICS.DASHBOARD.UNASSIGNED" ) + img( + src="/#{v}/images/unnamed.png" + alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + ) .status(ng-if="vm.column.status") {{vm.story.getIn(['status_extra_info', 'name'])}} .progress(ng-if="vm.column.progress") .progress-bar diff --git a/app/styles/dependencies/mixins/epics-dashboard.scss b/app/styles/dependencies/mixins/epics-dashboard.scss index b472653d..6465fda3 100644 --- a/app/styles/dependencies/mixins/epics-dashboard.scss +++ b/app/styles/dependencies/mixins/epics-dashboard.scss @@ -1,8 +1,8 @@ @mixin epics-table { + .project, .assigned { padding: .5rem; } - .project, .vote, .status, .sprint, From 04103e6120f972b9c208622bd9799ec65f5262aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 27 Jul 2016 19:25:52 +0200 Subject: [PATCH 156/315] Now user stories inside epics are sorted --- .../epics/dashboard/epic-row/epic-row.controller.coffee | 3 +-- app/modules/resources/userstories-resource.service.coffee | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index c1f034b2..4d6eb592 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -57,13 +57,12 @@ class EpicRowController onSuccess = (data) => @.epicStories = data - console.log @.epicStories.toJS() @.displayUserStories = true onError = (data) => @confirm.notify('error') - return @rs.userstories.listInEpics(id).then(onSuccess, onError) + return @rs.userstories.listInEpic(id).then(onSuccess, onError) else @.displayUserStories = false diff --git a/app/modules/resources/userstories-resource.service.coffee b/app/modules/resources/userstories-resource.service.coffee index f5b13f79..7b7f9b90 100644 --- a/app/modules/resources/userstories-resource.service.coffee +++ b/app/modules/resources/userstories-resource.service.coffee @@ -33,11 +33,12 @@ Resource = (urlsService, http) -> .then (result) -> return Immutable.fromJS(result.data) - service.listInEpics = (ids) -> + service.listInEpic = (epicIid) -> url = urlsService.resolve("userstories") params = { - 'epics': ids, + 'epic': epicIid, + 'order_by': 'epic_order', 'include_tasks': true } From f60b4119346fbe3c18c2f074171b2fbe37e004ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 28 Jul 2016 11:48:42 +0200 Subject: [PATCH 157/315] Display Stories style fixes --- .../epics/dashboard/epic-row/epic-row.controller.coffee | 3 +++ app/modules/epics/dashboard/epic-row/epic-row.jade | 2 ++ .../epics/dashboard/story-row/story-row.controller.coffee | 3 +++ app/modules/epics/dashboard/story-row/story-row.jade | 2 ++ app/modules/epics/dashboard/story-row/story-row.scss | 4 ++-- 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index 4d6eb592..221d6088 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -66,4 +66,7 @@ class EpicRowController else @.displayUserStories = false + onSelectAssignedTo: () -> + console.log 'onSelectAssignedTo' + module.controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index c5eaab92..d0eab6a2 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -33,6 +33,7 @@ ) .assigned( ng-if="vm.column.assigned && vm.epic.get('assigned_to')" + ng-click="vm.onSelectAssignedTo()" ) img( ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" @@ -47,6 +48,7 @@ .assigned( ng-if="vm.column.assigned && !vm.epic.get('assigned_to')" ng-class="{'is-unassigned': !vm.epic.get('assigned_to')}" + ng-click="vm.onSelectAssignedTo()" ) img( src="/#{v}/images/unnamed.png" diff --git a/app/modules/epics/dashboard/story-row/story-row.controller.coffee b/app/modules/epics/dashboard/story-row/story-row.controller.coffee index 42990d67..d2b2f68b 100644 --- a/app/modules/epics/dashboard/story-row/story-row.controller.coffee +++ b/app/modules/epics/dashboard/story-row/story-row.controller.coffee @@ -32,4 +32,7 @@ class StoryRowController totalTasksCompleted = _.pull(areTasksCompleted, false).length @.percentage = totalTasksCompleted * 100 / totalTasks + onSelectAssignedTo: () -> + console.log 'ng-click="vm.onSelectAssignedTo()"' + module.controller("StoryRowCtrl", StoryRowController) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 05e93e09..379e917b 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -29,6 +29,7 @@ .sprint(ng-if="vm.column.sprint") {{::vm.story.get('milestone_name')}} .assigned( ng-if="vm.column.assigned && vm.story.get('assigned_to')" + ng-click="vm.onSelectAssignedTo()" ) img( ng-if="vm.story.getIn(['assigned_to_extra_info', 'photo'])" @@ -43,6 +44,7 @@ .assigned( ng-if="vm.column.assigned && !vm.story.get('assigned_to')" ng-class="{'is-unassigned': !vm.story.get('assigned_to')}" + ng-click="vm.onSelectAssignedTo()" ) img( src="/#{v}/images/unnamed.png" diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index 0098b6fd..ec31c14d 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -8,7 +8,7 @@ border-bottom: 1px solid $whitish; cursor: pointer; display: flex; - margin-left: 2rem; + margin-left: 5rem; transition: background .2s; &:hover { background: rgba($primary-light, .05); @@ -33,7 +33,7 @@ transition: opacity .1s; } .name { - flex-basis: 18vw; + flex-basis: 16vw; } .story-pill { background: $grayer; From f5db7a7a4055359a3c16fe94aa75082de6666f13 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 28 Jul 2016 12:01:30 +0200 Subject: [PATCH 158/315] search epics --- app/coffee/modules/base.coffee | 1 + app/coffee/modules/common/components.coffee | 10 ++++++++++ app/coffee/modules/search.coffee | 5 ++++- app/locales/taiga/locale-en.json | 1 + .../includes/modules/search-filter.jade | 9 +++++++++ .../includes/modules/search-result-table.jade | 19 +++++++++++++++++++ 6 files changed, 44 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 70d836c2..708dd70e 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -76,6 +76,7 @@ urls = { "project-epics": "/project/:project/epics" "project-search": "/project/:project/search" + "project-epic-detail": "/project/:project/epic/:ref" "project-userstories-detail": "/project/:project/us/:ref" "project-tasks-detail": "/project/:project/task/:ref" "project-issues-detail": "/project/:project/issue/:ref" diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index a358dde4..a6f4fffb 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -765,6 +765,16 @@ module.directive("tgEditableWysiwyg", ["tgAttachmentsService", "tgAttachmentsFul ## completely bindonce, they only serves for visualization of data. ############################################################################# +ListItemEpicStatusDirective = -> + link = ($scope, $el, $attrs) -> + epic = $scope.$eval($attrs.tgListitemEpicStatus) + bindOnce $scope, "epicStatusById", (epicStatusById) -> + $el.html(epicStatusById[epic.status].name) + + return {link:link} + +module.directive("tgListitemEpicStatus", ListItemEpicStatusDirective) + ListItemUsStatusDirective = -> link = ($scope, $el, $attrs) -> us = $scope.$eval($attrs.tgListitemUsStatus) diff --git a/app/coffee/modules/search.coffee b/app/coffee/modules/search.coffee index 895a7d55..37cf51bc 100644 --- a/app/coffee/modules/search.coffee +++ b/app/coffee/modules/search.coffee @@ -88,6 +88,8 @@ class SearchController extends mixOf(taiga.Controller, taiga.PageMixin) return @rs.projects.getBySlug(@params.pslug).then (project) => @scope.project = project @scope.$emit('project:loaded', project) + + @scope.epicStatusById = groupBy(project.epic_statuses, (x) -> x.id) @scope.issueStatusById = groupBy(project.issue_statuses, (x) -> x.id) @scope.taskStatusById = groupBy(project.task_statuses, (x) -> x.id) @scope.severityById = groupBy(project.severities, (x) -> x.id) @@ -194,7 +196,7 @@ SearchDirective = ($log, $compile, $templatecache, $routeparams, $location) -> return selectedSection if data - for name in ["userstories", "issues", "tasks", "wikipages"] + for name in ["userstories", "epics", "issues", "tasks", "wikipages"] value = data[name] if value.length > maxVal @@ -222,6 +224,7 @@ SearchDirective = ($log, $compile, $templatecache, $routeparams, $location) -> activeSectionName = section.name templates = { + epics: $templatecache.get("search-epics") issues: $templatecache.get("search-issues") tasks: $templatecache.get("search-tasks") userstories: $templatecache.get("search-userstories") diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 15c3bf2e..3d8eff98 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1388,6 +1388,7 @@ "SEARCH": { "PAGE_TITLE": "Search - {{projectName}}", "PAGE_DESCRIPTION": "Search anything, user stories, issues, tasks or wiki pages, in the project {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "User Stories", "FILTER_ISSUES": "Issues", "FILTER_TASKS": "Tasks", diff --git a/app/partials/includes/modules/search-filter.jade b/app/partials/includes/modules/search-filter.jade index 2cf10b90..9f7834a5 100644 --- a/app/partials/includes/modules/search-filter.jade +++ b/app/partials/includes/modules/search-filter.jade @@ -1,4 +1,13 @@ ul.search-filter + li.epics(data-name="epics") + a( + href="#" + title="{{ 'SEARCH.FILTER_EPICS' | translate }}" + ) + tg-svg(svg-icon="icon-epics") + span.num + span.name(translate="SEARCH.FILTER_EPICS") + li.userstories(data-name="userstories") a.active( href="#" diff --git a/app/partials/includes/modules/search-result-table.jade b/app/partials/includes/modules/search-result-table.jade index 4b5df259..da3ac97b 100644 --- a/app/partials/includes/modules/search-result-table.jade +++ b/app/partials/includes/modules/search-result-table.jade @@ -21,6 +21,25 @@ script(type="text/ng-template", id="search-issues") div.empty-large(ng-class="{'hidden': issues.length}") include ../components/empty-search-results +script(type="text/ng-template", id="search-epics") + div.search-result-table-container(ng-class="{'hidden': !epics.length}", tg-bind-scope) + div.search-result-table-header + div.row.title + div.ref(translate="COMMON.FIELDS.REF") + div.user-stories(translate="SEARCH.FILTER_EPICS") + div.status(translate="COMMON.FIELDS.STATUS") + div.search-result-table-body + div.row.table-main(ng-repeat="epic in epics track by epic.id") + div.ref(tg-bo-ref="epic.ref") + div.user-stories + div.user-story-name + a(href="", tg-nav="project-epic-detail:project=project.slug,ref=epic.ref", + tg-bo-bind="epic.subject") + div.status(tg-listitem-epic-status="epic") + + div.empty-search-results(ng-class="{'hidden': epics.length}") + include ../components/empty-search-results + script(type="text/ng-template", id="search-userstories") div.search-result-table-container(ng-class="{'hidden': !userstories.length}", tg-bind-scope) From 01b5717783ed22fea81a334b9104cf7576e7dbcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 28 Jul 2016 13:12:37 +0200 Subject: [PATCH 159/315] Add depending epics --- .../epics/dashboard/story-row/story-row.jade | 4 ++- .../epics/dashboard/story-row/story-row.scss | 28 +++++++++++++++++-- .../dependencies/mixins/epics-dashboard.scss | 1 - 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 379e917b..c851e7b2 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -17,7 +17,9 @@ tg-nav="project-userstories-detail:project=vm.project.slug,ref=vm.story.get('ref')" ng-attr-title="{{::vm.story.get('subject')}}" ) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}} - .story-pill(ng-style="::{'background-color': vm.epic.get('color')}") + .story-pill-wrapper(tg-repeat="pill in vm.story.get('epics')") + .story-pill(ng-style="{'background': pill.get('color')}") + .story-pill-data #{hash}{{pill.get('id')}} {{pill.get('subject')}} .project( ng-if="vm.column.project" tg-nav="project:project=vm.story.getIn(['project_extra_info', 'slug'])" diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index ec31c14d..320e93f0 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -35,13 +35,35 @@ .name { flex-basis: 16vw; } + .story-pill-wrapper { + display: inline-block; + position: relative; + &:hover { + .story-pill-data { + display: block; + } + } + } .story-pill { - background: $grayer; + background-color: $grayer; border-radius: 50%; display: inline-block; - height: .5rem; + height: .75rem; margin-left: .25rem; - width: .5rem; + position: relative; + width: .75rem; + } + .story-pill-data { + animation: dropdownFade .2s; + background: rgba($black, .9); + bottom: 1.25rem; + color: $white; + display: none; + left: -100px; + padding: .5rem 1rem; + position: absolute; + width: 200px; + z-index: 99; } .progress-bar, .progress-status { diff --git a/app/styles/dependencies/mixins/epics-dashboard.scss b/app/styles/dependencies/mixins/epics-dashboard.scss index 6465fda3..c7e83fa4 100644 --- a/app/styles/dependencies/mixins/epics-dashboard.scss +++ b/app/styles/dependencies/mixins/epics-dashboard.scss @@ -44,7 +44,6 @@ .progress { flex-shrink: 3; } - .name, .sprint { overflow: hidden; text-overflow: ellipsis; From ebe8c159f6b426b2c785e83c46c5f23fc61dafde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 28 Jul 2016 16:16:50 +0200 Subject: [PATCH 160/315] Create Epic --- app/locales/taiga/locale-en.json | 8 ++ .../create-epic/create-epic.controller.coffee | 38 ++++++++ .../create-epic/create-epic.directive.coffee | 37 ++++++++ .../epics/create-epic/create-epic.jade | 90 +++++++++++++++++++ .../epics/create-epic/create-epic.scss | 67 ++++++++++++++ .../epic-row/epic-row.controller.coffee | 3 +- .../epics-dashboard.controller.coffee | 17 +++- .../epics/dashboard/epics-dashboard.jade | 8 +- .../resources/epics-resource.service.coffee | 5 ++ 9 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 app/modules/epics/create-epic/create-epic.controller.coffee create mode 100644 app/modules/epics/create-epic/create-epic.directive.coffee create mode 100644 app/modules/epics/create-epic/create-epic.jade create mode 100644 app/modules/epics/create-epic/create-epic.scss diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 3d8eff98..dc285a85 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -412,6 +412,14 @@ "STATUS": "Status", "PROGRESS": "Progress", "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Blocked", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" } }, "PROJECTS": { diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee new file mode 100644 index 00000000..ea0f1b3e --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -0,0 +1,38 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: create-epic.controller.coffee +### + +module = angular.module("taigaEpics") + +class CreateEpicController + @.$inject = [ + "tgResources", + "$tgConfirm", + ] + + constructor: (@rs, @confirm) -> + @.attachments = Immutable.List() + + createEpic: () -> + @.newEpic.project = @.project.id + return @rs.epics.post(@.newEpic).then () => + @confirm.notify("success") + @.onReloadEpics() + + +module.controller("CreateEpicCtrl", CreateEpicController) diff --git a/app/modules/epics/create-epic/create-epic.directive.coffee b/app/modules/epics/create-epic/create-epic.directive.coffee new file mode 100644 index 00000000..6fd7883a --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.directive.coffee @@ -0,0 +1,37 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: create-epic.directive.coffee +### + +module = angular.module('taigaEpics') + +CreateEpicDirective = () -> + + return { + templateUrl:"epics/create-epic/create-epic.html", + controller: "CreateEpicCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + project: '=', + onReloadEpics: '&' + } + } + +CreateEpicDirective.$inject = [] + +module.directive("tgCreateEpic", CreateEpicDirective) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade new file mode 100644 index 00000000..645ca696 --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.jade @@ -0,0 +1,90 @@ +tg-lightbox-close + +.create-epic-container + h2.title(translate="EPICS.CREATE.TITLE") + form( + ng-submit="vm.createEpic()" + ) + fieldset + input( + type="text" + name="subject" + maxlength="140" + ng-model="vm.newEpic.subject" + tg-auto-select + placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}" + required + ) + fieldset + select( + id="epic-status" + name="status" + ng-model="vm.newEpic.status" + ) + option( + ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" + ng-value="status.id" + ng-selected="vm.project.default_epic_status" + ) {{status.name}} + fieldset.tags-block( + tg-lb-tag-line + ng-model="vm.newEpic.tags" + ) + fieldset + textarea( + ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}" + ng-model="vm.newEpic.description" + ) + fieldset + tg-attachments-simple( + attachments="vm.attachments" + ) + .settings + fieldset.team-requirement + input( + type="checkbox" + name="team_requirement" + ng-model="vm.newEpic.teamRequirement" + id="team-requirement" + ) + label.requirement.trans-button( + for="team-requirement" + translate="EPICS.CREATE.TEAM_REQUIREMENT" + ) + fieldset.client-requirement + input( + type="checkbox" + name="client_requirement" + ng-model="vm.newEpic.clientRequirement" + id="client-requirement" + ) + label.requirement.trans-button( + for="client-requirement" + translate="EPICS.CREATE.CLIENT_REQUIREMENT" + ) + fieldset + input( + type="checkbox" + name="blocked" + ng-model="vm.newEpic.isBlocked" + id="blocked" + ng-click="displayBlockedReason = !displayBlockedReason" + ) + label.requirement.trans-button.blocked( + for="blocked" + translate="EPICS.CREATE.BLOCKED" + ) + fieldset(ng-if="displayBlockedReason") + input( + type="text" + name="blocked_note" + maxlength="140" + ng-model="vm.newEpic.blocked_note" + placeholder="{{'EPICS.CREATE.BLOCKED_NOTE_PLACEHOLDER' | translate}}" + ) + fieldset + input.button-green.create-epic-button( + type="submit" + translate="EPICS.CREATE.CREATE_EPIC" + ) + diff --git a/app/modules/epics/create-epic/create-epic.scss b/app/modules/epics/create-epic/create-epic.scss new file mode 100644 index 00000000..ad2090c4 --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.scss @@ -0,0 +1,67 @@ +.lightbox-create-epic { + align-items: center; + display: flex; + justify-content: center; + opacity: 1; + .create-epic-container { + max-width: 700px; + width: 90%; + } + .attachments { + margin-bottom: 0; + } + .settings { + display: flex; + justify-content: center; + fieldset { + margin-right: .5rem; + &:hover { + color: $white; + transition: all .2s ease-in; + transition-delay: .2s; + } + &:last-child { + margin: 0; + } + } + input { + display: none; + &:checked+label { + background: $primary; + border: 1px solid $primary; + color: $white; + } + &:checked+.blocked { + background: $red; + border: 1px solid $red; + color: $white; + } + } + } + label { + @include font-size(small); + background: $mass-white; + border: 1px solid $gray-light; + color: $gray-light; + cursor: pointer; + display: block; + padding: .5rem 3rem; + text-transform: none; + transition: all .2s ease-in; + &:hover { + background: $primary-light; + border: 1px solid $primary; + color: $white; + } + &.blocked { + &:hover { + background: $red-light; + border: 1px solid $red; + } + } + } + .create-epic-button { + display: block; + width: 100%; + } +} diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index 221d6088..ba338e5b 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -28,6 +28,7 @@ class EpicRowController constructor: (@rs, @confirm) -> @.displayUserStories = false @._calculateProgressBar() + @.displayAssignedTo = false _calculateProgressBar: () -> totalUs = @.epic.getIn(['user_stories_counts', 'closed']) @@ -67,6 +68,6 @@ class EpicRowController @.displayUserStories = false onSelectAssignedTo: () -> - console.log 'onSelectAssignedTo' + console.log 'Assigned to' module.controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 477c288e..90066000 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -24,12 +24,14 @@ class EpicsDashboardController "$tgResources", "tgResources", "$routeParams", - "tgErrorHandlingService" + "tgErrorHandlingService", + "tgLightboxFactory", ] - constructor: (@rs, @resources, @params, @errorHandlingService) -> + constructor: (@rs, @resources, @params, @errorHandlingService, @lightboxFactory) -> @.sectionName = "Epics" @._loadProject() + @.createEpic = false _loadProject: () -> return @rs.projects.getBySlug(@params.pslug).then (project) => @@ -43,7 +45,14 @@ class EpicsDashboardController return @resources.epics.list(projectId).then (epics) => @.epics = epics - addNewEpic: () -> - console.log 'Add new Epic' + onCreateEpic: () -> + @lightboxFactory.create('tg-create-epic', { + "class": "lightbox lightbox-create-epic" + "project": "project" + "on-reload-epics": "onReloadEpics" + }, { + "project": @.project + "onReloadEpics": @_loadEpics + }) module.controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 788cbdce..c2f22f05 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -1,5 +1,3 @@ -doctype html - .wrapper() tg-project-menu section.main(role="main") @@ -13,7 +11,7 @@ doctype html button.button-green( translate="EPICS.DASHBOARD.ADD" title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", - ng-click="vm.addNewEpic()" + ng-click="vm.onCreateEpic()" ) tg-epics-table( @@ -37,6 +35,6 @@ doctype html ) button.create-epic.button-green( translate="EPICS.DASHBOARD.ADD" - title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", - ng-click="vm.addNewEpic()" + title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}" + ng-click="vm.onCreateEpic()" ) diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index ddc4fe40..ed06dcb9 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -41,6 +41,11 @@ Resource = (urlsService, http) -> return http.patch(url, patch) + service.post = (params) -> + url = urlsService.resolve("epics") + + return http.post(url, params) + return () -> return {"epics": service} From 9423912ccdbb754d409b8c51032bfbc1a917f77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 29 Jul 2016 18:34:39 +0200 Subject: [PATCH 161/315] Show animals avatars --- app/modules/epics/dashboard/epic-row/epic-row.jade | 8 +------- app/modules/epics/dashboard/story-row/story-row.jade | 8 +------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index d0eab6a2..b62a32e6 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -36,13 +36,7 @@ ng-click="vm.onSelectAssignedTo()" ) img( - ng-if="vm.epic.getIn(['assigned_to_extra_info', 'photo'])" - ng-src="{{vm.epic.getIn(['assigned_to_extra_info', 'photo'])}}" - alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" - ) - img( - ng-if="!vm.epic.getIn(['assigned_to_extra_info', 'photo'])" - ng-src="https://www.gravatar.com/avatar/{{vm.epic.getIn(['assigned_to_extra_info', 'gravatar_id'])}}" + tg-avatar="vm.epic.get('assigned_to_extra_info')" alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" ) .assigned( diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index c851e7b2..fefac319 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -34,13 +34,7 @@ ng-click="vm.onSelectAssignedTo()" ) img( - ng-if="vm.story.getIn(['assigned_to_extra_info', 'photo'])" - ng-src="{{vm.story.getIn(['assigned_to_extra_info', 'photo'])}}" - alt="{{::vm.story.getIn(['assigned_to_extra_info', 'full_name_display'])}}" - ) - img( - ng-if="!vm.story.getIn(['assigned_to_extra_info', 'photo'])" - ng-src="https://www.gravatar.com/avatar/{{vm.story.getIn(['assigned_to_extra_info', 'gravatar_id'])}}" + tg-avatar="vm.story.get('assigned_to_extra_info')" alt="{{::vm.story.getIn(['assigned_to_extra_info', 'full_name_display'])}}" ) .assigned( From 209e33b6477d78628c0596175ef366c3a97da0fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 1 Aug 2016 10:42:08 +0200 Subject: [PATCH 162/315] Update epics list on create --- .../create-epic/create-epic.controller.coffee | 7 ++----- .../dashboard/epics-dashboard.controller.coffee | 15 +++++++++++---- .../epics/dashboard/story-row/story-row.jade | 3 --- .../epics/dashboard/story-row/story-row.scss | 14 ++------------ 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index ea0f1b3e..c80ef89c 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -21,18 +21,15 @@ module = angular.module("taigaEpics") class CreateEpicController @.$inject = [ - "tgResources", - "$tgConfirm", + "tgResources" ] - constructor: (@rs, @confirm) -> + constructor: (@rs) -> @.attachments = Immutable.List() createEpic: () -> @.newEpic.project = @.project.id return @rs.epics.post(@.newEpic).then () => - @confirm.notify("success") @.onReloadEpics() - module.controller("CreateEpicCtrl", CreateEpicController) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 90066000..1c9d8b6e 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -26,9 +26,11 @@ class EpicsDashboardController "$routeParams", "tgErrorHandlingService", "tgLightboxFactory", + "lightboxService", + "$tgConfirm" ] - constructor: (@rs, @resources, @params, @errorHandlingService, @lightboxFactory) -> + constructor: (@rs, @resources, @params, @errorHandlingService, @lightboxFactory, @lightboxService, @confirm) -> @.sectionName = "Epics" @._loadProject() @.createEpic = false @@ -45,14 +47,19 @@ class EpicsDashboardController return @resources.epics.list(projectId).then (epics) => @.epics = epics + _onCreateEpic: () -> + @lightboxService.closeAll() + @confirm.notify("success") + @._loadEpics() + onCreateEpic: () -> @lightboxFactory.create('tg-create-epic', { - "class": "lightbox lightbox-create-epic" + "class": "lightbox lightbox-create-epic open" "project": "project" - "on-reload-epics": "onReloadEpics" + "on-reload-epics": "reloadEpics()" }, { "project": @.project - "onReloadEpics": @_loadEpics + "reloadEpics": @._onCreateEpic.bind(this) }) module.controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index fefac319..9c8c513a 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -1,9 +1,6 @@ .story-row( ng-class="{'is-blocked': vm.story.is_blocked, 'is-closed': vm.story.is_closed}" ) - tg-svg.icon-drag( - svg-icon="icon-drag" - ) .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.story.get('is_voter')}" diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index 320e93f0..4eaf597e 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -8,13 +8,10 @@ border-bottom: 1px solid $whitish; cursor: pointer; display: flex; - margin-left: 5rem; + margin-left: 4rem; transition: background .2s; &:hover { background: rgba($primary-light, .05); - .icon-drag { - opacity: 1; - } } &.is-blocked { background: rgba($red-light, .5); @@ -25,15 +22,8 @@ text-decoration: line-through; } } - .icon-drag { - @include svg-size(.75rem); - cursor: move; - fill: $whitish; - opacity: 0; - transition: opacity .1s; - } .name { - flex-basis: 16vw; + flex-basis: 17.5vw; } .story-pill-wrapper { display: inline-block; From 8e23148920302a3b3e13e20372a7df2908ca3db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 1 Aug 2016 13:13:56 +0200 Subject: [PATCH 163/315] Fix create form and progress bars --- app/modules/epics/create-epic/create-epic.jade | 8 ++++---- .../dashboard/epic-row/epic-row.controller.coffee | 13 ++++++++++--- app/modules/epics/dashboard/epic-row/epic-row.jade | 2 +- .../dashboard/story-row/story-row.controller.coffee | 13 ++++++++----- .../epics/dashboard/story-row/story-row.jade | 6 +++--- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 645ca696..44367e04 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -44,7 +44,7 @@ tg-lightbox-close input( type="checkbox" name="team_requirement" - ng-model="vm.newEpic.teamRequirement" + ng-model="vm.newEpic.team_requirement" id="team-requirement" ) label.requirement.trans-button( @@ -55,7 +55,7 @@ tg-lightbox-close input( type="checkbox" name="client_requirement" - ng-model="vm.newEpic.clientRequirement" + ng-model="vm.newEpic.client_requirement" id="client-requirement" ) label.requirement.trans-button( @@ -66,7 +66,7 @@ tg-lightbox-close input( type="checkbox" name="blocked" - ng-model="vm.newEpic.isBlocked" + ng-model="vm.newEpic.is_blocked" id="blocked" ng-click="displayBlockedReason = !displayBlockedReason" ) @@ -87,4 +87,4 @@ tg-lightbox-close type="submit" translate="EPICS.CREATE.CREATE_EPIC" ) - + diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index ba338e5b..d840e567 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -31,9 +31,16 @@ class EpicRowController @.displayAssignedTo = false _calculateProgressBar: () -> - totalUs = @.epic.getIn(['user_stories_counts', 'closed']) - totalUsCompleted = @.epic.getIn(['user_stories_counts', 'opened']) - @.percentage = totalUs * 100 / totalUsCompleted + if @.epic.getIn(['status_extra_info', 'is_closed']) == true + @.percentage = "100%" + else + opened = @.epic.getIn(['user_stories_counts', 'opened']) + closed = @.epic.getIn(['user_stories_counts', 'closed']) + total = opened + closed + if total == 0 + @.percentage = "0%" + else + @.percentage = "#{closed * 100 / total}%" updateEpicStatus: (status) -> id = @.epic.get('id') diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index b62a32e6..c7c9d94a 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -74,7 +74,7 @@ .progress-bar .progress-status( ng-if="::vm.percentage" - ng-attr-width="::vm.percentage" + ng-style="{'width':vm.percentage}" ) .epic-stories-wrapper(ng-if="vm.displayUserStories && vm.epicStories") diff --git a/app/modules/epics/dashboard/story-row/story-row.controller.coffee b/app/modules/epics/dashboard/story-row/story-row.controller.coffee index d2b2f68b..e93eca79 100644 --- a/app/modules/epics/dashboard/story-row/story-row.controller.coffee +++ b/app/modules/epics/dashboard/story-row/story-row.controller.coffee @@ -26,11 +26,14 @@ class StoryRowController @._calculateProgressBar() _calculateProgressBar: () -> - tasks = @.story.get('tasks').toJS() - totalTasks = @.story.get('tasks').size - areTasksCompleted = _.map(tasks, 'is_closed') - totalTasksCompleted = _.pull(areTasksCompleted, false).length - @.percentage = totalTasksCompleted * 100 / totalTasks + if @.story.get('is_closed') == true + @.percentage = "100%" + else + tasks = @.story.get('tasks').toJS() + totalTasks = @.story.get('tasks').size + areTasksCompleted = _.map(tasks, 'is_closed') + totalTasksCompleted = _.pull(areTasksCompleted, false).length + @.percentage = "#{totalTasksCompleted * 100 / totalTasks}%" onSelectAssignedTo: () -> console.log 'ng-click="vm.onSelectAssignedTo()"' diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 9c8c513a..0783c479 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -4,10 +4,10 @@ .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.story.get('is_voter')}" - ) + ) tg-svg(svg-icon='icon-upvote') span {{::vm.story.get('total_voters')}} - + .name(ng-if="vm.column.name") - var hash = "#"; a( @@ -48,5 +48,5 @@ .progress-bar .progress-status( ng-if="::vm.percentage" - ng-attr-width="::vm.percentage" + ng-style="{'width':vm.percentage}" ) From 3e5ab894a650f35609b142028ce3e460853238bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 1 Aug 2016 13:37:53 +0200 Subject: [PATCH 164/315] Create Assigned to component --- .../assigned-to-selector.directive.coffee | 40 +++++++++++++++++++ .../assigned-to-selector.jade | 4 ++ .../assigned-to/assigned-to.controller.coffee | 37 +++++++++++++++++ .../assigned-to/assigned-to.directive.coffee | 35 ++++++++++++++++ .../components/assigned-to/assigned-to.jade | 13 ++++++ .../epics/create-epic/create-epic.jade | 2 + .../epic-row/epic-row.controller.coffee | 1 - .../epics/dashboard/epic-row/epic-row.jade | 20 ++-------- .../epics-dashboard.controller.coffee | 6 +-- .../epics/dashboard/epics-dashboard.jade | 1 + .../epics-table/epics-table.directive.coffee | 5 ++- .../dashboard/epics-table/epics-table.jade | 2 +- 12 files changed, 143 insertions(+), 23 deletions(-) create mode 100644 app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee create mode 100644 app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade create mode 100644 app/modules/components/assigned-to/assigned-to.controller.coffee create mode 100644 app/modules/components/assigned-to/assigned-to.directive.coffee create mode 100644 app/modules/components/assigned-to/assigned-to.jade diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee new file mode 100644 index 00000000..b13be4d0 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee @@ -0,0 +1,40 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: assigned-to-selector.directive.coffee +### + +AssignedToSelectorDirective = () -> + + link = (scope, el, attrs) -> + console.log scope.assigned.toJS() + console.log scope.project.toJS() + + return { + # controller: "AssignedToSelectorCtrl", + # controllerAs: "vm", + # bindToController: true, + templateUrl: "components/assigned-to/assigned-to-selector/assigned-to-selector.html", + scope: { + assigned: "=", + project: "=" + }, + link: link + } + +AssignedToSelectorDirective.$inject = [] + +angular.module("taigaComponents").directive("tgAssignedToSelector", AssignedToSelectorDirective) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade new file mode 100644 index 00000000..29de6158 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade @@ -0,0 +1,4 @@ +tg-lightbox-close + +.assigned-to-container + h2.title(translate="COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT") diff --git a/app/modules/components/assigned-to/assigned-to.controller.coffee b/app/modules/components/assigned-to/assigned-to.controller.coffee new file mode 100644 index 00000000..d7b76bc4 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to.controller.coffee @@ -0,0 +1,37 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: attchment.controller.coffee +### + +class AssignedToController + @.$inject = [ + "tgLightboxFactory" + ] + + constructor: (@lightboxFactory) -> + + onSelectAssignedTo: (assigned, project) -> + @lightboxFactory.create('tg-assigned-to-selector', { + "class": "lightbox lightbox-assigned-to-selector open" + "assigned": "assigned" + "project": "project" + }, { + "assigned": assigned + "project": project + }) + +angular.module('taigaComponents').controller('AssignedToCtrl', AssignedToController) diff --git a/app/modules/components/assigned-to/assigned-to.directive.coffee b/app/modules/components/assigned-to/assigned-to.directive.coffee new file mode 100644 index 00000000..bc596b3c --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to.directive.coffee @@ -0,0 +1,35 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: assigned-to.directive.coffee +### + +AssignedToDirective = () -> + + return { + controller: "AssignedToCtrl", + controllerAs: "vm", + bindToController: true, + templateUrl: "components/assigned-to/assigned-to.html", + scope: { + assignedTo: "=" + project: "=" + } + } + +AssignedToDirective.$inject = [] + +angular.module("taigaComponents").directive("tgAssignedToComponent", AssignedToDirective) diff --git a/app/modules/components/assigned-to/assigned-to.jade b/app/modules/components/assigned-to/assigned-to.jade new file mode 100644 index 00000000..e1570c69 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to.jade @@ -0,0 +1,13 @@ +img.assigned-to( + ng-if="vm.assignedTo" + tg-avatar="vm.assignedTo" + alt="{{vm.assignedTo.get('full_name_display')}}" + title="{{vm.assignedTo.get('full_name_display')}}" + ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" +) +img.assigned-to( + ng-if="!vm.assignedTo" + src="/#{v}/images/unnamed.png" + alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" +) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 44367e04..c7ccf9c2 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -6,6 +6,8 @@ tg-lightbox-close ng-submit="vm.createEpic()" ) fieldset + // TODO ADD COLOR SELECTOR + //- tg-color-selector(on-select-dropdown-color="vm.newEpic.color = color") input( type="text" name="subject" diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index d840e567..ffe2898a 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -54,7 +54,6 @@ class EpicRowController @.onUpdateEpicStatus() onError = (data) => - console.log data @confirm.notify('error') return @rs.epics.patch(id, patch).then(onSuccess, onError) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index c7c9d94a..627579ce 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -31,22 +31,10 @@ .sprint( ng-if="vm.column.sprint" ) - .assigned( - ng-if="vm.column.assigned && vm.epic.get('assigned_to')" - ng-click="vm.onSelectAssignedTo()" - ) - img( - tg-avatar="vm.epic.get('assigned_to_extra_info')" - alt="{{::vm.epic.getIn(['assigned_to_extra_info', 'full_name_display'])}}" - ) - .assigned( - ng-if="vm.column.assigned && !vm.epic.get('assigned_to')" - ng-class="{'is-unassigned': !vm.epic.get('assigned_to')}" - ng-click="vm.onSelectAssignedTo()" - ) - img( - src="/#{v}/images/unnamed.png" - alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + .assigned + tg-assigned-to-component( + assigned-to="vm.epic.get('assigned_to_extra_info')" + project="vm.project" ) .status( ng-if="vm.column.status && !vm.permissions.canEdit" diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 1c9d8b6e..39c94575 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -40,9 +40,9 @@ class EpicsDashboardController if not project.is_epics_activated @errorHandlingService.permissionDenied() @.project = project - @._loadEpics() + @.loadEpics() - _loadEpics: () -> + loadEpics: () -> projectId = @.project.id return @resources.epics.list(projectId).then (epics) => @.epics = epics @@ -50,7 +50,7 @@ class EpicsDashboardController _onCreateEpic: () -> @lightboxService.closeAll() @confirm.notify("success") - @._loadEpics() + @.loadEpics() onCreateEpic: () -> @lightboxFactory.create('tg-create-epic', { diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index c2f22f05..3657a406 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -18,6 +18,7 @@ ng-if="vm.project && vm.epics.size" project="vm.project" epics="vm.epics" + on-update-epic-status="vm.loadEpics()" ) section.empty-epics(ng-if="!vm.epics.size") diff --git a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee index bc4793cb..e6b273d3 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -27,8 +27,9 @@ EpicsTableDirective = () -> controllerAs: "vm", bindToController: true, scope: { - epics: "=" - project: "=" + epics: "=", + project: "=", + onUpdateEpicStatus: "&" } } diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index ef17423b..8dec9f2d 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -94,6 +94,6 @@ mixin epicSwitch(name, model) epic="epic" project="vm.project" column="vm.column" - on-update-epic-status="vm.loadEpics()" + on-update-epic-status="vm.onUpdateEpicStatus()" permissions="vm.permissions" ) From 9e4ac74cfe2a7e883e1ee34d3e82db5a7a9fc1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 1 Aug 2016 15:39:26 +0200 Subject: [PATCH 165/315] Assigned to lightbox component --- .../assigned-item.directive.coffee | 37 +++++++++++++++++++ .../assigned-item/assigned-item.jade | 1 + .../assigned-to-selector.controller.coffee | 25 +++++++++++++ .../assigned-to-selector.directive.coffee | 13 ++----- .../assigned-to-selector.jade | 15 +++++++- .../assigned-to/assigned-to.controller.coffee | 7 ++-- .../assigned-to/assigned-to.directive.coffee | 2 +- .../components/assigned-to/assigned-to.jade | 15 +++++++- .../epics/dashboard/story-row/story-row.jade | 11 +----- .../epics/dashboard/story-row/story-row.scss | 3 -- 10 files changed, 101 insertions(+), 28 deletions(-) create mode 100644 app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee create mode 100644 app/modules/components/assigned-to/assigned-item/assigned-item.jade create mode 100644 app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee b/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee new file mode 100644 index 00000000..42bcf36d --- /dev/null +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee @@ -0,0 +1,37 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: assigned-to-selector.directive.coffee +### + +AssignedItemDirective = () -> + + link = (scope, el, attrs) -> + + return { + # controller: "AssignedToSelectorCtrl", + # controllerAs: "vm", + # bindToController: true, + templateUrl: "components/assigned-to/assigned-item/assigned-item.html", + scope: { + member: "=", + }, + link: link + } + +AssignedItemDirective.$inject = [] + +angular.module("taigaComponents").directive("tgAssignedItem", AssignedItemDirective) diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.jade b/app/modules/components/assigned-to/assigned-item/assigned-item.jade new file mode 100644 index 00000000..d3c97e4f --- /dev/null +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.jade @@ -0,0 +1 @@ +.member {{member}} diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee new file mode 100644 index 00000000..6d05b878 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee @@ -0,0 +1,25 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: assigned-to-selector.controller.coffee +### + +class AssignedToSelectorController + @.$inject = [] + + constructor: () -> + +angular.module('taigaComponents').controller('AssignedToSelectorCtrl', AssignedToSelectorController) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee index b13be4d0..33b828d0 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee @@ -19,20 +19,15 @@ AssignedToSelectorDirective = () -> - link = (scope, el, attrs) -> - console.log scope.assigned.toJS() - console.log scope.project.toJS() - return { - # controller: "AssignedToSelectorCtrl", - # controllerAs: "vm", - # bindToController: true, + controller: "AssignedToSelectorCtrl", + controllerAs: "vm", + bindToController: true, templateUrl: "components/assigned-to/assigned-to-selector/assigned-to-selector.html", scope: { assigned: "=", project: "=" - }, - link: link + } } AssignedToSelectorDirective.$inject = [] diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade index 29de6158..e14d5c8c 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade @@ -1,4 +1,17 @@ tg-lightbox-close .assigned-to-container - h2.title(translate="COMMON.ASSIGNED_TO.TITLE_ACTION_EDIT_ASSIGNMENT") + h2.title(translate="LIGHTBOX.ASSIGNED_TO.SELECT") + input.assign-input( + type="text" + placeholder="{{'LIGHTBOX.ASSIGNED_TO.SEARCH' | translate}}" + autofocus + ng-model="vm.assignToMember.name" + ng-model-options="{debounce: 200}" + ) + ul.tags-dropdown + li(ng-repeat="member in vm.project.members | filter: vm.assignToMember.name") + tg-assigned-item.assigned-members-option( + member="member" + ng-click="vm.onAddTag(tag[0], tag[1], vm.project)" + ) diff --git a/app/modules/components/assigned-to/assigned-to.controller.coffee b/app/modules/components/assigned-to/assigned-to.controller.coffee index d7b76bc4..9a04e103 100644 --- a/app/modules/components/assigned-to/assigned-to.controller.coffee +++ b/app/modules/components/assigned-to/assigned-to.controller.coffee @@ -23,15 +23,16 @@ class AssignedToController ] constructor: (@lightboxFactory) -> + @.has_permissions = _.includes(@.project.my_permissions, 'modify_epic') onSelectAssignedTo: (assigned, project) -> @lightboxFactory.create('tg-assigned-to-selector', { "class": "lightbox lightbox-assigned-to-selector open" - "assigned": "assigned" + "assignedTo": "assignedTo" "project": "project" }, { - "assigned": assigned - "project": project + "assignedTo": @.assignedTo + "project": @.project }) angular.module('taigaComponents').controller('AssignedToCtrl', AssignedToController) diff --git a/app/modules/components/assigned-to/assigned-to.directive.coffee b/app/modules/components/assigned-to/assigned-to.directive.coffee index bc596b3c..ae0683ce 100644 --- a/app/modules/components/assigned-to/assigned-to.directive.coffee +++ b/app/modules/components/assigned-to/assigned-to.directive.coffee @@ -25,7 +25,7 @@ AssignedToDirective = () -> bindToController: true, templateUrl: "components/assigned-to/assigned-to.html", scope: { - assignedTo: "=" + assignedTo: "=", project: "=" } } diff --git a/app/modules/components/assigned-to/assigned-to.jade b/app/modules/components/assigned-to/assigned-to.jade index e1570c69..3eb7fe76 100644 --- a/app/modules/components/assigned-to/assigned-to.jade +++ b/app/modules/components/assigned-to/assigned-to.jade @@ -1,13 +1,24 @@ img.assigned-to( - ng-if="vm.assignedTo" + ng-if="vm.assignedTo && vm.has_permissions" tg-avatar="vm.assignedTo" alt="{{vm.assignedTo.get('full_name_display')}}" title="{{vm.assignedTo.get('full_name_display')}}" ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" ) img.assigned-to( - ng-if="!vm.assignedTo" + ng-if="vm.assignedTo && !vm.has_permissions" + tg-avatar="vm.assignedTo" + alt="{{vm.assignedTo.get('full_name_display')}}" + title="{{vm.assignedTo.get('full_name_display')}}" +) +img.assigned-to( + ng-if="!vm.assignedTo && vm.has_permissions" src="/#{v}/images/unnamed.png" alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" ) +img.assigned-to( + ng-if="!vm.assignedTo && !vm.has_permissions" + src="/#{v}/images/unnamed.png" + alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" +) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 0783c479..ba9580ef 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -26,19 +26,12 @@ alt="{{::vm.story.getIn(['project_extra_info', 'name'])}}" ) .sprint(ng-if="vm.column.sprint") {{::vm.story.get('milestone_name')}} - .assigned( - ng-if="vm.column.assigned && vm.story.get('assigned_to')" - ng-click="vm.onSelectAssignedTo()" - ) + .assigned(ng-if="vm.column.assigned && vm.story.get('assigned_to')") img( tg-avatar="vm.story.get('assigned_to_extra_info')" alt="{{::vm.story.getIn(['assigned_to_extra_info', 'full_name_display'])}}" ) - .assigned( - ng-if="vm.column.assigned && !vm.story.get('assigned_to')" - ng-class="{'is-unassigned': !vm.story.get('assigned_to')}" - ng-click="vm.onSelectAssignedTo()" - ) + .assigned(ng-if="vm.column.assigned && !vm.story.get('assigned_to')") img( src="/#{v}/images/unnamed.png" alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index 4eaf597e..ad742a2a 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -86,7 +86,4 @@ margin-right: .25rem; vertical-align: middle; } - .is-unassigned { - color: $gray-light; - } } From 1a76aa5d1ada7ebd9de95a670b4d5e4495179abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 1 Aug 2016 16:53:00 +0200 Subject: [PATCH 166/315] Filter Current assigned --- .../assigned-item/assigned-item.jade | 4 +++- .../assigned-item/assigned-item.scss | 20 +++++++++++++++++++ .../assigned-to-selector.controller.coffee | 9 +++++++++ .../assigned-to-selector.jade | 6 +++--- .../assigned-to-selector.scss | 7 +++++++ .../assigned-to/assigned-to.controller.coffee | 6 +++--- 6 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 app/modules/components/assigned-to/assigned-item/assigned-item.scss create mode 100644 app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.jade b/app/modules/components/assigned-to/assigned-item/assigned-item.jade index d3c97e4f..28564e61 100644 --- a/app/modules/components/assigned-to/assigned-item/assigned-item.jade +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.jade @@ -1 +1,3 @@ -.member {{member}} +.assignable-member-single(ng-click="onSelectMember()") + img.assignable-member-avatar(tg-avatar="member") + .assignable-member-name {{member.full_name}} diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.scss b/app/modules/components/assigned-to/assigned-item/assigned-item.scss new file mode 100644 index 00000000..a4865088 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.scss @@ -0,0 +1,20 @@ +.assignable-member-single { + align-items: center; + background: $white; + border-bottom: 1px solid $whitish; + display: flex; + padding: .25rem 0; + &:hover { + background: rgba($primary-light, .05); + cursor: pointer; + } + .assignable-member-avatar { + flex-basis: 3rem; + margin-right: .5rem; + max-height: 3rem; + max-width: 3rem; + } + .assignable-member-name { + flex: 1; + } +} diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee index 6d05b878..65bf4b42 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee @@ -21,5 +21,14 @@ class AssignedToSelectorController @.$inject = [] constructor: () -> + @._filterAssignedMember() + + _filterAssignedMember: () -> + @.nonAssignedMembers = _.filter(@.project.members, (member) => + return member.id != @.assigned.get('id') + ) + + onAssignTo: (member) -> + console.log member angular.module('taigaComponents').controller('AssignedToSelectorCtrl', AssignedToSelectorController) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade index e14d5c8c..12c02a7c 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade @@ -9,9 +9,9 @@ tg-lightbox-close ng-model="vm.assignToMember.name" ng-model-options="{debounce: 200}" ) - ul.tags-dropdown - li(ng-repeat="member in vm.project.members | filter: vm.assignToMember.name") + ul.assignable-member-list + li(ng-repeat="member in vm.nonAssignedMembers | filter: vm.assignToMember.name | limitTo:6") tg-assigned-item.assigned-members-option( member="member" - ng-click="vm.onAddTag(tag[0], tag[1], vm.project)" + ng-click="vm.onAssignTo(member)" ) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss new file mode 100644 index 00000000..c5b1aec3 --- /dev/null +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss @@ -0,0 +1,7 @@ +.assigned-to-container { + width: 600px; +} + +.assignable-member-list { + margin-top: 1rem; +} diff --git a/app/modules/components/assigned-to/assigned-to.controller.coffee b/app/modules/components/assigned-to/assigned-to.controller.coffee index 9a04e103..d3e1c878 100644 --- a/app/modules/components/assigned-to/assigned-to.controller.coffee +++ b/app/modules/components/assigned-to/assigned-to.controller.coffee @@ -27,11 +27,11 @@ class AssignedToController onSelectAssignedTo: (assigned, project) -> @lightboxFactory.create('tg-assigned-to-selector', { - "class": "lightbox lightbox-assigned-to-selector open" - "assignedTo": "assignedTo" + "class": "lightbox lightbox-assigned-to-selector open", + "assigned": "assigned", "project": "project" }, { - "assignedTo": @.assignedTo + "assigned": @.assignedTo, "project": @.project }) From 1891191ebc9f38377ea95f88738dc59b16fbcca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 2 Aug 2016 10:24:33 +0200 Subject: [PATCH 167/315] Update assigned --- .../assigned-item.directive.coffee | 5 +-- .../assigned-item/assigned-item.jade | 2 +- .../assigned-item/assigned-item.scss | 8 +++-- .../assigned-to-selector.controller.coffee | 17 ++++++--- .../assigned-to-selector.directive.coffee | 4 ++- .../assigned-to-selector.jade | 12 ++++++- .../assigned-to-selector.scss | 20 +++++++++++ .../assigned-to/assigned-to.controller.coffee | 23 +++++++++--- .../assigned-to/assigned-to.directive.coffee | 4 ++- .../epic-row/epic-row.controller.coffee | 35 +++++++++++++++++-- .../epics/dashboard/epic-row/epic-row.jade | 2 ++ 11 files changed, 109 insertions(+), 23 deletions(-) diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee b/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee index 42bcf36d..709ba6cc 100644 --- a/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.directive.coffee @@ -22,12 +22,9 @@ AssignedItemDirective = () -> link = (scope, el, attrs) -> return { - # controller: "AssignedToSelectorCtrl", - # controllerAs: "vm", - # bindToController: true, templateUrl: "components/assigned-to/assigned-item/assigned-item.html", scope: { - member: "=", + member: "=" }, link: link } diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.jade b/app/modules/components/assigned-to/assigned-item/assigned-item.jade index 28564e61..b0c06515 100644 --- a/app/modules/components/assigned-to/assigned-item/assigned-item.jade +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.jade @@ -1,3 +1,3 @@ -.assignable-member-single(ng-click="onSelectMember()") +.assignable-member-single img.assignable-member-avatar(tg-avatar="member") .assignable-member-name {{member.full_name}} diff --git a/app/modules/components/assigned-to/assigned-item/assigned-item.scss b/app/modules/components/assigned-to/assigned-item/assigned-item.scss index a4865088..132d34aa 100644 --- a/app/modules/components/assigned-to/assigned-item/assigned-item.scss +++ b/app/modules/components/assigned-to/assigned-item/assigned-item.scss @@ -1,12 +1,14 @@ .assignable-member-single { align-items: center; - background: $white; - border-bottom: 1px solid $whitish; display: flex; padding: .25rem 0; + .assigned-members-option & { + background: $white; + border-bottom: 1px solid $whitish; + cursor: pointer; + } &:hover { background: rgba($primary-light, .05); - cursor: pointer; } .assignable-member-avatar { flex-basis: 3rem; diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee index 65bf4b42..4e70615b 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.controller.coffee @@ -21,14 +21,21 @@ class AssignedToSelectorController @.$inject = [] constructor: () -> + if @.assigned + @._getAssignedMember() @._filterAssignedMember() - _filterAssignedMember: () -> - @.nonAssignedMembers = _.filter(@.project.members, (member) => - return member.id != @.assigned.get('id') + _getAssignedMember: () -> + @.assignedMember = _.filter(@.project.members, (member) => + return member.id == @.assigned.get('id') ) - onAssignTo: (member) -> - console.log member + _filterAssignedMember: () -> + if @.assigned + @.nonAssignedMembers = _.filter(@.project.members, (member) => + return member.id != @.assigned.get('id') + ) + else + @.nonAssignedMembers = @.project.members angular.module('taigaComponents').controller('AssignedToSelectorCtrl', AssignedToSelectorController) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee index 33b828d0..b840e856 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.directive.coffee @@ -26,7 +26,9 @@ AssignedToSelectorDirective = () -> templateUrl: "components/assigned-to/assigned-to-selector/assigned-to-selector.html", scope: { assigned: "=", - project: "=" + project: "=", + onRemoveAssigned: "&", + onAssignTo: "&" } } diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade index 12c02a7c..e74b5bb3 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade @@ -10,8 +10,18 @@ tg-lightbox-close ng-model-options="{debounce: 200}" ) ul.assignable-member-list + li.assigned-member( + ng-repeat="member in vm.assignedMember" + ng-if="vm.assigned" + ) + tg-assigned-item(member="member") + tg-svg.unassign-epic( + svg-icon="icon-close" + svg-title-translate="COMMON.ASSIGNED_TO.REMOVE_ASSIGNED" + ng-click="vm.onRemoveAssigned()" + ) li(ng-repeat="member in vm.nonAssignedMembers | filter: vm.assignToMember.name | limitTo:6") tg-assigned-item.assigned-members-option( member="member" - ng-click="vm.onAssignTo(member)" + ng-click="vm.onAssignTo({'member': member})" ) diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss index c5b1aec3..ac54aa3a 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.scss @@ -4,4 +4,24 @@ .assignable-member-list { margin-top: 1rem; + .assigned-member { + align-items: center; + background: rgba($primary-light, .05); + border-bottom: 1px solid $whitish; + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + } + .unassign-epic { + cursor: pointer; + margin-right: 1rem; + } + .icon { + fill: $red-light; + transition: fill .2s; + &:hover { + cursor: pointer; + fill: $red; + } + } } diff --git a/app/modules/components/assigned-to/assigned-to.controller.coffee b/app/modules/components/assigned-to/assigned-to.controller.coffee index d3e1c878..dc69b30e 100644 --- a/app/modules/components/assigned-to/assigned-to.controller.coffee +++ b/app/modules/components/assigned-to/assigned-to.controller.coffee @@ -14,25 +14,38 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -# File: attchment.controller.coffee +# File: assigned-to.controller.coffee ### class AssignedToController @.$inject = [ - "tgLightboxFactory" + "tgLightboxFactory", + "lightboxService", ] - constructor: (@lightboxFactory) -> + constructor: (@lightboxFactory, @lightboxService) -> @.has_permissions = _.includes(@.project.my_permissions, 'modify_epic') + _closeAndRemoveAssigned: () -> + @lightboxService.closeAll() + @.onRemoveAssigned() + + _closeAndAssign: (member) -> + @lightboxService.closeAll() + @.onAssignTo({'member': member}) + onSelectAssignedTo: (assigned, project) -> @lightboxFactory.create('tg-assigned-to-selector', { "class": "lightbox lightbox-assigned-to-selector open", "assigned": "assigned", - "project": "project" + "project": "project", + "on-remove-assigned": "onRemoveAssigned()" + "on-assign-to": "assignTo(member)" }, { "assigned": @.assignedTo, - "project": @.project + "project": @.project, + "onRemoveAssigned": @._closeAndRemoveAssigned.bind(this), + "assignTo": @._closeAndAssign.bind(this) }) angular.module('taigaComponents').controller('AssignedToCtrl', AssignedToController) diff --git a/app/modules/components/assigned-to/assigned-to.directive.coffee b/app/modules/components/assigned-to/assigned-to.directive.coffee index ae0683ce..a6ec47aa 100644 --- a/app/modules/components/assigned-to/assigned-to.directive.coffee +++ b/app/modules/components/assigned-to/assigned-to.directive.coffee @@ -26,7 +26,9 @@ AssignedToDirective = () -> templateUrl: "components/assigned-to/assigned-to.html", scope: { assignedTo: "=", - project: "=" + project: "=", + onRemoveAssigned: "&", + onAssignTo: "&" } } diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index ffe2898a..7b26a8d2 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -73,7 +73,38 @@ class EpicRowController else @.displayUserStories = false - onSelectAssignedTo: () -> - console.log 'Assigned to' + onRemoveAssigned: () -> + id = @.epic.get('id') + version = @.epic.get('version') + patch = { + 'assigned_to': null, + 'version': version + } + + onSuccess = => + @.onUpdateEpicStatus() + @confirm.notify('success') + + onError = (data) => + @confirm.notify('error') + + return @rs.epics.patch(id, patch).then(onSuccess, onError) + + onAssignTo: (member) -> + id = @.epic.get('id') + version = @.epic.get('version') + patch = { + 'assigned_to': member.id, + 'version': version + } + + onSuccess = => + @.onUpdateEpicStatus() + @confirm.notify('success') + + onError = (data) => + @confirm.notify('error') + + return @rs.epics.patch(id, patch).then(onSuccess, onError) module.controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 627579ce..4e778250 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -35,6 +35,8 @@ tg-assigned-to-component( assigned-to="vm.epic.get('assigned_to_extra_info')" project="vm.project" + on-remove-assigned="vm.onRemoveAssigned()" + on-assign-to="vm.onAssignTo(member)" ) .status( ng-if="vm.column.status && !vm.permissions.canEdit" From 901fad6229c2e79bd332bd8dce63ff2fc441f35f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 3 Aug 2016 08:53:45 +0200 Subject: [PATCH 168/315] Update Epics --- .../epics/dashboard/epic-row/epic-row.controller.coffee | 6 +++--- .../epics/dashboard/epic-row/epic-row.directive.coffee | 2 +- .../epics/dashboard/epics-dashboard.controller.coffee | 1 + app/modules/epics/dashboard/epics-dashboard.jade | 2 +- .../dashboard/epics-table/epics-table.directive.coffee | 2 +- app/modules/epics/dashboard/epics-table/epics-table.jade | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index 7b26a8d2..ae0d0cc0 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -51,7 +51,7 @@ class EpicRowController } onSuccess = => - @.onUpdateEpicStatus() + @.onUpdateEpic() onError = (data) => @confirm.notify('error') @@ -82,7 +82,7 @@ class EpicRowController } onSuccess = => - @.onUpdateEpicStatus() + @.onUpdateEpic() @confirm.notify('success') onError = (data) => @@ -99,7 +99,7 @@ class EpicRowController } onSuccess = => - @.onUpdateEpicStatus() + @.onUpdateEpic() @confirm.notify('success') onError = (data) => diff --git a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee index 14b224c8..ecf94d4d 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -31,7 +31,7 @@ EpicRowDirective = () -> epic: '=', column: '=', permissions: '=', - onUpdateEpicStatus: "&" + onUpdateEpic: "&" } } diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 39c94575..9e2e6a36 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -43,6 +43,7 @@ class EpicsDashboardController @.loadEpics() loadEpics: () -> + console.log 'reload' projectId = @.project.id return @resources.epics.list(projectId).then (epics) => @.epics = epics diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 3657a406..50abb268 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -18,7 +18,7 @@ ng-if="vm.project && vm.epics.size" project="vm.project" epics="vm.epics" - on-update-epic-status="vm.loadEpics()" + on-update-epic="vm.loadEpics()" ) section.empty-epics(ng-if="!vm.epics.size") diff --git a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee index e6b273d3..6d89296e 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -29,7 +29,7 @@ EpicsTableDirective = () -> scope: { epics: "=", project: "=", - onUpdateEpicStatus: "&" + onUpdateEpic: "&" } } diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 8dec9f2d..e82bdab0 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -94,6 +94,6 @@ mixin epicSwitch(name, model) epic="epic" project="vm.project" column="vm.column" - on-update-epic-status="vm.onUpdateEpicStatus()" + on-update-epic="vm.onUpdateEpic()" permissions="vm.permissions" ) From 596d854f76d3e0a06f614461c5680d449334308a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 3 Aug 2016 09:34:14 +0200 Subject: [PATCH 169/315] Addremove epic module --- app/locales/taiga/locale-en.json | 2 ++ .../project-menu.controller.coffee | 4 ++++ .../components/project-menu/project-menu.jade | 4 ++-- app/partials/admin/admin-project-modules.jade | 20 ++++++++++++++++++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index dc285a85..a13aef93 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -492,6 +492,8 @@ "TITLE": "Modules", "ENABLE": "Enable", "DISABLE": "Disable", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Manage your user stories to maintain an organized view of upcoming and prioritized work.", "NUMBER_SPRINTS": "Expected number of sprints", diff --git a/app/modules/components/project-menu/project-menu.controller.coffee b/app/modules/components/project-menu/project-menu.controller.coffee index 23fc3591..9ef67946 100644 --- a/app/modules/components/project-menu/project-menu.controller.coffee +++ b/app/modules/components/project-menu/project-menu.controller.coffee @@ -52,12 +52,16 @@ class ProjectMenuController _setMenuPermissions: () -> @.menu = Immutable.Map({ + epics: false, backlog: false, kanban: false, issues: false, wiki: false }) + if @.project.get("is_epics_activated") && @.project.get("my_permissions").indexOf("view_epics") != -1 + @.menu = @.menu.set("epics", true) + if @.project.get("is_backlog_activated") && @.project.get("my_permissions").indexOf("view_us") != -1 @.menu = @.menu.set("backlog", true) diff --git a/app/modules/components/project-menu/project-menu.jade b/app/modules/components/project-menu/project-menu.jade index 829854d8..62fc2b9c 100644 --- a/app/modules/components/project-menu/project-menu.jade +++ b/app/modules/components/project-menu/project-menu.jade @@ -24,8 +24,8 @@ nav.menu( ) tg-svg(svg-icon="icon-timeline") span.helper(translate="PROJECT.SECTION.TIMELINE") - - li#nav-epics + + li#nav-epics(ng-if="vm.menu.get('epics')") a( tg-nav="project-epics:project=vm.project.get('slug')" ng-class="{active: vm.active == 'epics'}" diff --git a/app/partials/admin/admin-project-modules.jade b/app/partials/admin/admin-project-modules.jade index 09f80d3a..037a231e 100644 --- a/app/partials/admin/admin-project-modules.jade +++ b/app/partials/admin/admin-project-modules.jade @@ -17,12 +17,30 @@ div.wrapper( include ../includes/components/mainTitle form.module-container + .module.module-epics(ng-class="{true:'active', false:''}[project.is_epics_activated]") + .module-icon + tg-svg(svg-icon="icon-epics") + .module-name(translate="ADMIN.MODULES.EPICS") + .module-desc + p(translate="ADMIN.MODULES.EPICS_DESCRIPTION") + .module-activation.module-direct-active + div.check + input.activate-input( + id="functionality-epics" + name="functionality-epics" + type="checkbox" + ng-checked="project.is_epics_activated" + ng-model="project.is_epics_activated" + ) + div + span.check-text.check-yes(translate="COMMON.YES") + span.check-text.check-no(translate="COMMON.NO") .module.module-scrum(ng-class="{true:'active', false:''}[project.is_backlog_activated]") .module-icon tg-svg(svg-icon="icon-scrum") .module-name(translate="ADMIN.MODULES.BACKLOG") .module-desc - p(translate="ADMIN.MODULES.BACKLOG_DESCRIPTION") + p(translate="ADMIN.MODULES.BACKLOG_DESCRIPTION") .module-desc-options(ng-if="project.is_backlog_activated") fieldset label(for="total-sprints") {{ 'ADMIN.MODULES.NUMBER_SPRINTS' | translate }} From 1946d25e8713dc27e9f9e4223d81320890d414a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 3 Aug 2016 15:40:30 +0200 Subject: [PATCH 170/315] Add epics to user dashboard --- app/coffee/modules/base.coffee | 2 +- app/locales/taiga/locale-en.json | 1 + .../epics/dashboard/epic-row/epic-row.jade | 2 +- app/modules/home/duties/duty.directive.coffee | 2 + app/modules/home/home.service.coffee | 33 +++++++++++++-- app/modules/home/home.service.spec.coffee | 41 ++++++++++++++++++- .../working-on/working-on.controller.coffee | 6 ++- .../resources/epics-resource.service.coffee | 13 ++++-- .../includes/modules/search-result-table.jade | 2 +- 9 files changed, 88 insertions(+), 14 deletions(-) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 708dd70e..5cfa7dd4 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -76,7 +76,7 @@ urls = { "project-epics": "/project/:project/epics" "project-search": "/project/:project/search" - "project-epic-detail": "/project/:project/epic/:ref" + "project-epics-detail": "/project/:project/epic/:ref" "project-userstories-detail": "/project/:project/us/:ref" "project-tasks-detail": "/project/:project/task/:ref" "project-issues-detail": "/project/:project/issue/:ref" diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index a13aef93..2c16e946 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -119,6 +119,7 @@ "USER_STORY": "User story", "TASK": "Task", "ISSUE": "Issue", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Enter tag", "DELETE": "Delete tag", diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 4e778250..c6ff107a 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -15,7 +15,7 @@ .name(ng-if="vm.column.name") - var hash = "#"; a( - tg-nav="project-epic-detail:project=vm.project.get('slug')" + tg-nav="project-epics-detail:project=vm.project.get('slug')" ng-attr-title="{{::vm.epic.get('subject')}}" ) #{hash}{{::vm.epic.get('ref')}} {{::vm.epic.get('subject')}} span.epic-pill( diff --git a/app/modules/home/duties/duty.directive.coffee b/app/modules/home/duties/duty.directive.coffee index 798db53c..e4f40268 100644 --- a/app/modules/home/duties/duty.directive.coffee +++ b/app/modules/home/duties/duty.directive.coffee @@ -25,6 +25,8 @@ DutyDirective = (navurls, $translate) -> scope.vm.getDutyType = () -> if scope.vm.duty + if scope.vm.duty.get('_name') == "epics" + return $translate.instant("COMMON.EPIC") if scope.vm.duty.get('_name') == "userstories" return $translate.instant("COMMON.USER_STORY") if scope.vm.duty.get('_name') == "tasks" diff --git a/app/modules/home/home.service.coffee b/app/modules/home/home.service.coffee index 74bb9e5f..862e6b68 100644 --- a/app/modules/home/home.service.coffee +++ b/app/modules/home/home.service.coffee @@ -57,6 +57,10 @@ class HomeService extends taiga.Service assignedTo = workInProgress.get("assignedTo") + if assignedTo.get("epics") + _duties = _getValidDutiesAndAttachProjectInfo(assignedTo.get("epics"), "epics") + assignedTo = assignedTo.set("epics", _duties) + if assignedTo.get("userStories") _duties = _getValidDutiesAndAttachProjectInfo(assignedTo.get("userStories"), "userstories") assignedTo = assignedTo.set("userStories", _duties) @@ -65,7 +69,6 @@ class HomeService extends taiga.Service _duties = _getValidDutiesAndAttachProjectInfo(assignedTo.get("tasks"), "tasks") assignedTo = assignedTo.set("tasks", _duties) - if assignedTo.get("issues") _duties = _getValidDutiesAndAttachProjectInfo(assignedTo.get("issues"), "issues") assignedTo = assignedTo.set("issues", _duties) @@ -73,6 +76,10 @@ class HomeService extends taiga.Service watching = workInProgress.get("watching") + if watching.get("epics") + _duties = _getValidDutiesAndAttachProjectInfo(watching.get("epics"), "epics") + watching = watching.set("epics", _duties) + if watching.get("userStories") _duties = _getValidDutiesAndAttachProjectInfo(watching.get("userStories"), "userstories") watching = watching.set("userStories", _duties) @@ -106,6 +113,14 @@ class HomeService extends taiga.Service assigned_to: userId } + params_epics = { + is_closed: false + assigned_to: userId + } + + assignedEpicsPromise = @rs.epics.listInAllProjects(params_epics).then (epics) -> + assignedTo = assignedTo.set("epics", epics) + assignedUserStoriesPromise = @rs.userstories.listInAllProjects(params_us).then (userstories) -> assignedTo = assignedTo.set("userStories", userstories) @@ -125,8 +140,16 @@ class HomeService extends taiga.Service watchers: userId } + params_epics = { + is_closed: false + watchers: userId + } + watching = Immutable.Map() + watchingEpicsPromise = @rs.epics.listInAllProjects(params_epics).then (epics) -> + watching = watching.set("epics", epics) + watchingUserStoriesPromise = @rs.userstories.listInAllProjects(params_us).then (userstories) -> watching = watching.set("userStories", userstories) @@ -139,12 +162,14 @@ class HomeService extends taiga.Service workInProgress = Immutable.Map() Promise.all([ - projectsPromise + projectsPromise, + assignedEpicsPromise, + watchingEpicsPromise, assignedUserStoriesPromise, - assignedTasksPromise, - assignedIssuesPromise, watchingUserStoriesPromise, + assignedTasksPromise, watchingTasksPromise, + assignedIssuesPromise, watchingIssuesPromise ]).then => workInProgress = workInProgress.set("assignedTo", assignedTo) diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee index f1ef832f..000477e9 100644 --- a/app/modules/home/home.service.spec.coffee +++ b/app/modules/home/home.service.spec.coffee @@ -24,10 +24,12 @@ describe "tgHome", -> _mockResources = () -> mocks.resources = {} + mocks.resources.epics = {} mocks.resources.userstories = {} mocks.resources.tasks = {} mocks.resources.issues = {} + mocks.resources.epics.listInAllProjects = sinon.stub() mocks.resources.userstories.listInAllProjects = sinon.stub() mocks.resources.tasks.listInAllProjects = sinon.stub() mocks.resources.issues.listInAllProjects = sinon.stub() @@ -70,7 +72,7 @@ describe "tgHome", -> _setup() _inject() - it "get work in progress by user", (done) -> + it.only "get work in progress by user", (done) -> userId = 3 project1 = {id: 1, name: "fake1", slug: "project-1"} @@ -83,6 +85,25 @@ describe "tgHome", -> project2 ])) + mocks.resources.epics.listInAllProjects + .withArgs(sinon.match({ + is_closed: false + assigned_to: userId + })) + .promise() + .resolve(Immutable.fromJS([{id: 4, ref: 4, project: "1"}])) + + mocks.resources.epics.listInAllProjects + .withArgs(sinon.match({ + is_closed: false + watchers: userId + })) + .promise() + .resolve(Immutable.fromJS([ + {id: 4, ref: 4, project: "1"}, + {id: 5, ref: 5, project: "10"} # the user is not member of this project + ])) + mocks.resources.userstories.listInAllProjects .withArgs(sinon.match({ is_closed: false @@ -109,6 +130,10 @@ describe "tgHome", -> .resolve(Immutable.fromJS([{id: 3, ref: 3, project: "1"}])) # mock urls + mocks.tgNavUrls.resolve + .withArgs("project-epics-detail", {project: "project-1", ref: 4}) + .returns("/testing-project/epic/1") + mocks.tgNavUrls.resolve .withArgs("project-userstories-detail", {project: "project-1", ref: 1}) .returns("/testing-project/us/1") @@ -125,6 +150,13 @@ describe "tgHome", -> .then (workInProgress) -> expect(workInProgress.toJS()).to.be.eql({ assignedTo: { + epics: [{ + id: 4, + ref: 4, + url: '/testing-project/epic/1', + project: project1, + _name: 'epics' + }] userStories: [{ id: 1, ref: 1, @@ -148,6 +180,13 @@ describe "tgHome", -> }] } watching: { + epics: [{ + id: 4, + ref: 4, + url: '/testing-project/epic/1', + project: project1, + _name: 'epics' + }] userStories: [{ id: 1, ref: 1, diff --git a/app/modules/home/working-on/working-on.controller.coffee b/app/modules/home/working-on/working-on.controller.coffee index dba27fc7..b02d341d 100644 --- a/app/modules/home/working-on/working-on.controller.coffee +++ b/app/modules/home/working-on/working-on.controller.coffee @@ -27,20 +27,22 @@ class WorkingOnController @.watching = Immutable.Map() _setAssignedTo: (workInProgress) -> + epics = workInProgress.get("assignedTo").get("epics") userStories = workInProgress.get("assignedTo").get("userStories") tasks = workInProgress.get("assignedTo").get("tasks") issues = workInProgress.get("assignedTo").get("issues") - @.assignedTo = userStories.concat(tasks).concat(issues) + @.assignedTo = userStories.concat(tasks).concat(issues).concat(epics) if @.assignedTo.size > 0 @.assignedTo = @.assignedTo.sortBy((elem) -> elem.get("modified_date")).reverse() _setWatching: (workInProgress) -> + epics = workInProgress.get("watching").get("epics") userStories = workInProgress.get("watching").get("userStories") tasks = workInProgress.get("watching").get("tasks") issues = workInProgress.get("watching").get("issues") - @.watching = userStories.concat(tasks).concat(issues) + @.watching = userStories.concat(tasks).concat(issues).concat(epics) if @.watching.size > 0 @.watching = @.watching.sortBy((elem) -> elem.get("modified_date")).reverse() diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index ed06dcb9..21129745 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -20,13 +20,18 @@ Resource = (urlsService, http) -> service = {} - service.listAll = (params) -> + service.listInAllProjects = (params) -> url = urlsService.resolve("epics") - httpOptions = {} + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } - return http.get(url, params, httpOptions).then (result) -> - return Immutable.fromJS(result.data) + return http.get(url, params, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) service.list = (projectId) -> url = urlsService.resolve("epics") diff --git a/app/partials/includes/modules/search-result-table.jade b/app/partials/includes/modules/search-result-table.jade index da3ac97b..f794f480 100644 --- a/app/partials/includes/modules/search-result-table.jade +++ b/app/partials/includes/modules/search-result-table.jade @@ -33,7 +33,7 @@ script(type="text/ng-template", id="search-epics") div.ref(tg-bo-ref="epic.ref") div.user-stories div.user-story-name - a(href="", tg-nav="project-epic-detail:project=project.slug,ref=epic.ref", + a(href="", tg-nav="project-epics-detail:project=project.slug,ref=epic.ref", tg-bo-bind="epic.subject") div.status(tg-listitem-epic-status="epic") From 866c576e9efdeb75d886d8e8f514d0002bfd4c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 4 Aug 2016 11:34:17 +0200 Subject: [PATCH 171/315] Add some confirm messages --- app/coffee/modules/backlog/main.coffee | 31 +++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/app/coffee/modules/backlog/main.coffee b/app/coffee/modules/backlog/main.coffee index 831d24cd..c742d74f 100644 --- a/app/coffee/modules/backlog/main.coffee +++ b/app/coffee/modules/backlog/main.coffee @@ -117,11 +117,13 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F @scope.$on "usform:bulk:success", => @.loadUserstories(true) @.loadProjectStats() + @confirm.notify("success") @analytics.trackEvent("userstory", "create", "bulk create userstory on backlog", 1) @scope.$on "sprintform:create:success", => @.loadSprints() @.loadProjectStats() + @confirm.notify("success") @analytics.trackEvent("sprint", "create", "create sprint on backlog", 1) @scope.$on "usform:new:success", => @@ -129,6 +131,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F @.loadProjectStats() @rootscope.$broadcast("filters:update") + @confirm.notify("success") @analytics.trackEvent("userstory", "create", "create userstory on backlog", 1) @scope.$on "sprintform:edit:success", => @@ -331,14 +334,15 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F return _.map(uses, (x) -> {"us_id": x.id, "order": x[field]}) # --move us api behavior-- - # if your are moving multiples USs you must use the bulk api - # if there is only one US you must use patch (repo.save) - # the new US position is the position of the previous US + 1 - # if the previous US has a position value that it is equal to + # If your are moving multiples USs you must use the bulk api + # If there is only one US you must use patch (repo.save) + # + # The new US position is the position of the previous US + 1. + # If the previous US has a position value that it is equal to # other USs, you must send all the USs with that position value # only if they are before of the target position with this USs # if it's a patch you must add them to the header, if is a bulk - # you must send them with the other USs. + # you must send them with the other USs moveUs: (ctx, usList, newUsIndex, newSprintId) -> oldSprintId = usList[0].milestone project = usList[0].project @@ -408,17 +412,18 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F else if previous startIndex = orderList[previous.id] + 1 - previousWithTheSameOrder = _.filter beforeDestination, (it) -> - return it[orderField] == orderList[previous.id] + previousWithTheSameOrder = _.filter(beforeDestination, (it) -> + it[orderField] == orderList[previous.id] + ) - # we must send the USs previous to the dropped USs to - # tell the backend which USs are before the dropped - # USs, if they have the same value to order, the backend - # doens't know after which one do you want to drop + # we must send the USs previous to the dropped USs to tell the backend + # which USs are before the dropped USs, if they have the same value to + # order, the backend doens't know after which one do you want to drop # the USs if previousWithTheSameOrder.length > 1 - setPreviousOrders = _.map previousWithTheSameOrder, (it) -> - return {us_id: it.id, order: orderList[it.id]} + setPreviousOrders = _.map(previousWithTheSameOrder, (it) -> + {us_id: it.id, order: orderList[it.id]} + ) modifiedUs = [] From 479d6f02e6bbe5e2c67a110c9576af6599811daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 4 Aug 2016 13:29:34 +0200 Subject: [PATCH 172/315] Use milestone_id instead of sprint_id in the API --- app/coffee/modules/resources/tasks.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/coffee/modules/resources/tasks.coffee b/app/coffee/modules/resources/tasks.coffee index ba27fec7..4c47ad6e 100644 --- a/app/coffee/modules/resources/tasks.coffee +++ b/app/coffee/modules/resources/tasks.coffee @@ -62,7 +62,7 @@ resourceProvider = ($repo, $http, $urls, $storage) -> service.bulkCreate = (projectId, sprintId, usId, data) -> url = $urls.resolve("bulk-create-tasks") - params = {project_id: projectId, sprint_id: sprintId, us_id: usId, bulk_tasks: data} + params = {project_id: projectId, milestone_id: sprintId, us_id: usId, bulk_tasks: data} return $http.post(url, params).then (result) -> return result.data From 9ed2e3abf2f89f30d043c5d08e5df5fc6683c0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 4 Aug 2016 17:59:51 +0200 Subject: [PATCH 173/315] Tests EPICS dashboard --- .../project-menu.controller.spec.coffee | 7 +- .../epic-row/epic-row.controller.coffee | 27 +- .../epic-row/epic-row.controller.spec.coffee | 240 ++++++++++++++++++ .../epic-row/epic-row.directive.coffee | 4 + .../epics/dashboard/epic-row/epic-row.jade | 7 +- .../epics/dashboard/epic-row/epic-row.scss | 4 +- .../epics-dashboard.controller.coffee | 4 +- .../epics-dashboard.controller.spec.coffee | 142 +++++++++++ .../epics/dashboard/epics-dashboard.jade | 2 +- .../epics-table/epics-table.controller.coffee | 1 - .../epics-table.controller.spec.coffee | 64 +++++ .../epics-table/epics-table.directive.coffee | 4 + app/modules/home/home.service.spec.coffee | 2 +- 13 files changed, 482 insertions(+), 26 deletions(-) create mode 100644 app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee create mode 100644 app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee create mode 100644 app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee diff --git a/app/modules/components/project-menu/project-menu.controller.spec.coffee b/app/modules/components/project-menu/project-menu.controller.spec.coffee index 2e3e1829..65601c88 100644 --- a/app/modules/components/project-menu/project-menu.controller.spec.coffee +++ b/app/modules/components/project-menu/project-menu.controller.spec.coffee @@ -111,6 +111,7 @@ describe "ProjectMenu", -> menu = ctrl.menu.toJS() expect(menu).to.be.eql({ + epics: false, backlog: false, kanban: false, issues: false, @@ -119,11 +120,12 @@ describe "ProjectMenu", -> it "all options enabled", () -> project = Immutable.fromJS({ + is_epics_activated: true, is_backlog_activated: true, is_kanban_activated: true, is_issues_activated: true, is_wiki_activated: true, - my_permissions: ["view_us", "view_issues", "view_wiki_pages"] + my_permissions: ["view_epics", "view_us", "view_issues", "view_wiki_pages"] }) mocks.projectService.project = project @@ -136,6 +138,7 @@ describe "ProjectMenu", -> menu = ctrl.menu.toJS() expect(menu).to.be.eql({ + epics: true, backlog: true, kanban: true, issues: true, @@ -144,6 +147,7 @@ describe "ProjectMenu", -> it "all options disabled because the user doesn't have permissions", () -> project = Immutable.fromJS({ + is_epics_activated: true, is_backlog_activated: true, is_kanban_activated: true, is_issues_activated: true, @@ -161,6 +165,7 @@ describe "ProjectMenu", -> menu = ctrl.menu.toJS() expect(menu).to.be.eql({ + epics: false, backlog: false, kanban: false, issues: false, diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index ae0d0cc0..33c30bf4 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -27,40 +27,40 @@ class EpicRowController constructor: (@rs, @confirm) -> @.displayUserStories = false - @._calculateProgressBar() @.displayAssignedTo = false + @.loadingStatus = false _calculateProgressBar: () -> if @.epic.getIn(['status_extra_info', 'is_closed']) == true @.percentage = "100%" else - opened = @.epic.getIn(['user_stories_counts', 'opened']) - closed = @.epic.getIn(['user_stories_counts', 'closed']) - total = opened + closed - if total == 0 + @.opened = @.epic.getIn(['user_stories_counts', 'opened']) + @.closed = @.epic.getIn(['user_stories_counts', 'closed']) + @.total = @.opened + @.closed + if @.total == 0 @.percentage = "0%" else - @.percentage = "#{closed * 100 / total}%" + @.percentage = "#{@.closed * 100 / @.total}%" updateEpicStatus: (status) -> - id = @.epic.get('id') - version = @.epic.get('version') + @.loadingStatus = true + @.displayStatusList = false patch = { 'status': status, - 'version': version + 'version': @.epic.get('version') } onSuccess = => + @.loadingStatus = false @.onUpdateEpic() onError = (data) => @confirm.notify('error') - return @rs.epics.patch(id, patch).then(onSuccess, onError) + return @rs.epics.patch(@.epic.get('id'), patch).then(onSuccess, onError) requestUserStories: (epic) -> - if @.displayUserStories == false - id = @.epic.get('id') + if !@.displayUserStories onSuccess = (data) => @.epicStories = data @@ -69,7 +69,7 @@ class EpicRowController onError = (data) => @confirm.notify('error') - return @rs.userstories.listInEpic(id).then(onSuccess, onError) + return @rs.userstories.listInEpic(@.epic.get('id')).then(onSuccess, onError) else @.displayUserStories = false @@ -83,7 +83,6 @@ class EpicRowController onSuccess = => @.onUpdateEpic() - @confirm.notify('success') onError = (data) => @confirm.notify('error') diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee new file mode 100644 index 00000000..6205a6df --- /dev/null +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee @@ -0,0 +1,240 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: epic-row.controller.spec.coffee +### + +describe "EpicRow", -> + epicRowCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + epics: { + patch: sinon.stub() + }, + userstories: { + listInEpic: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + _mockTgConfirm() + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.displayUserStories = false + EpicRowCtrl.displayAssignedTo = false + EpicRowCtrl.loadingStatus = false + + it "calculate progress bar in open US", () -> + + EpicRowCtrl = controller "EpicRowCtrl" + + EpicRowCtrl.epic = Immutable.fromJS({ + status_extra_info: { + is_closed: false + } + user_stories_counts: { + opened: 10, + closed: 10 + } + }) + + EpicRowCtrl._calculateProgressBar() + expect(EpicRowCtrl.opened).to.be.equal(10) + expect(EpicRowCtrl.closed).to.be.equal(10) + expect(EpicRowCtrl.total).to.be.equal(20) + expect(EpicRowCtrl.percentage).to.be.equal("50%") + + it "calculate progress bar in zero US", () -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.epic = Immutable.fromJS({ + status_extra_info: { + is_closed: false + } + user_stories_counts: { + opened: 0, + closed: 0 + } + }) + EpicRowCtrl._calculateProgressBar() + expect(EpicRowCtrl.opened).to.be.equal(0) + expect(EpicRowCtrl.closed).to.be.equal(0) + expect(EpicRowCtrl.total).to.be.equal(0) + expect(EpicRowCtrl.percentage).to.be.equal("0%") + + it "calculate progress bar in zero US", () -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.epic = Immutable.fromJS({ + status_extra_info: { + is_closed: true + } + }) + EpicRowCtrl._calculateProgressBar() + expect(EpicRowCtrl.percentage).to.be.equal("100%") + + it "Update Epic Status Success", (done) -> + EpicRowCtrl = controller "EpicRowCtrl" + + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1, + version: 1 + }) + + EpicRowCtrl.patch = { + 'status': 'new', + 'version': EpicRowCtrl.epic.get('version') + } + + EpicRowCtrl.loadingStatus = true + EpicRowCtrl.onUpdateEpic = sinon.stub() + + promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().resolve() + + status = "new" + EpicRowCtrl.updateEpicStatus(status).then () -> + expect(EpicRowCtrl.loadingStatus).to.be.false + expect(EpicRowCtrl.displayStatusList).to.be.false + expect(EpicRowCtrl.onUpdateEpic).to.be.called + done() + + it "Update Epic Status Error", (done) -> + EpicRowCtrl = controller "EpicRowCtrl" + + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1, + version: 1 + }) + + EpicRowCtrl.patch = { + 'status': 'new', + 'version': EpicRowCtrl.epic.get('version') + } + + EpicRowCtrl.loadingStatus = true + EpicRowCtrl.onUpdateEpic = sinon.stub() + + promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().reject(new Error('error')) + + status = "new" + EpicRowCtrl.updateEpicStatus(status).then () -> + expect(mocks.tgConfirm.notify).have.been.calledWith('error') + done() + + it "display User Stories", (done) -> + EpicRowCtrl = controller "EpicRowCtrl" + + EpicRowCtrl.displayUserStories = false + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1 + }) + data = true + + promise = mocks.tgResources.userstories.listInEpic.withArgs(EpicRowCtrl.epic.get('id')).promise().resolve(data) + + EpicRowCtrl.requestUserStories(EpicRowCtrl.epic).then () -> + expect(EpicRowCtrl.displayUserStories).to.be.true + expect(EpicRowCtrl.epicStories).is.equal(data) + done() + + it "display User Stories error", (done) -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.displayUserStories = false + + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1 + }) + + promise = mocks.tgResources.userstories.listInEpic.withArgs(EpicRowCtrl.epic.get('id')).promise().reject(new Error('error')) + + EpicRowCtrl.requestUserStories(EpicRowCtrl.epic).then () -> + expect(mocks.tgConfirm.notify).have.been.calledWith('error') + done() + + it "DO NOT display User Stories", () -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.displayUserStories = true + + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1 + }) + EpicRowCtrl.requestUserStories(EpicRowCtrl.epic) + expect(EpicRowCtrl.displayUserStories).to.be.false + + it "On remove assigned", () -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1, + version: 1 + }) + EpicRowCtrl.patch = { + 'assigned_to': null, + 'version': EpicRowCtrl.epic.get('version') + } + EpicRowCtrl.onUpdateEpic = sinon.stub() + + promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().resolve() + + EpicRowCtrl.onRemoveAssigned().then () -> + expect(EpicRowCtrl.onUpdateEpic).to.have.been.called + + it "On assign to", (done) -> + EpicRowCtrl = controller "EpicRowCtrl" + EpicRowCtrl.epic = Immutable.fromJS({ + id: 1, + version: 1 + }) + id = EpicRowCtrl.epic.get('id') + version = EpicRowCtrl.epic.get('version') + member = { + id: 1 + } + EpicRowCtrl.patch = { + assigned_to: member.id + version: EpicRowCtrl.epic.get('version') + } + + EpicRowCtrl.onUpdateEpic = sinon.stub() + + promise = mocks.tgResources.epics.patch.withArgs(id, EpicRowCtrl.patch).promise().resolve(member) + EpicRowCtrl.onAssignTo(member).then () -> + expect(EpicRowCtrl.onUpdateEpic).to.have.been.called + expect(mocks.tgConfirm.notify).have.been.calledWith('success') + done() diff --git a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee index ecf94d4d..70fbb8e3 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -21,7 +21,11 @@ module = angular.module('taigaEpics') EpicRowDirective = () -> + link = (scope, el, attrs, ctrl) -> + ctrl._calculateProgressBar() + return { + link: link, templateUrl:"epics/dashboard/epic-row/epic-row.html", controller: "EpicRowCtrl", controllerAs: "vm", diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index c6ff107a..1644ab12 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -44,18 +44,19 @@ span {{vm.epic.getIn(['status_extra_info', 'name'])}} .status( ng-if="vm.column.status && vm.permissions.canEdit" - ng-mouseleave="displayStatusList = false" + ng-mouseleave="vm.displayStatusList = false" ) button( - ng-click="displayStatusList = true" + ng-click="vm.displayStatusList = true" ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" + tg-loading="vm.loadingStatus" ) span {{vm.epic.getIn(['status_extra_info', 'name'])}} tg-svg( svg-icon="icon-arrow-down" ) - ul.epic-statuses(ng-show="displayStatusList") + ul.epic-statuses(ng-if="vm.displayStatusList") li( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" ng-click="vm.updateEpicStatus(status.id)" diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 7760f8e0..3cf55907 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -19,8 +19,8 @@ background: rgba($red-light, .5); } &.is-closed { - .name { - color: $gray-light; + .name a { + color: lighten($gray-light, 15%); text-decoration: line-through; } } diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 9e2e6a36..26c2edbf 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -32,10 +32,9 @@ class EpicsDashboardController constructor: (@rs, @resources, @params, @errorHandlingService, @lightboxFactory, @lightboxService, @confirm) -> @.sectionName = "Epics" - @._loadProject() @.createEpic = false - _loadProject: () -> + loadProject: () -> return @rs.projects.getBySlug(@params.pslug).then (project) => if not project.is_epics_activated @errorHandlingService.permissionDenied() @@ -43,7 +42,6 @@ class EpicsDashboardController @.loadEpics() loadEpics: () -> - console.log 'reload' projectId = @.project.id return @resources.epics.list(projectId).then (epics) => @.epics = epics diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee new file mode 100644 index 00000000..12b98e64 --- /dev/null +++ b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee @@ -0,0 +1,142 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: epic-row.controller.spec.coffee +### + +describe "EpicsDashboard", -> + EpicsDashboardCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + projects: { + getBySlug: sinon.stub() + } + } + + provide.value "$tgResources", mocks.tgResources + + _mockTgResourcesNew = () -> + mocks.tgResourcesNew = { + epics: { + list: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResourcesNew + + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + _mockRouteParams = () -> + mocks.routeparams = { + pslug: sinon.stub() + } + + provide.value "$routeParams", mocks.routeparams + + _mockTgErrorHandlingService = () -> + mocks.tgErrorHandlingService = { + permissionDenied: sinon.stub() + } + + provide.value "tgErrorHandlingService", mocks.tgErrorHandlingService + + _mockTgLightboxFactory = () -> + mocks.tgLightboxFactory = { + create: sinon.stub() + } + + provide.value "tgLightboxFactory", mocks.tgLightboxFactory + + _mockLightboxService = () -> + mocks.lightboxService = { + closeAll: sinon.stub() + } + + provide.value "lightboxService", mocks.lightboxService + + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + _mockTgResourcesNew() + _mockRouteParams() + _mockTgErrorHandlingService() + _mockTgLightboxFactory() + _mockLightboxService() + _mockTgConfirm() + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + EpicsDashboardCtrl = controller "EpicsDashboardCtrl" + + it "load projects", (done) -> + EpicsDashboardCtrl = controller "EpicsDashboardCtrl" + params = mocks.routeparams.pslug + EpicsDashboardCtrl.loadEpics = sinon.stub() + project = { + is_epics_activated: false + } + promise = mocks.tgResources.projects.getBySlug.withArgs(params).promise().resolve(project) + EpicsDashboardCtrl.loadProject().then () -> + expect(mocks.tgErrorHandlingService.permissionDenied).have.been.called + expect(EpicsDashboardCtrl.project).is.equal(project) + expect(EpicsDashboardCtrl.loadEpics).have.been.called + done() + + it "load epics", (done) -> + EpicsDashboardCtrl = controller "EpicsDashboardCtrl" + EpicsDashboardCtrl.project = { + id: 1 + } + epics = { + id: 1 + } + promise = mocks.tgResourcesNew.epics.list.withArgs(EpicsDashboardCtrl.project.id).promise().resolve(epics) + EpicsDashboardCtrl.loadEpics().then () -> + expect(EpicsDashboardCtrl.epics).is.equal(epics) + done() + + it "on create epic", () -> + EpicsDashboardCtrl = controller "EpicsDashboardCtrl" + EpicsDashboardCtrl.loadEpics = sinon.stub() + EpicsDashboardCtrl._onCreateEpic() + expect(mocks.lightboxService.closeAll).have.been.called + expect(mocks.tgConfirm.notify).have.been.calledWith("success") + expect(EpicsDashboardCtrl.loadEpics).have.been.called diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 50abb268..dd9f88ee 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -1,4 +1,4 @@ -.wrapper() +.wrapper(ng-init="vm.loadProject()") tg-project-menu section.main(role="main") header.header-with-actions diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee index 6f02f06f..ecc9ae69 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -34,7 +34,6 @@ class EpicsTableController status: true, progress: true } - @._checkPermissions() toggleEpicTableOptions: () -> @.displayOptions = !@.displayOptions diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee new file mode 100644 index 00000000..95f19644 --- /dev/null +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee @@ -0,0 +1,64 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: epic-row.controller.spec.coffee +### + +describe "EpicTable", -> + epicTableCtrl = null + provide = null + controller = null + mocks = {} + + _mocks = () -> + module ($provide) -> + provide = $provide + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "toggle table options", () -> + epicTableCtrl = controller "EpicsTableCtrl" + epicTableCtrl.displayOptions = true + epicTableCtrl.toggleEpicTableOptions() + expect(epicTableCtrl.displayOptions).to.be.false + + it "can edit", () -> + epicTableCtrl = controller "EpicsTableCtrl" + epicTableCtrl.project = { + my_permissions: [ + 'modify_epic' + ] + } + epicTableCtrl._checkPermissions() + expect(epicTableCtrl.permissions.canEdit).to.be.true + + it "can NOT edit", () -> + epicTableCtrl = controller "EpicsTableCtrl" + epicTableCtrl.project = { + my_permissions: [ + 'modify_us' + ] + } + epicTableCtrl._checkPermissions() + expect(epicTableCtrl.permissions.canEdit).to.be.false diff --git a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee index 6d89296e..39827f77 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -21,7 +21,11 @@ module = angular.module('taigaEpics') EpicsTableDirective = () -> + link = (scope, el, attrs, ctrl) -> + ctrl._checkPermissions() + return { + link: link, templateUrl:"epics/dashboard/epics-table/epics-table.html", controller: "EpicsTableCtrl", controllerAs: "vm", diff --git a/app/modules/home/home.service.spec.coffee b/app/modules/home/home.service.spec.coffee index 000477e9..a547feb8 100644 --- a/app/modules/home/home.service.spec.coffee +++ b/app/modules/home/home.service.spec.coffee @@ -72,7 +72,7 @@ describe "tgHome", -> _setup() _inject() - it.only "get work in progress by user", (done) -> + it "get work in progress by user", (done) -> userId = 3 project1 = {id: 1, name: "fake1", slug: "project-1"} From 0816e134bf5b3e1015664143acf4183ef2d44c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 8 Aug 2016 11:09:38 +0200 Subject: [PATCH 174/315] Fix working on tests --- .../working-on.controller.spec.coffee | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/app/modules/home/working-on/working-on.controller.spec.coffee b/app/modules/home/working-on/working-on.controller.spec.coffee index 39d4c1e5..d255ab18 100644 --- a/app/modules/home/working-on/working-on.controller.spec.coffee +++ b/app/modules/home/working-on/working-on.controller.spec.coffee @@ -51,14 +51,32 @@ describe "WorkingOn", -> workInProgress = Immutable.fromJS({ assignedTo: { - userStories: [{id: 1, modified_date: "2015-01-01"}, {id: 2, modified_date: "2015-01-04"}], - tasks: [{id: 3, modified_date: "2015-01-02"}, {id: 4, modified_date: "2015-01-05"}], - issues: [{id: 5, modified_date: "2015-01-03"}, {id: 6, modified_date: "2015-01-06"}] + epics: [ + {id: 7, modified_date: "2015-01-08"}, + {id: 8, modified_date: "2015-01-07"}], + userStories: [ + {id: 1, modified_date: "2015-01-01"}, + {id: 2, modified_date: "2015-01-04"}], + tasks: [ + {id: 3, modified_date: "2015-01-02"}, + {id: 4, modified_date: "2015-01-05"}], + issues: [ + {id: 5, modified_date: "2015-01-03"}, + {id: 6, modified_date: "2015-01-06"}] }, watching: { - userStories: [{id: 7, modified_date: "2015-01-01"}, {id: 8, modified_date: "2015-01-04"}], - tasks: [{id: 9, modified_date: "2015-01-02"}, {id: 10, modified_date: "2015-01-05"}], - issues: [{id: 11, modified_date: "2015-01-03"}, {id: 12, modified_date: "2015-01-06"}] + epics: [ + {id: 13, modified_date: "2015-01-07"}, + {id: 14, modified_date: "2015-01-08"}], + userStories: [ + {id: 7, modified_date: "2015-01-01"}, + {id: 8, modified_date: "2015-01-04"}], + tasks: [ + {id: 9, modified_date: "2015-01-02"}, + {id: 10, modified_date: "2015-01-05"}], + issues: [ + {id: 11, modified_date: "2015-01-03"}, + {id: 12, modified_date: "2015-01-06"}] } }) @@ -68,6 +86,8 @@ describe "WorkingOn", -> ctrl.getWorkInProgress(userId).then () -> expect(ctrl.assignedTo.toJS()).to.be.eql([ + {id: 7, modified_date: '2015-01-08'}, + {id: 8, modified_date: '2015-01-07'}, {id: 6, modified_date: '2015-01-06'}, {id: 4, modified_date: '2015-01-05'}, {id: 2, modified_date: '2015-01-04'}, @@ -77,6 +97,8 @@ describe "WorkingOn", -> ]) expect(ctrl.watching.toJS()).to.be.eql([ + {id: 14, modified_date: '2015-01-08'}, + {id: 13, modified_date: '2015-01-07'}, {id: 12, modified_date: '2015-01-06'}, {id: 10, modified_date: '2015-01-05'}, {id: 8, modified_date: '2015-01-04'}, From 1a42030e80562a737a263c18a38e6dd0c69fdb78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 8 Aug 2016 12:29:04 +0200 Subject: [PATCH 175/315] Belong to epic component --- .../belong-to-epics.directive.coffee | 33 +++++++++++++++++++ .../belong-to-epics/belong-to-epics.jade | 4 +++ .../belong-to-epics/belong-to-epics.scss | 30 +++++++++++++++++ .../epics/dashboard/story-row/story-row.jade | 7 ++-- .../epics/dashboard/story-row/story-row.scss | 30 ----------------- 5 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 app/modules/components/belong-to-epics/belong-to-epics.directive.coffee create mode 100644 app/modules/components/belong-to-epics/belong-to-epics.jade create mode 100644 app/modules/components/belong-to-epics/belong-to-epics.scss diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee new file mode 100644 index 00000000..0752ef40 --- /dev/null +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -0,0 +1,33 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: belong-to-epics.directive.coffee +### + +module = angular.module('taigaEpics') + +BelongToEpicsDirective = () -> + + return { + templateUrl:"components/belong-to-epics/belong-to-epics.html", + scope: { + epics: '=' + } + } + +BelongToEpicsDirective.$inject = [] + +module.directive("tgBelongToEpics", BelongToEpicsDirective) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.jade b/app/modules/components/belong-to-epics/belong-to-epics.jade new file mode 100644 index 00000000..d492581e --- /dev/null +++ b/app/modules/components/belong-to-epics/belong-to-epics.jade @@ -0,0 +1,4 @@ +- var hash = "#"; +.belong-to-epic-pill-wrapper(tg-repeat="epic in epics") + .belong-to-epic-pill(ng-style="{'background': epic.get('color')}") + .belong-to-epic-pill-data #{hash}{{epic.get('id')}} {{epic.get('subject')}} diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss new file mode 100644 index 00000000..65ad4f2c --- /dev/null +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -0,0 +1,30 @@ +.belong-to-epic-pill-wrapper { + display: inline-block; + position: relative; + &:hover { + .belong-to-epic-pill-data { + display: block; + } + } +} +.belong-to-epic-pill { + background-color: $grayer; + border-radius: 50%; + display: inline-block; + height: .75rem; + margin-left: .25rem; + position: relative; + width: .75rem; +} +.belong-to-epic-pill-data { + animation: dropdownFade .2s; + background: rgba($black, .9); + bottom: 1.25rem; + color: $white; + display: none; + left: -100px; + padding: .5rem 1rem; + position: absolute; + width: 200px; + z-index: 99; +} diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index ba9580ef..4010abde 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -14,9 +14,10 @@ tg-nav="project-userstories-detail:project=vm.project.slug,ref=vm.story.get('ref')" ng-attr-title="{{::vm.story.get('subject')}}" ) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}} - .story-pill-wrapper(tg-repeat="pill in vm.story.get('epics')") - .story-pill(ng-style="{'background': pill.get('color')}") - .story-pill-data #{hash}{{pill.get('id')}} {{pill.get('subject')}} + tg-belong-to-epics( + ng-if="vm.story.get('epics')" + epics="vm.story.get('epics')" + ) .project( ng-if="vm.column.project" tg-nav="project:project=vm.story.getIn(['project_extra_info', 'slug'])" diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index ad742a2a..51aa9fcd 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -25,36 +25,6 @@ .name { flex-basis: 17.5vw; } - .story-pill-wrapper { - display: inline-block; - position: relative; - &:hover { - .story-pill-data { - display: block; - } - } - } - .story-pill { - background-color: $grayer; - border-radius: 50%; - display: inline-block; - height: .75rem; - margin-left: .25rem; - position: relative; - width: .75rem; - } - .story-pill-data { - animation: dropdownFade .2s; - background: rgba($black, .9); - bottom: 1.25rem; - color: $white; - display: none; - left: -100px; - padding: .5rem 1rem; - position: absolute; - width: 200px; - z-index: 99; - } .progress-bar, .progress-status { height: 1.5rem; From 93645cee8cdc2897739c277e142ded4f0b38d321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 8 Aug 2016 16:22:52 +0200 Subject: [PATCH 176/315] Display epics in backlog and sprints --- .../belong-to-epics.directive.coffee | 5 +++++ .../includes/components/backlog-row.jade | 18 +++++++++++------- app/partials/includes/modules/sprint.jade | 4 ++++ app/styles/modules/backlog/backlog-table.scss | 2 +- app/styles/modules/backlog/sprints.scss | 3 --- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index 0752ef40..ea4485ed 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -21,7 +21,12 @@ module = angular.module('taigaEpics') BelongToEpicsDirective = () -> + link = (scope, el, attrs) -> + if !scope.epics.isIterable + scope.epics = Immutable.fromJS(scope.epics) + return { + link: link, templateUrl:"components/belong-to-epics/belong-to-epics.html", scope: { epics: '=' diff --git a/app/partials/includes/components/backlog-row.jade b/app/partials/includes/components/backlog-row.jade index 0ed659b9..a569ad99 100644 --- a/app/partials/includes/components/backlog-row.jade +++ b/app/partials/includes/components/backlog-row.jade @@ -1,23 +1,23 @@ -div.row.us-item-row( +.row.us-item-row( ng-repeat="us in userstories track by us.id" tg-bind-scope ng-class="{blocked: us.is_blocked}" tg-class-permission="{'readonly': '!modify_us'}" ) - div.input(tg-check-permission="modify_us") + .input(tg-check-permission="modify_us") input( type="checkbox" name="" ) - div.votes( + .votes( ng-class="{'inactive': !us.total_voters, 'is-voted': us.is_voter}" title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:us.total_voters||0}:'messageformat' }}" ) tg-svg(svg-icon="icon-upvote") span {{ ::us.total_voters }} - div.user-stories - div.tags-block(tg-colorize-tags="us.tags", tg-colorize-tags-type="backlog") - div.user-story-name + .user-stories + .tags-block(tg-colorize-tags="us.tags", tg-colorize-tags-type="backlog") + .user-story-name a.clickable( href="" tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" @@ -26,7 +26,11 @@ div.row.us-item-row( ) span(tg-bo-ref="us.ref") span(ng-bind="us.subject") - div.us-settings + tg-belong-to-epics( + ng-if="us.epics" + epics="us.epics" + ) + .us-settings a.e2e-edit.edit-story( href="" tg-check-permission="modify_us" diff --git a/app/partials/includes/modules/sprint.jade b/app/partials/includes/modules/sprint.jade index d66c5ca7..373ff370 100644 --- a/app/partials/includes/modules/sprint.jade +++ b/app/partials/includes/modules/sprint.jade @@ -19,6 +19,10 @@ div.sprint-table(tg-bind-scope, ng-class="{'sprint-empty-wrapper': !sprint.user_ ng-class="{closed: us.is_closed, blocked: us.is_blocked}") span(tg-bo-ref="us.ref") span(tg-bo-bind="us.subject") + tg-belong-to-epics( + ng-if="us.epics" + epics="us.epics" + ) div.column-points.width-1(tg-bo-bind="us.total_points", ng-class="{closed: us.is_closed, blocked: us.is_blocked}") diff --git a/app/styles/modules/backlog/backlog-table.scss b/app/styles/modules/backlog/backlog-table.scss index 99f22b12..a7124d28 100644 --- a/app/styles/modules/backlog/backlog-table.scss +++ b/app/styles/modules/backlog/backlog-table.scss @@ -28,7 +28,7 @@ flex-shrink: 0; } .user-stories { - overflow: hidden; + // overflow: hidden; width: 100%; } .status { diff --git a/app/styles/modules/backlog/sprints.scss b/app/styles/modules/backlog/sprints.scss index ae95465b..82deb18a 100644 --- a/app/styles/modules/backlog/sprints.scss +++ b/app/styles/modules/backlog/sprints.scss @@ -47,14 +47,12 @@ a { @include font-size(normal); @include font-type(text); - @include ellipsis($width: 90%); display: inline-block; margin-right: .5rem; } } .sprint { margin-bottom: 2rem; - overflow: hidden; header { position: relative; } @@ -182,7 +180,6 @@ padding: 0 4px; } .us-name { - @include ellipsis(230px); display: block; &.closed { color: lighten($gray-light, 5%); From fcecb72b40401a45fc6b5d6cf4044a36b2736a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 9 Aug 2016 08:59:22 +0200 Subject: [PATCH 177/315] Epics in taskboard and kanban --- .../belong-to-epics/belong-to-epics.jade | 6 ++++-- .../belong-to-epics/belong-to-epics.scss | 17 ++--------------- .../card/card-templates/card-title.jade | 4 ++++ .../includes/modules/taskboard-table.jade | 4 ++++ 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.jade b/app/modules/components/belong-to-epics/belong-to-epics.jade index d492581e..0bcdb61a 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics.jade @@ -1,4 +1,6 @@ - var hash = "#"; .belong-to-epic-pill-wrapper(tg-repeat="epic in epics") - .belong-to-epic-pill(ng-style="{'background': epic.get('color')}") - .belong-to-epic-pill-data #{hash}{{epic.get('id')}} {{epic.get('subject')}} + .belong-to-epic-pill( + ng-style="{'background': epic.get('color')}" + title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" + ) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss index 65ad4f2c..e2cbde9c 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.scss +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -11,20 +11,7 @@ background-color: $grayer; border-radius: 50%; display: inline-block; - height: .75rem; - margin-left: .25rem; + height: .7rem; position: relative; - width: .75rem; -} -.belong-to-epic-pill-data { - animation: dropdownFade .2s; - background: rgba($black, .9); - bottom: 1.25rem; - color: $white; - display: none; - left: -100px; - padding: .5rem 1rem; - position: absolute; - width: 200px; - z-index: 99; + width: .7rem; } diff --git a/app/modules/components/card/card-templates/card-title.jade b/app/modules/components/card/card-templates/card-title.jade index ad5434d8..76783ecf 100644 --- a/app/modules/components/card/card-templates/card-title.jade +++ b/app/modules/components/card/card-templates/card-title.jade @@ -7,3 +7,7 @@ h2.card-title ) span(ng-if="vm.visible('ref')") {{::"#" + vm.item.getIn(['model', 'ref'])}} span.e2e-title(ng-if="vm.visible('subject')") {{vm.item.getIn(['model', 'subject'])}} + tg-belong-to-epics( + ng-if="vm.item.getIn(['model', 'epics'])" + epics="vm.item.getIn(['model', 'epics'])" + ) diff --git a/app/partials/includes/modules/taskboard-table.jade b/app/partials/includes/modules/taskboard-table.jade index 3ad5325b..91950ac1 100644 --- a/app/partials/includes/modules/taskboard-table.jade +++ b/app/partials/includes/modules/taskboard-table.jade @@ -54,6 +54,10 @@ div.taskboard-table( tg-bo-title="'#' + us.ref + ' ' + us.subject") span.us-ref(tg-bo-ref="us.ref") span(ng-bind="us.subject") + tg-belong-to-epics( + ng-if="us.epics" + epics="us.epics" + ) p.points-value span(ng-bind="us.total_points") span(translate="TASKBOARD.TABLE.FIELD_POINTS") From 8e901b0067208302e43ad54e8ce24c125b082c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 9 Aug 2016 13:58:04 +0200 Subject: [PATCH 178/315] belong to epic multitemplate --- ...to-epics.jade => belong-to-epics-pill.jade} | 2 +- .../belong-to-epics/belong-to-epics-text.jade | 10 ++++++++++ .../belong-to-epics.directive.coffee | 12 +++++++++--- .../belong-to-epics/belong-to-epics.scss | 9 +++++++++ .../card/card-templates/card-title.jade | 1 + .../epics/dashboard/story-row/story-row.jade | 1 + .../includes/components/backlog-row.jade | 1 + app/partials/includes/modules/sprint.jade | 1 + .../includes/modules/taskboard-table.jade | 1 + app/partials/us/us-detail.jade | 18 +++++++++++++++--- app/styles/layout/ticket-detail.scss | 8 ++++++++ 11 files changed, 57 insertions(+), 7 deletions(-) rename app/modules/components/belong-to-epics/{belong-to-epics.jade => belong-to-epics-pill.jade} (67%) create mode 100644 app/modules/components/belong-to-epics/belong-to-epics-text.jade diff --git a/app/modules/components/belong-to-epics/belong-to-epics.jade b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade similarity index 67% rename from app/modules/components/belong-to-epics/belong-to-epics.jade rename to app/modules/components/belong-to-epics/belong-to-epics-pill.jade index 0bcdb61a..61670efa 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade @@ -1,5 +1,5 @@ - var hash = "#"; -.belong-to-epic-pill-wrapper(tg-repeat="epic in epics") +.belong-to-epic-pill-wrapper(tg-repeat="epic in epics track by epic.get('id')") .belong-to-epic-pill( ng-style="{'background': epic.get('color')}" title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" diff --git a/app/modules/components/belong-to-epics/belong-to-epics-text.jade b/app/modules/components/belong-to-epics/belong-to-epics-text.jade new file mode 100644 index 00000000..f8b935d0 --- /dev/null +++ b/app/modules/components/belong-to-epics/belong-to-epics-text.jade @@ -0,0 +1,10 @@ +- var hash = "#"; +span.belong-to-epic-text-wrapper(tg-repeat="epic in epics track by epic.get('id')") + .belong-to-epic-pill( + ng-style="{'background': epic.get('color')}" + title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" + ) + a.belong-to-epic-text( + href="" + tg-nav="project-epics-detail:project=vm.project.get('slug')" + ) #{hash}{{epic.get('id')}} {{epic.get('subject')}} diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index ea4485ed..455adc1d 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -22,15 +22,21 @@ module = angular.module('taigaEpics') BelongToEpicsDirective = () -> link = (scope, el, attrs) -> - if !scope.epics.isIterable + if scope.epics && !scope.epics.isIterable scope.epics = Immutable.fromJS(scope.epics) + if scope.project && !scope.project.isIterable + scope.project = Immutable.fromJS(scope.project) + + scope.getTemplateUrl = () -> + return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" + return { link: link, - templateUrl:"components/belong-to-epics/belong-to-epics.html", scope: { epics: '=' - } + }, + template : '' } BelongToEpicsDirective.$inject = [] diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss index e2cbde9c..fd487f3c 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.scss +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -7,6 +7,7 @@ } } } + .belong-to-epic-pill { background-color: $grayer; border-radius: 50%; @@ -15,3 +16,11 @@ position: relative; width: .7rem; } + +.belong-to-epic-text-wrapper { + margin-right: 1rem; +} + +.belong-to-epic-text { + margin-left: .25rem; +} diff --git a/app/modules/components/card/card-templates/card-title.jade b/app/modules/components/card/card-templates/card-title.jade index 76783ecf..94df8825 100644 --- a/app/modules/components/card/card-templates/card-title.jade +++ b/app/modules/components/card/card-templates/card-title.jade @@ -8,6 +8,7 @@ h2.card-title span(ng-if="vm.visible('ref')") {{::"#" + vm.item.getIn(['model', 'ref'])}} span.e2e-title(ng-if="vm.visible('subject')") {{vm.item.getIn(['model', 'subject'])}} tg-belong-to-epics( + format="pill" ng-if="vm.item.getIn(['model', 'epics'])" epics="vm.item.getIn(['model', 'epics'])" ) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index 4010abde..d421ca0c 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -15,6 +15,7 @@ ng-attr-title="{{::vm.story.get('subject')}}" ) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}} tg-belong-to-epics( + format="pill" ng-if="vm.story.get('epics')" epics="vm.story.get('epics')" ) diff --git a/app/partials/includes/components/backlog-row.jade b/app/partials/includes/components/backlog-row.jade index a569ad99..4a3a6143 100644 --- a/app/partials/includes/components/backlog-row.jade +++ b/app/partials/includes/components/backlog-row.jade @@ -27,6 +27,7 @@ span(tg-bo-ref="us.ref") span(ng-bind="us.subject") tg-belong-to-epics( + format="pill" ng-if="us.epics" epics="us.epics" ) diff --git a/app/partials/includes/modules/sprint.jade b/app/partials/includes/modules/sprint.jade index 373ff370..f85bc3d6 100644 --- a/app/partials/includes/modules/sprint.jade +++ b/app/partials/includes/modules/sprint.jade @@ -20,6 +20,7 @@ div.sprint-table(tg-bind-scope, ng-class="{'sprint-empty-wrapper': !sprint.user_ span(tg-bo-ref="us.ref") span(tg-bo-bind="us.subject") tg-belong-to-epics( + format="pill" ng-if="us.epics" epics="us.epics" ) diff --git a/app/partials/includes/modules/taskboard-table.jade b/app/partials/includes/modules/taskboard-table.jade index 91950ac1..1b7bad37 100644 --- a/app/partials/includes/modules/taskboard-table.jade +++ b/app/partials/includes/modules/taskboard-table.jade @@ -55,6 +55,7 @@ div.taskboard-table( span.us-ref(tg-bo-ref="us.ref") span(ng-bind="us.subject") tg-belong-to-epics( + format="pill" ng-if="us.epics" epics="us.epics" ) diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index 8baf2bec..eb3a0b5f 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -29,10 +29,22 @@ div.wrapper( div.us-title(ng-class="{blocked: us.is_blocked}") h2.us-title-text span.us-number(tg-bo-ref="us.ref") - span.us-name(tg-editable-subject, ng-model="us", required-perm="modify_us") + span.us-name( + tg-editable-subject + ng-model="us" + required-perm="modify_us" + ) + + p.belong-to-epics-wrapper(ng-if="us.epics") + span This User Story belongs to + tg-belong-to-epics( + ng-if="us.epics" + epics="us.epics" + format="text" + project="project" + ) - p.us-related-task(ng-if="us.origin_issue") - | {{ 'US.PROMOTED'|translate }} + p.us-related-task(ng-if="us.origin_issue") {{ 'US.PROMOTED'|translate }} a( href="" tg-check-permission="view_us" diff --git a/app/styles/layout/ticket-detail.scss b/app/styles/layout/ticket-detail.scss index 7021eca6..5daa3f6a 100644 --- a/app/styles/layout/ticket-detail.scss +++ b/app/styles/layout/ticket-detail.scss @@ -142,6 +142,14 @@ margin-right: 5rem; } } + .belong-to-epics-wrapper { + @include font-size(small); + color: $gray-light; + margin-top: .5rem; + a:hover { + color: $primary; + } + } .loading-spinner { @include loading-spinner; max-height: 1.5rem; From 58a19f1425a854e38e2a7ec2207021641893646a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 11 Aug 2016 16:51:50 +0200 Subject: [PATCH 179/315] Header detail WIP --- app/coffee/modules/common/components.coffee | 168 ++++++------ .../belong-to-epics/belong-to-epics.scss | 1 + .../header/story-header.controller.coffee | 67 +++++ .../header/story-header.directive.coffee | 42 +++ app/modules/stories/header/story-header.jade | 76 ++++++ app/modules/stories/header/story-header.scss | 251 ++++++++++++++++++ app/partials/us/us-detail.jade | 98 ++++--- app/styles/layout/ticket-detail.scss | 180 ------------- 8 files changed, 567 insertions(+), 316 deletions(-) create mode 100644 app/modules/stories/header/story-header.controller.coffee create mode 100644 app/modules/stories/header/story-header.directive.coffee create mode 100644 app/modules/stories/header/story-header.jade create mode 100644 app/modules/stories/header/story-header.scss diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index a6f4fffb..d1c0cea5 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -493,90 +493,90 @@ DeleteButtonDirective = ($log, $repo, $confirm, $location, $template) -> module.directive("tgDeleteButton", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "$tgTemplate", DeleteButtonDirective]) -############################################################################# -## Editable subject directive -############################################################################# - -EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $modelTransform, $template) -> - template = $template.get("common/components/editable-subject.html") - - link = ($scope, $el, $attrs, $model) -> - - $scope.$on "object:updated", () -> - $el.find('.edit-subject').hide() - $el.find('.view-subject').show() - - isEditable = -> - return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 - - save = (subject) -> - currentLoading = $loading() - .target($el.find('.save-container')) - .start() - - transform = $modelTransform.save (item) -> - item.subject = subject - - return item - - transform.then => - $confirm.notify("success") - $rootscope.$broadcast("object:updated") - $el.find('.edit-subject').hide() - $el.find('.view-subject').show() - - transform.then null, -> - $confirm.notify("error") - - transform.finally -> - currentLoading.finish() - - return transform - - $el.click -> - return if not isEditable() - $el.find('.edit-subject').show() - $el.find('.view-subject').hide() - $el.find('input').focus() - - $el.on "click", ".save", (e) -> - e.preventDefault() - - subject = $scope.item.subject - save(subject) - - $el.on "keyup", "input", (event) -> - if event.keyCode == 13 - subject = $scope.item.subject - save(subject) - else if event.keyCode == 27 - $scope.$apply () => $model.$modelValue.revert() - - $el.find('.edit-subject').hide() - $el.find('.view-subject').show() - - $el.find('.edit-subject').hide() - - $scope.$watch $attrs.ngModel, (value) -> - return if not value - $scope.item = value - - if not isEditable() - $el.find('.view-subject .edit').remove() - - $scope.$on "$destroy", -> - $el.off() - - - return { - link: link - restrict: "EA" - require: "ngModel" - template: template - } - -module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", - "$tgTemplate", EditableSubjectDirective]) +# ############################################################################# +# ## Editable subject directive +# ############################################################################# +# +# EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $modelTransform, $template) -> +# template = $template.get("common/components/editable-subject.html") +# +# link = ($scope, $el, $attrs, $model) -> +# +# $scope.$on "object:updated", () -> +# $el.find('.edit-subject').hide() +# $el.find('.view-subject').show() +# +# isEditable = -> +# return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 +# +# save = (subject) -> +# currentLoading = $loading() +# .target($el.find('.save-container')) +# .start() +# +# transform = $modelTransform.save (item) -> +# item.subject = subject +# +# return item +# +# transform.then => +# $confirm.notify("success") +# $rootscope.$broadcast("object:updated") +# $el.find('.edit-subject').hide() +# $el.find('.view-subject').show() +# +# transform.then null, -> +# $confirm.notify("error") +# +# transform.finally -> +# currentLoading.finish() +# +# return transform +# +# $el.click -> +# return if not isEditable() +# $el.find('.edit-subject').show() +# $el.find('.view-subject').hide() +# $el.find('input').focus() +# +# $el.on "click", ".save", (e) -> +# e.preventDefault() +# +# subject = $scope.item.subject +# save(subject) +# +# $el.on "keyup", "input", (event) -> +# if event.keyCode == 13 +# subject = $scope.item.subject +# save(subject) +# else if event.keyCode == 27 +# $scope.$apply () => $model.$modelValue.revert() +# +# $el.find('.edit-subject').hide() +# $el.find('.view-subject').show() +# +# $el.find('.edit-subject').hide() +# +# $scope.$watch $attrs.ngModel, (value) -> +# return if not value +# $scope.item = value +# +# if not isEditable() +# $el.find('.view-subject .edit').remove() +# +# $scope.$on "$destroy", -> +# $el.off() +# +# +# return { +# link: link +# restrict: "EA" +# require: "ngModel" +# template: template +# } +# +# module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", +# "$tgTemplate", EditableSubjectDirective]) ############################################################################# diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss index fd487f3c..e59c4fb0 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.scss +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -13,6 +13,7 @@ border-radius: 50%; display: inline-block; height: .7rem; + margin: 0 .1rem; position: relative; width: .7rem; } diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee new file mode 100644 index 00000000..7fb78177 --- /dev/null +++ b/app/modules/stories/header/story-header.controller.coffee @@ -0,0 +1,67 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: epics.dashboard.controller.coffee +### + +module = angular.module("taigaUserStories") + +class StoryHeaderController + @.$inject = [ + "$rootScope", + "$tgConfirm", + "$tgQueueModelTransformation" + ] + + constructor: (@rootScope, @confirm, @modelTransform) -> + @.editMode = false + @.loadingSubject = false + @.originalSubject = @.item.subject + + _checkPermissions: () -> + @.permissions = { + canEdit: _.includes(@.project.my_permissions, @.requiredPerm) + } + + editSubject: (value) -> + if value + @.editMode = true + if !value + @.editMode = false + + onCancelEdition: (event) -> + if event.which == 27 + @.item.subject = @.originalSubject + @.editSubject(false) + + saveSubject: () -> + onEditSubjectSuccess = () => + @.loadingSubject = false + @rootScope.$broadcast("object:updated") + @confirm.notify('success') + + onEditSubjectError = () => + @.loadingSubject = false + @confirm.notify('error') + + @.editMode = false + @.loadingSubject = true + item = @.item + transform = @modelTransform.save (item) -> + return item + return transform.then(onEditSubjectSuccess, onEditSubjectError) + +module.controller("StoryHeaderCtrl", StoryHeaderController) diff --git a/app/modules/stories/header/story-header.directive.coffee b/app/modules/stories/header/story-header.directive.coffee new file mode 100644 index 00000000..58d9088a --- /dev/null +++ b/app/modules/stories/header/story-header.directive.coffee @@ -0,0 +1,42 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: story-header.directive.coffee +### + +module = angular.module('taigaUserStories') + +DetailHeaderDirective = () -> + @.$inject = [] + + link = (scope, el, attrs, ctrl) -> + ctrl._checkPermissions() + + return { + link: link, + controller: "StoryHeaderCtrl", + bindToController: true, + scope: { + item: "=", + project: "=", + requiredPerm: "@" + }, + controllerAs: "vm", + templateUrl:"stories/header/story-header.html" + } + + +module.directive("tgDetailHeader", DetailHeaderDirective) diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade new file mode 100644 index 00000000..8c42b97f --- /dev/null +++ b/app/modules/stories/header/story-header.jade @@ -0,0 +1,76 @@ +.detail-title-wrapper + h2.detail-title-text.ng-animate-disabled( + ng-show="!vm.editMode" + ng-hide="vm.editMode" + ) + span.detail-number {{'#' + vm.item.ref}} + span.detail-subject( + ng-click="vm.editSubject(true)" + ng-if="vm.permissions.canEdit" + ) {{vm.item.subject}} + span.detail-subject( + ng-if="!vm.permissions.canEdit" + ) {{vm.item.subject}} + tg-svg.detail-edit( + ng-if="vm.permissions.canEdit" + svg-icon="icon-edit" + ng-click="vm.editSubject(true)" + ) + + .edit-title-wrapper(ng-if="vm.editMode") + input.edit-title-input( + type="text" + ng-model="vm.item.subject" + maxlength="500" + autofocus + required + ng-keydown="vm.onCancelEdition($event)" + ) + button.edit-title-button( + ng-click="vm.saveSubject()" + tg-loading="vm.loadingSubject" + ) + tg-svg( + svg-icon="icon-save" + ) +.belong-to-epics-wrapper(ng-if="vm.item.epics") + span This User Story belongs to + tg-belong-to-epics( + ng-if="::vm.item.epics" + epics="::vm.item.epics" + format="text" + project="project" + ) + +.item-origin-issue( + ng-if="vm.item.origin_issue" +) + span(translate="US.PROMOTED") + a( + href="" + tg-check-permission="view_us" + tg-nav="project-issues-detail:project=vm.project.slug,ref=vm.item.origin_issue.ref" + title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" + ) + span {{'#' + vm.item.origin_issue.ref}} + span {{vm.item.origin_issue.subject}} + +.block-desc-container(ng-show="vm.item.is_blocked") + span.block-description-title(translate="COMMON.BLOCKED") + span.block-description( + ng-if="vm.item.blocked_note" + ) {{vm.item.blocked_note}} + +.issue-nav + a( + ng-show="previousUrl" + tg-bo-href="previousUrl" + title="{{'US.PREVIOUS' | translate}}" + ) + tg-svg(svg-icon="icon-arrow-left") + a( + ng-show="nextUrl" + tg-bo-href="nextUrl" + title="{{'US.NEXT' | translate}}" + ) + tg-svg(svg-icon="icon-arrow-right") diff --git a/app/modules/stories/header/story-header.scss b/app/modules/stories/header/story-header.scss new file mode 100644 index 00000000..a8d0693e --- /dev/null +++ b/app/modules/stories/header/story-header.scss @@ -0,0 +1,251 @@ +.detail-header-container { + background: $mass-white; + flex: 1; + padding: 1rem; + &:hover { + .detail-edit { + opacity: 1; + } + } + &.blocked { + background: $red; + color: $white; + transition: all .2s linear; + a, + .detail-number, + .detail-subject { + color: $white; + } + svg { + fill: $white; + } + } + .item-origin-issue, + .belong-to-epics-wrapper, + .block-desc-container { + @include font-size(small); + margin-top: .5rem; + } + .item-origin-issue { + a { + padding: 0 .2rem; + } + } +} + +.detail-title-wrapper { + @include font-size(larger); + @include font-type(text); + align-content: center; + display: flex; + position: relative; + transition: all .2s linear; + &.blocked { + background: $red; + transition: all .2s linear; + } + .detail-title-text { + margin: 0; + } + .detail-number { + color: $gray-light; + flex-shrink: 0; + margin-right: .5rem; + } + .detail-subject { + color: $gray; + flex-grow: 1; + } + + .detail-edit { + cursor: pointer; + margin-left: .75rem; + opacity: 0; + transition: opacity .2s; + svg { + @include svg-size(1.25rem); + } + } +} + +.edit-title-wrapper { + @include font-size(larger); + @include font-type(text); + display: flex; + flex: 1; + .edit-title-input { + background: $white; + flex: 1; + } + .edit-title-button { + background: none; + display: inline; + margin-left: 1rem; + transition: fill .2s; + &:hover { + fill: $primary; + } + } +} + +.block-desc-container { + .block-description-title { + @include font-type(bold); + margin-right: .5rem; + + } +} + +.issue-nav { + position: absolute; + right: 1rem; + top: 1rem; + a { + display: inline-block; + } + svg { + @include svg-size(1.2rem); + fill: currentColor; + } +} + +// +// .us-title { +// +// &.blocked { +// background: $red; +// transition: all .2s linear; +// vertical-align: middle; +// .us-title-text, +// input { +// margin-bottom: .5rem; +// } +// .us-number, +// .us-name, +// .us-related-task { +// color: $white; +// } +// a { +// color: $white; +// transition: color .3s linear; +// } +// a:hover { +// color: $red-light; +// } +// .unblock { +// @include font-type(bold); +// color: $white; +// float: right; +// } +// .unblock:hover { +// color: $red-light; +// transition: color .3s linear; +// } +// } +// p { +// margin-bottom: 0; +// } +// .us-edit-name-inner { +// display: flex; +// } +// .edit-subject { +// align-content: center; +// align-items: center; +// display: flex; +// width: 100%; +// } +// input { +// background: $white; +// flex-grow: 1; +// } +// .save-container { +// flex-grow: 1; +// .save { +// display: block; +// } +// } +// .us-title-text { +// @include font-size(larger); +// @include font-type(text); +// align-content: center; +// align-items: center; +// display: flex; +// flex: 1; +// margin-bottom: 0; +// max-width: 92%; +// width: 100%; +// } +// .us-title-text:hover { +// .edit { +// opacity: 1; +// transition: opacity .3s linear; +// } +// } +// .us-number { +// @include font-type(text); +// color: $gray-light; +// flex-shrink: 0; +// line-height: 2.2rem; +// margin-right: .5rem; +// } +// .us-name { +// color: $gray; +// display: inline-block; +// flex-grow: 1; +// line-height: 2.2rem; +// padding-right: 1rem; +// width: 100%; +// } +// .save, +// .edit { +// cursor: pointer; +// margin-left: .5rem; +// svg { +// fill: $gray-light; +// } +// } +// .edit { +// opacity: 0; +// } +// .us-related-task { +// @include font-size(small); +// color: $gray-light; +// margin-top: .5rem; +// a { +// border-left: 1px solid $gray-light; +// padding: 0 .2rem; +// } +// a:hover { +// color: $primary; +// } +// a:first-child { +// border: 0; +// } +// } +// .block-desc-container { +// @include font-size(small); +// } +// .block-description-title { +// @include font-type(bold); +// color: $white; +// margin-right: .5rem; +// } +// .block-description { +// color: $white; +// display: inline-block; +// margin-right: 5rem; +// } +// } +// .belong-to-epics-wrapper { +// @include font-size(small); +// color: $gray-light; +// margin-top: .5rem; +// a:hover { +// color: $primary; +// } +// } +// .loading-spinner { +// @include loading-spinner; +// max-height: 1.5rem; +// max-width: 1.5rem; +// } diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index eb3a0b5f..fbf4fd23 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -26,59 +26,53 @@ div.wrapper( on-upvote="ctrl.onUpvote" on-downvote="ctrl.onDownvote" ) - div.us-title(ng-class="{blocked: us.is_blocked}") - h2.us-title-text - span.us-number(tg-bo-ref="us.ref") - span.us-name( - tg-editable-subject - ng-model="us" - required-perm="modify_us" - ) - - p.belong-to-epics-wrapper(ng-if="us.epics") - span This User Story belongs to - tg-belong-to-epics( - ng-if="us.epics" - epics="us.epics" - format="text" - project="project" - ) + tg-detail-header.detail-header-container( + item="us" + project="project" + required-perm="modify_us" + ng-class="{blocked: us.is_blocked}" + ng-if="project && us" + ) - p.us-related-task(ng-if="us.origin_issue") {{ 'US.PROMOTED'|translate }} - a( - href="" - tg-check-permission="view_us" - tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref" - tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject" - title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" - ) - span(tg-bo-ref="us.origin_issue.ref") - - p.external-reference(ng-if="us.external_reference") - | {{ 'US.EXTERNAL_REFERENCE'|translate }} - a( - tg-bo-href="us.external_reference[1]", - title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}" - target="_blank" - ) - span {{ us.external_reference[1] }} - - p.block-desc-container(ng-show="us.is_blocked") - span.block-description-title(translate="COMMON.BLOCKED") - span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)") - div.issue-nav - a( - ng-show="previousUrl" - tg-bo-href="previousUrl" - title="{{'US.PREVIOUS' | translate}}" - ) - tg-svg(svg-icon="icon-arrow-left") - a( - ng-show="nextUrl" - tg-bo-href="nextUrl" - title="{{'US.NEXT' | translate}}" - ) - tg-svg(svg-icon="icon-arrow-right") + //- div.us-title(ng-class="{blocked: us.is_blocked}") + //- h2.us-title-text + //- + //- + //- p.us-related-task(ng-if="us.origin_issue") {{ 'US.PROMOTED'|translate }} + //- a( + //- href="" + //- tg-check-permission="view_us" + //- tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref" + //- tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject" + //- title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" + //- ) + //- span(tg-bo-ref="us.origin_issue.ref") + //- + //- p.external-reference(ng-if="us.external_reference") + //- | {{ 'US.EXTERNAL_REFERENCE'|translate }} + //- a( + //- tg-bo-href="us.external_reference[1]", + //- title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}" + //- target="_blank" + //- ) + //- span {{ us.external_reference[1] }} + //- + //- p.block-desc-container(ng-show="us.is_blocked") + //- span.block-description-title(translate="COMMON.BLOCKED") + //- span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)") + //- div.issue-nav + //- a( + //- ng-show="previousUrl" + //- tg-bo-href="previousUrl" + //- title="{{'US.PREVIOUS' | translate}}" + //- ) + //- tg-svg(svg-icon="icon-arrow-left") + //- a( + //- ng-show="nextUrl" + //- tg-bo-href="nextUrl" + //- title="{{'US.NEXT' | translate}}" + //- ) + //- tg-svg(svg-icon="icon-arrow-right") .subheader tg-tag-line.tags-block( ng-if="us && project" diff --git a/app/styles/layout/ticket-detail.scss b/app/styles/layout/ticket-detail.scss index 5daa3f6a..30af3d0a 100644 --- a/app/styles/layout/ticket-detail.scss +++ b/app/styles/layout/ticket-detail.scss @@ -7,186 +7,6 @@ justify-content: center; margin-bottom: .5rem; } - .us-title { - @include font-size(large); - @include font-type(text); - align-items: flex-start; - background: $mass-white; - display: flex; - flex: 1; - flex-direction: column; - padding: .5rem; - position: relative; - transition: all .2s linear; - &.blocked { - background: $red; - transition: all .2s linear; - vertical-align: middle; - .us-title-text, - input { - margin-bottom: .5rem; - } - .us-number, - .us-name, - .us-related-task { - color: $white; - } - a { - color: $white; - transition: color .3s linear; - } - a:hover { - color: $red-light; - } - .unblock { - @include font-type(bold); - color: $white; - float: right; - } - .unblock:hover { - color: $red-light; - transition: color .3s linear; - } - } - p { - margin-bottom: 0; - } - .us-edit-name-inner { - display: flex; - } - .edit-subject { - align-content: center; - align-items: center; - display: flex; - width: 100%; - } - input { - background: $white; - flex-grow: 1; - } - .save-container { - flex-grow: 1; - .save { - display: block; - } - } - .us-title-text { - @include font-size(larger); - @include font-type(text); - align-content: center; - align-items: center; - display: flex; - flex: 1; - margin-bottom: 0; - max-width: 92%; - width: 100%; - } - .us-title-text:hover { - .edit { - opacity: 1; - transition: opacity .3s linear; - } - } - .us-number { - @include font-type(text); - color: $gray-light; - flex-shrink: 0; - line-height: 2.2rem; - margin-right: .5rem; - } - .us-name { - color: $gray; - display: inline-block; - flex-grow: 1; - line-height: 2.2rem; - padding-right: 1rem; - width: 100%; - } - .save, - .edit { - cursor: pointer; - margin-left: .5rem; - svg { - fill: $gray-light; - } - } - .edit { - opacity: 0; - } - .us-related-task { - @include font-size(small); - color: $gray-light; - margin-top: .5rem; - a { - border-left: 1px solid $gray-light; - padding: 0 .2rem; - } - a:hover { - color: $primary; - } - a:first-child { - border: 0; - } - } - .block-desc-container { - @include font-size(small); - } - .block-description-title { - @include font-type(bold); - color: $white; - margin-right: .5rem; - } - .block-description { - color: $white; - display: inline-block; - margin-right: 5rem; - } - } - .belong-to-epics-wrapper { - @include font-size(small); - color: $gray-light; - margin-top: .5rem; - a:hover { - color: $primary; - } - } - .loading-spinner { - @include loading-spinner; - max-height: 1.5rem; - max-width: 1.5rem; - } -} - -.blocked-warning { - margin-bottom: 1rem; - .blocked { - @include font-type(text); - @include font-size(xlarge); - color: $red; - line-height: 2.5rem; - margin-bottom: .5rem; - } - .icon { - @include font-size(xlarge); - vertical-align: middle; - } - .block-description { - color: $grayer; - margin: 0; - } -} - -.issue-nav { - position: absolute; - right: 1rem; - top: 1rem; - a { - display: inline-block; - } - svg { - @include svg-size(1.2rem); - fill: currentColor; - } } .subheader { From fccd26c7499fd9f263f9b80eb1362133a089040a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 12 Aug 2016 14:08:34 +0200 Subject: [PATCH 180/315] US navigation --- app/coffee/modules/common/components.coffee | 87 ------------------- app/coffee/modules/userstories/detail.coffee | 14 --- .../header/story-header.controller.coffee | 27 +++++- app/modules/stories/header/story-header.jade | 10 +-- app/modules/stories/header/story-header.scss | 2 + 5 files changed, 31 insertions(+), 109 deletions(-) diff --git a/app/coffee/modules/common/components.coffee b/app/coffee/modules/common/components.coffee index d1c0cea5..5da45d4a 100644 --- a/app/coffee/modules/common/components.coffee +++ b/app/coffee/modules/common/components.coffee @@ -492,93 +492,6 @@ DeleteButtonDirective = ($log, $repo, $confirm, $location, $template) -> module.directive("tgDeleteButton", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "$tgTemplate", DeleteButtonDirective]) - -# ############################################################################# -# ## Editable subject directive -# ############################################################################# -# -# EditableSubjectDirective = ($rootscope, $repo, $confirm, $loading, $modelTransform, $template) -> -# template = $template.get("common/components/editable-subject.html") -# -# link = ($scope, $el, $attrs, $model) -> -# -# $scope.$on "object:updated", () -> -# $el.find('.edit-subject').hide() -# $el.find('.view-subject').show() -# -# isEditable = -> -# return $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1 -# -# save = (subject) -> -# currentLoading = $loading() -# .target($el.find('.save-container')) -# .start() -# -# transform = $modelTransform.save (item) -> -# item.subject = subject -# -# return item -# -# transform.then => -# $confirm.notify("success") -# $rootscope.$broadcast("object:updated") -# $el.find('.edit-subject').hide() -# $el.find('.view-subject').show() -# -# transform.then null, -> -# $confirm.notify("error") -# -# transform.finally -> -# currentLoading.finish() -# -# return transform -# -# $el.click -> -# return if not isEditable() -# $el.find('.edit-subject').show() -# $el.find('.view-subject').hide() -# $el.find('input').focus() -# -# $el.on "click", ".save", (e) -> -# e.preventDefault() -# -# subject = $scope.item.subject -# save(subject) -# -# $el.on "keyup", "input", (event) -> -# if event.keyCode == 13 -# subject = $scope.item.subject -# save(subject) -# else if event.keyCode == 27 -# $scope.$apply () => $model.$modelValue.revert() -# -# $el.find('.edit-subject').hide() -# $el.find('.view-subject').show() -# -# $el.find('.edit-subject').hide() -# -# $scope.$watch $attrs.ngModel, (value) -> -# return if not value -# $scope.item = value -# -# if not isEditable() -# $el.find('.view-subject .edit').remove() -# -# $scope.$on "$destroy", -> -# $el.off() -# -# -# return { -# link: link -# restrict: "EA" -# require: "ngModel" -# template: template -# } -# -# module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", -# "$tgTemplate", EditableSubjectDirective]) - - ############################################################################# ## Editable description directive ############################################################################# diff --git a/app/coffee/modules/userstories/detail.coffee b/app/coffee/modules/userstories/detail.coffee index c19d4fdb..950185df 100644 --- a/app/coffee/modules/userstories/detail.coffee +++ b/app/coffee/modules/userstories/detail.coffee @@ -166,20 +166,6 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin) @modelTransform.setObject(@scope, 'us') - if @scope.us.neighbors.previous?.ref? - ctx = { - project: @scope.project.slug - ref: @scope.us.neighbors.previous.ref - } - @scope.previousUrl = @navUrls.resolve("project-userstories-detail", ctx) - - if @scope.us.neighbors.next?.ref? - ctx = { - project: @scope.project.slug - ref: @scope.us.neighbors.next.ref - } - @scope.nextUrl = @navUrls.resolve("project-userstories-detail", ctx) - return us loadSprint: -> diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee index 7fb78177..f0720cb9 100644 --- a/app/modules/stories/header/story-header.controller.coffee +++ b/app/modules/stories/header/story-header.controller.coffee @@ -23,14 +23,31 @@ class StoryHeaderController @.$inject = [ "$rootScope", "$tgConfirm", - "$tgQueueModelTransformation" + "$tgQueueModelTransformation", + "$tgNavUrls", ] - constructor: (@rootScope, @confirm, @modelTransform) -> + constructor: (@rootScope, @confirm, @modelTransform, @navUrls) -> @.editMode = false @.loadingSubject = false @.originalSubject = @.item.subject + console.log @.item + + if @.item.neighbors.previous?.ref? + ctx = { + project: @.project.slug + ref: @.item.neighbors.previous.ref + } + @.previousUrl = @navUrls.resolve("project-userstories-detail", ctx) + + if @.item.neighbors.next?.ref? + ctx = { + project: @.project.slug + ref: @.item.neighbors.next.ref + } + @.nextUrl = @navUrls.resolve("project-userstories-detail", ctx) + _checkPermissions: () -> @.permissions = { canEdit: _.includes(@.project.my_permissions, @.requiredPerm) @@ -42,7 +59,10 @@ class StoryHeaderController if !value @.editMode = false - onCancelEdition: (event) -> + onKeyDown: (event) -> + if event.which == 13 + @.saveSubject() + if event.which == 27 @.item.subject = @.originalSubject @.editSubject(false) @@ -52,6 +72,7 @@ class StoryHeaderController @.loadingSubject = false @rootScope.$broadcast("object:updated") @confirm.notify('success') + @.originalSubject = @.item.subject onEditSubjectError = () => @.loadingSubject = false diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade index 8c42b97f..31bd92ea 100644 --- a/app/modules/stories/header/story-header.jade +++ b/app/modules/stories/header/story-header.jade @@ -24,7 +24,7 @@ maxlength="500" autofocus required - ng-keydown="vm.onCancelEdition($event)" + ng-keydown="vm.onKeyDown($event)" ) button.edit-title-button( ng-click="vm.saveSubject()" @@ -63,14 +63,14 @@ .issue-nav a( - ng-show="previousUrl" - tg-bo-href="previousUrl" + ng-if="vm.previousUrl" + ng-href="{{vm.previousUrl}}" title="{{'US.PREVIOUS' | translate}}" ) tg-svg(svg-icon="icon-arrow-left") a( - ng-show="nextUrl" - tg-bo-href="nextUrl" + ng-if="vm.nextUrl" + ng-href="{{vm.nextUrl}}" title="{{'US.NEXT' | translate}}" ) tg-svg(svg-icon="icon-arrow-right") diff --git a/app/modules/stories/header/story-header.scss b/app/modules/stories/header/story-header.scss index a8d0693e..330db122 100644 --- a/app/modules/stories/header/story-header.scss +++ b/app/modules/stories/header/story-header.scss @@ -2,6 +2,7 @@ background: $mass-white; flex: 1; padding: 1rem; + position: relative; &:hover { .detail-edit { opacity: 1; @@ -45,6 +46,7 @@ transition: all .2s linear; } .detail-title-text { + line-height: normal; margin: 0; } .detail-number { From 1dcb363389b55eb823bf208db258793914be5c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 16 Aug 2016 09:07:09 +0200 Subject: [PATCH 181/315] Avoid click on select range --- .../stories/header/story-header.controller.coffee | 15 +++++++++------ app/modules/stories/header/story-header.scss | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee index f0720cb9..46304d9a 100644 --- a/app/modules/stories/header/story-header.controller.coffee +++ b/app/modules/stories/header/story-header.controller.coffee @@ -14,7 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -# File: epics.dashboard.controller.coffee +# File: story-header.controller.coffee ### module = angular.module("taigaUserStories") @@ -25,9 +25,10 @@ class StoryHeaderController "$tgConfirm", "$tgQueueModelTransformation", "$tgNavUrls", + "$window" ] - constructor: (@rootScope, @confirm, @modelTransform, @navUrls) -> + constructor: (@rootScope, @confirm, @modelTransform, @navUrls, @window) -> @.editMode = false @.loadingSubject = false @.originalSubject = @.item.subject @@ -54,10 +55,12 @@ class StoryHeaderController } editSubject: (value) -> - if value - @.editMode = true - if !value - @.editMode = false + selection = @window.getSelection() + if selection.type != "Range" + if value + @.editMode = true + if !value + @.editMode = false onKeyDown: (event) -> if event.which == 13 diff --git a/app/modules/stories/header/story-header.scss b/app/modules/stories/header/story-header.scss index 330db122..f98ec85c 100644 --- a/app/modules/stories/header/story-header.scss +++ b/app/modules/stories/header/story-header.scss @@ -39,6 +39,7 @@ @include font-type(text); align-content: center; display: flex; + max-width: 95%; position: relative; transition: all .2s linear; &.blocked { From 0905c2b56f97509285fc64bb649bc5ab23004621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 16 Aug 2016 09:58:06 +0200 Subject: [PATCH 182/315] Apply to task --- .../header/story-header.controller.coffee | 2 + .../header/story-header.directive.coffee | 1 + app/modules/stories/header/story-header.jade | 11 ++ app/modules/stories/header/story-header.scss | 148 +----------------- app/partials/task/task-detail.jade | 113 +++++++------ app/partials/us/us-detail.jade | 40 ----- 6 files changed, 85 insertions(+), 230 deletions(-) diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee index 46304d9a..77c1c5bc 100644 --- a/app/modules/stories/header/story-header.controller.coffee +++ b/app/modules/stories/header/story-header.controller.coffee @@ -35,6 +35,8 @@ class StoryHeaderController console.log @.item + _checkNav: () -> + if @.item.neighbors.previous?.ref? ctx = { project: @.project.slug diff --git a/app/modules/stories/header/story-header.directive.coffee b/app/modules/stories/header/story-header.directive.coffee index 58d9088a..81f0f9a4 100644 --- a/app/modules/stories/header/story-header.directive.coffee +++ b/app/modules/stories/header/story-header.directive.coffee @@ -24,6 +24,7 @@ DetailHeaderDirective = () -> link = (scope, el, attrs, ctrl) -> ctrl._checkPermissions() + ctrl._checkNav() return { link: link, diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade index 31bd92ea..9c2a6f8a 100644 --- a/app/modules/stories/header/story-header.jade +++ b/app/modules/stories/header/story-header.jade @@ -42,6 +42,17 @@ project="project" ) +.task-belongs-to(ng-if="vm.item.user_story") + span(translate="TASK.OWNER_US") + a( + href="" + tg-check-permission="view_us" + tg-nav="project-userstories-detail:project=project.slug,ref=vm.item.user_story.ref" + title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" + ) + span.item-ref {{'#' + vm.item.ref}} + span {{::vm.item.subject}} + .item-origin-issue( ng-if="vm.item.origin_issue" ) diff --git a/app/modules/stories/header/story-header.scss b/app/modules/stories/header/story-header.scss index f98ec85c..251a316a 100644 --- a/app/modules/stories/header/story-header.scss +++ b/app/modules/stories/header/story-header.scss @@ -22,13 +22,19 @@ } } .item-origin-issue, + .task-belongs-to, .belong-to-epics-wrapper, .block-desc-container { @include font-size(small); margin-top: .5rem; } + .task-belongs-to, .item-origin-issue { a { + cursor: pointer; + padding: 0 .2rem; + } + .item-ref { padding: 0 .2rem; } } @@ -59,7 +65,6 @@ color: $gray; flex-grow: 1; } - .detail-edit { cursor: pointer; margin-left: .75rem; @@ -111,144 +116,3 @@ fill: currentColor; } } - -// -// .us-title { -// -// &.blocked { -// background: $red; -// transition: all .2s linear; -// vertical-align: middle; -// .us-title-text, -// input { -// margin-bottom: .5rem; -// } -// .us-number, -// .us-name, -// .us-related-task { -// color: $white; -// } -// a { -// color: $white; -// transition: color .3s linear; -// } -// a:hover { -// color: $red-light; -// } -// .unblock { -// @include font-type(bold); -// color: $white; -// float: right; -// } -// .unblock:hover { -// color: $red-light; -// transition: color .3s linear; -// } -// } -// p { -// margin-bottom: 0; -// } -// .us-edit-name-inner { -// display: flex; -// } -// .edit-subject { -// align-content: center; -// align-items: center; -// display: flex; -// width: 100%; -// } -// input { -// background: $white; -// flex-grow: 1; -// } -// .save-container { -// flex-grow: 1; -// .save { -// display: block; -// } -// } -// .us-title-text { -// @include font-size(larger); -// @include font-type(text); -// align-content: center; -// align-items: center; -// display: flex; -// flex: 1; -// margin-bottom: 0; -// max-width: 92%; -// width: 100%; -// } -// .us-title-text:hover { -// .edit { -// opacity: 1; -// transition: opacity .3s linear; -// } -// } -// .us-number { -// @include font-type(text); -// color: $gray-light; -// flex-shrink: 0; -// line-height: 2.2rem; -// margin-right: .5rem; -// } -// .us-name { -// color: $gray; -// display: inline-block; -// flex-grow: 1; -// line-height: 2.2rem; -// padding-right: 1rem; -// width: 100%; -// } -// .save, -// .edit { -// cursor: pointer; -// margin-left: .5rem; -// svg { -// fill: $gray-light; -// } -// } -// .edit { -// opacity: 0; -// } -// .us-related-task { -// @include font-size(small); -// color: $gray-light; -// margin-top: .5rem; -// a { -// border-left: 1px solid $gray-light; -// padding: 0 .2rem; -// } -// a:hover { -// color: $primary; -// } -// a:first-child { -// border: 0; -// } -// } -// .block-desc-container { -// @include font-size(small); -// } -// .block-description-title { -// @include font-type(bold); -// color: $white; -// margin-right: .5rem; -// } -// .block-description { -// color: $white; -// display: inline-block; -// margin-right: 5rem; -// } -// } -// .belong-to-epics-wrapper { -// @include font-size(small); -// color: $gray-light; -// margin-top: .5rem; -// a:hover { -// color: $primary; -// } -// } -// .loading-spinner { -// @include loading-spinner; -// max-height: 1.5rem; -// max-width: 1.5rem; -// } diff --git a/app/partials/task/task-detail.jade b/app/partials/task/task-detail.jade index 6ec12a6b..59b6e4be 100644 --- a/app/partials/task/task-detail.jade +++ b/app/partials/task/task-detail.jade @@ -26,54 +26,71 @@ div.wrapper( on-upvote="ctrl.onUpvote", on-downvote="ctrl.onDownvote" ) - div.us-title(ng-class="{blocked: task.is_blocked}") - h2.us-title-text - span.us-number(tg-bo-ref="task.ref") - span.us-name( - tg-editable-subject - ng-model="task" - required-perm="modify_task" - ) - - h3.us-related-task(ng-if="us") - | {{ 'TASK.OWNER_US'|translate }} - a( - href="" - tg-check-permission="view_us" - tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" - title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" - ) - span(tg-bo-ref="us.ref") - span(tg-bo-bind="us.subject") - - p.external-reference(ng-if="task.external_reference") - a( - tg-bo-href="task.external_reference[1]", - target="_blank" - title="{{'TASK.TITLE_LINK_GO_ORIGIN' | translate}}" - ) - | {{ "TASK.ORIGIN_US"| translate }} - span {{ task.external_reference[1] }} - - p.block-desc-container(ng-show="task.is_blocked") - span.block-description-title(translate="COMMON.BLOCKED") - span.block-description( - ng-bind="task.blocked_note || ('TASK.BLOCKED_DESCRIPTION' | translate)" - ) - - div.issue-nav - a( - ng-show="previousUrl" - tg-bo-href="previousUrl" - title="{{'TASK.PREVIOUS' | translate}}" - ) - tg-svg(svg-icon="icon-arrow-left") - a( - ng-show="nextUrl" - tg-bo-href="nextUrl" - title="{{'TASK.NEXT' | translate}}" - ) - tg-svg(svg-icon="icon-arrow-right") + tg-detail-header.detail-header-container( + item="task" + project="project" + required-perm="modify_task" + ng-class="{blocked: task.is_blocked}" + ng-if="project && task" + ) + //- h3.us-related-task(ng-if="us") + //- | {{ 'TASK.OWNER_US'|translate }} + //- a( + //- href="" + //- tg-check-permission="view_us" + //- tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" + //- title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" + //- ) + //- span(tg-bo-ref="us.ref") + //- span(tg-bo-bind="us.subject") + //- div.us-title(ng-class="{blocked: task.is_blocked}") + //- h2.us-title-text + //- span.us-number(tg-bo-ref="task.ref") + //- span.us-name( + //- tg-editable-subject + //- ng-model="task" + //- required-perm="modify_task" + //- ) + //- + //- h3.us-related-task(ng-if="us") + //- | {{ 'TASK.OWNER_US'|translate }} + //- a( + //- href="" + //- tg-check-permission="view_us" + //- tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" + //- title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" + //- ) + //- span(tg-bo-ref="us.ref") + //- span(tg-bo-bind="us.subject") + //- + //- p.external-reference(ng-if="task.external_reference") + //- a( + //- tg-bo-href="task.external_reference[1]", + //- target="_blank" + //- title="{{'TASK.TITLE_LINK_GO_ORIGIN' | translate}}" + //- ) + //- | {{ "TASK.ORIGIN_US"| translate }} + //- span {{ task.external_reference[1] }} + //- + //- p.block-desc-container(ng-show="task.is_blocked") + //- span.block-description-title(translate="COMMON.BLOCKED") + //- span.block-description( + //- ng-bind="task.blocked_note || ('TASK.BLOCKED_DESCRIPTION' | translate)" + //- ) + //- + //- div.issue-nav + //- a( + //- ng-show="previousUrl" + //- tg-bo-href="previousUrl" + //- title="{{'TASK.PREVIOUS' | translate}}" + //- ) + //- tg-svg(svg-icon="icon-arrow-left") + //- a( + //- ng-show="nextUrl" + //- tg-bo-href="nextUrl" + //- title="{{'TASK.NEXT' | translate}}" + //- ) + //- tg-svg(svg-icon="icon-arrow-right") .subheader tg-tag-line.tags-block( ng-if="task && project" diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index fbf4fd23..b35b512d 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -33,46 +33,6 @@ div.wrapper( ng-class="{blocked: us.is_blocked}" ng-if="project && us" ) - - //- div.us-title(ng-class="{blocked: us.is_blocked}") - //- h2.us-title-text - //- - //- - //- p.us-related-task(ng-if="us.origin_issue") {{ 'US.PROMOTED'|translate }} - //- a( - //- href="" - //- tg-check-permission="view_us" - //- tg-nav="project-issues-detail:project=project.slug,ref=us.origin_issue.ref" - //- tg-bo-title="'#' + us.origin_issue.ref + ' ' + us.origin_issue.subject" - //- title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" - //- ) - //- span(tg-bo-ref="us.origin_issue.ref") - //- - //- p.external-reference(ng-if="us.external_reference") - //- | {{ 'US.EXTERNAL_REFERENCE'|translate }} - //- a( - //- tg-bo-href="us.external_reference[1]", - //- title="{{'US.GO_TO_EXTERNAL_REFERENCE' | translate}}" - //- target="_blank" - //- ) - //- span {{ us.external_reference[1] }} - //- - //- p.block-desc-container(ng-show="us.is_blocked") - //- span.block-description-title(translate="COMMON.BLOCKED") - //- span.block-description(ng-bind="us.blocked_note || ('US.BLOCKED' | translate)") - //- div.issue-nav - //- a( - //- ng-show="previousUrl" - //- tg-bo-href="previousUrl" - //- title="{{'US.PREVIOUS' | translate}}" - //- ) - //- tg-svg(svg-icon="icon-arrow-left") - //- a( - //- ng-show="nextUrl" - //- tg-bo-href="nextUrl" - //- title="{{'US.NEXT' | translate}}" - //- ) - //- tg-svg(svg-icon="icon-arrow-right") .subheader tg-tag-line.tags-block( ng-if="us && project" From 140bc1d3f8b018504e83632f49a3758f913b0b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 16 Aug 2016 17:54:58 +0200 Subject: [PATCH 183/315] Header Detail --- app/locales/taiga/locale-en.json | 8 +-- .../header/story-header.controller.coffee | 5 +- app/modules/stories/header/story-header.jade | 53 +++++++++++++------ app/modules/stories/header/story-header.scss | 2 + app/partials/issue/issues-detail.jade | 53 +++---------------- 5 files changed, 51 insertions(+), 70 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 2c16e946..c79fd001 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "One item per line...", "NEW_BULK": "New bulk insert", "RELATED_TASKS": "Related tasks", + "PREVIOUS": "Previous", + "NEXT": "Next", "LOGOUT": "Logout", "EXTERNAL_USER": "an external user", "GENERIC_ERROR": "One of our Oompa Loompas says {{error}}.", @@ -1060,8 +1062,6 @@ "EXTERNAL_REFERENCE": "This US has been created from", "GO_TO_EXTERNAL_REFERENCE": "Go to origin", "BLOCKED": "This user story is blocked", - "PREVIOUS": "previous user story", - "NEXT": "next user story", "TITLE_DELETE_ACTION": "Delete User Story", "LIGHTBOX_TITLE_BLOKING_US": "Blocking us", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tasks completed", @@ -1295,8 +1295,6 @@ "ORIGIN_US": "This task has been created from", "TITLE_LINK_GO_ORIGIN": "Go to user story", "BLOCKED": "This task is blocked", - "PREVIOUS": "previous task", - "NEXT": "next task", "TITLE_DELETE_ACTION": "Delete Task", "LIGHTBOX_TITLE_BLOKING_TASK": "Blocking task", "FIELDS": { @@ -1341,8 +1339,6 @@ "EXTERNAL_REFERENCE": "This issue has been created from", "GO_TO_EXTERNAL_REFERENCE": "Go to origin", "BLOCKED": "This issue is blocked", - "TITLE_PREVIOUS_ISSUE": "previous issue", - "TITLE_NEXT_ISSUE": "next issue", "ACTION_DELETE": "Delete issue", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blocking issue", "FIELDS": { diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee index 77c1c5bc..e784a57c 100644 --- a/app/modules/stories/header/story-header.controller.coffee +++ b/app/modules/stories/header/story-header.controller.coffee @@ -36,20 +36,19 @@ class StoryHeaderController console.log @.item _checkNav: () -> - if @.item.neighbors.previous?.ref? ctx = { project: @.project.slug ref: @.item.neighbors.previous.ref } - @.previousUrl = @navUrls.resolve("project-userstories-detail", ctx) + @.previousUrl = @navUrls.resolve("project-" + @.item._name + "-detail", ctx) if @.item.neighbors.next?.ref? ctx = { project: @.project.slug ref: @.item.neighbors.next.ref } - @.nextUrl = @navUrls.resolve("project-userstories-detail", ctx) + @.nextUrl = @navUrls.resolve("project-" + @.item._name + "-detail", ctx) _checkPermissions: () -> @.permissions = { diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade index 9c2a6f8a..d56ae7e4 100644 --- a/app/modules/stories/header/story-header.jade +++ b/app/modules/stories/header/story-header.jade @@ -33,6 +33,8 @@ tg-svg( svg-icon="icon-save" ) + +//- User Story belongs to epic .belong-to-epics-wrapper(ng-if="vm.item.epics") span This User Story belongs to tg-belong-to-epics( @@ -42,20 +44,41 @@ project="project" ) -.task-belongs-to(ng-if="vm.item.user_story") +//- Task belongs to US +.task-belongs-to( + ng-if="vm.item.user_story_extra_info" + tg-check-permission="view_us" +) span(translate="TASK.OWNER_US") a( - href="" - tg-check-permission="view_us" - tg-nav="project-userstories-detail:project=project.slug,ref=vm.item.user_story.ref" + tg-nav="project-userstories-detail:project=vm.project.slug,ref=vm.item.user_story_extra_info.ref" title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" ) - span.item-ref {{'#' + vm.item.ref}} - span {{::vm.item.subject}} + span.item-ref {{'#' + vm.item.user_story_extra_info.ref}} + span {{::vm.item.user_story_extra_info.subject}} -.item-origin-issue( - ng-if="vm.item.origin_issue" -) + +//- User Stories generated from issue +.item-generated-us(ng-if="vm.item.generated_user_stories.length") + span(translate="ISSUES.PROMOTED") + a( + ng-repeat="userstory in vm.item.generated_user_stories track by userstory.id" + tg-check-permission="view_us" + tg-nav="project-userstories-detail:project=vm.project.slug,ref=userstory.ref" + ) {{'#' + userstory.ref}} {{userstory.subject}} + +//- Issue origin from github +.issue-external-reference(ng-if="vm.item.external_reference") + span(translate="ISSUES.EXTERNAL_REFERENCE") + a( + target="_blank" + ng-href="::vm.item.external_reference[1]" + ng-title="{{'ISSUES.GO_TO_EXTERNAL_REFERENCE' | translate}}" + ) + span {{ ::vm.item.external_reference[1] }} + +//- User Story promoted from issue +.item-origin-issue(ng-if="vm.item.origin_issue") span(translate="US.PROMOTED") a( href="" @@ -63,25 +86,25 @@ tg-nav="project-issues-detail:project=vm.project.slug,ref=vm.item.origin_issue.ref" title="{{'US.TITLE_LINK_GO_TO_ISSUE' | translate}}" ) - span {{'#' + vm.item.origin_issue.ref}} + span.item-ref {{'#' + vm.item.origin_issue.ref}} span {{vm.item.origin_issue.subject}} +//- Blocked description .block-desc-container(ng-show="vm.item.is_blocked") span.block-description-title(translate="COMMON.BLOCKED") - span.block-description( - ng-if="vm.item.blocked_note" - ) {{vm.item.blocked_note}} + span.block-description(ng-if="vm.item.blocked_note") {{vm.item.blocked_note}} +//- Navigation .issue-nav a( ng-if="vm.previousUrl" ng-href="{{vm.previousUrl}}" - title="{{'US.PREVIOUS' | translate}}" + title="{{'COMMON.PREVIOUS' | translate}}" ) tg-svg(svg-icon="icon-arrow-left") a( ng-if="vm.nextUrl" ng-href="{{vm.nextUrl}}" - title="{{'US.NEXT' | translate}}" + title="{{'COMMON.NEXT' | translate}}" ) tg-svg(svg-icon="icon-arrow-right") diff --git a/app/modules/stories/header/story-header.scss b/app/modules/stories/header/story-header.scss index 251a316a..122e1cd3 100644 --- a/app/modules/stories/header/story-header.scss +++ b/app/modules/stories/header/story-header.scss @@ -21,6 +21,7 @@ fill: $white; } } + .item-generated-us, .item-origin-issue, .task-belongs-to, .belong-to-epics-wrapper, @@ -28,6 +29,7 @@ @include font-size(small); margin-top: .5rem; } + .item-generated-us, .task-belongs-to, .item-origin-issue { a { diff --git a/app/partials/issue/issues-detail.jade b/app/partials/issue/issues-detail.jade index 1b4185f0..771cf4fd 100644 --- a/app/partials/issue/issues-detail.jade +++ b/app/partials/issue/issues-detail.jade @@ -17,52 +17,13 @@ div.wrapper( on-upvote="ctrl.onUpvote" on-downvote="ctrl.onDownvote" ) - .us-title(ng-class="{blocked: issue.is_blocked}") - h2.us-title-text - span.us-number(tg-bo-ref="issue.ref") - span.us-name(tg-editable-subject, ng-model="issue", required-perm="modify_issue") - - p.us-related-task(ng-if="issue.generated_user_stories.length") {{ 'ISSUES.PROMOTED'|translate }} - a( - href="" - ng-repeat="us in issue.generated_user_stories" - tg-check-permission="view_us" - tg-bo-title="'#' + us.ref + ' ' + us.subject" - tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" - ) - span(tg-bo-ref="us.ref") - - p.external-reference(ng-if="issue.external_reference") - | {{ 'ISSUES.EXTERNAL_REFERENCE'|translate }} - a( - target="_blank" - tg-bo-href="issue.external_reference[1]" - title="{{'ISSUES.GO_TO_EXTERNAL_REFERENCE' | translate}}" - ) - span {{ issue.external_reference[1] }} - - p.block-desc-container(ng-show="issue.is_blocked") - span.block-description-title(translate="COMMON.BLOCKED") - span.block-description(ng-bind="issue.blocked_note || ('ISSUES.BLOCKED' | translate)") - - .issue-nav - a( - ng-show="previousUrl" - tg-bo-href="previousUrl" - title="{{'ISSUES.TITLE_PREVIOUS_ISSUE' | translate}}" - ) - tg-svg( - svg-icon="icon-arrow-left" - ) - a( - ng-show="nextUrl" - tg-bo-href="nextUrl" - title="{{'ISSUES.TITLE_NEXT_ISSUE' | translate}}" - - ) - tg-svg( - svg-icon="icon-arrow-right" - ) + tg-detail-header.detail-header-container( + item="issue" + project="project" + required-perm="modify_issue" + ng-class="{blocked: issue.is_blocked}" + ng-if="project && issue" + ) .subheader tg-tag-line.tags-block( ng-if="issue && project" From 3b5fe82b5495e5502d5e96a5fd5a2381dca0abd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 17 Aug 2016 16:21:20 +0200 Subject: [PATCH 184/315] Tests --- .../header/story-header.controller.coffee | 2 - .../story-header.controller.spec.coffee | 144 ++++++++++++++++++ app/modules/stories/header/story-header.jade | 7 +- 3 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 app/modules/stories/header/story-header.controller.spec.coffee diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/stories/header/story-header.controller.coffee index e784a57c..2d2b5437 100644 --- a/app/modules/stories/header/story-header.controller.coffee +++ b/app/modules/stories/header/story-header.controller.coffee @@ -33,8 +33,6 @@ class StoryHeaderController @.loadingSubject = false @.originalSubject = @.item.subject - console.log @.item - _checkNav: () -> if @.item.neighbors.previous?.ref? ctx = { diff --git a/app/modules/stories/header/story-header.controller.spec.coffee b/app/modules/stories/header/story-header.controller.spec.coffee new file mode 100644 index 00000000..31ee9c96 --- /dev/null +++ b/app/modules/stories/header/story-header.controller.spec.coffee @@ -0,0 +1,144 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: wiki-history.controller.spec.coffee +### + +describe "StoryHeaderComponent", -> + headerDetailCtrl = null + provide = null + controller = null + rootScope = null + mocks = {} + + _mockRootScope = () -> + mocks.rootScope = { + $broadcast: sinon.stub() + } + + provide.value "$rootScope", mocks.rootScope + + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + _mockTgQueueModelTransformation = () -> + mocks.modelTransform = { + save: sinon.stub() + } + + provide.value "$tgQueueModelTransformation", mocks.tgQueueModelTransformation + + _mockTgNav = () -> + mocks.navUrls = { + resolve: sinon.stub().returns('project-issues-detail') + } + + provide.value "$tgNavUrls", mocks.navUrls + + _mockWindow = () -> + mocks.window = { + getSelection: sinon.stub() + } + + provide.value "$window", mocks.window + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockRootScope() + _mockTgConfirm() + _mockTgQueueModelTransformation() + _mockTgNav() + _mockWindow() + + return null + + beforeEach -> + module "taigaUserStories" + + _mocks() + + inject ($controller) -> + controller = $controller + + headerDetailCtrl = controller "StoryHeaderCtrl", {}, { + item: { + subject: 'Example subject' + } + } + + headerDetailCtrl.originalSubject = headerDetailCtrl.item.subject + + it "previous item neighbor", () -> + headerDetailCtrl.project = { + slug: 'example_subject' + } + headerDetailCtrl.item.neighbors = { + previous: { + ref: 42 + } + } + headerDetailCtrl._checkNav() + headerDetailCtrl.previousUrl = mocks.navUrls.resolve("project-issues-detail") + expect(headerDetailCtrl.previousUrl).to.be.equal("project-issues-detail") + + it "check permissions", () -> + headerDetailCtrl.project = { + my_permissions: ['view_us'] + } + headerDetailCtrl.requiredPerm = 'view_us' + headerDetailCtrl._checkPermissions() + expect(headerDetailCtrl.permissions).to.be.eql({canEdit: true}) + + it "edit subject without selection", () -> + mocks.window.getSelection.returns({ + type: 'Range' + }) + headerDetailCtrl.editSubject(true) + expect(headerDetailCtrl.editMode).to.be.false + + it "edit subject on click", () -> + mocks.window.getSelection.returns({ + type: 'potato' + }) + headerDetailCtrl.editSubject(true) + expect(headerDetailCtrl.editMode).to.be.true + + it "do not edit subject", () -> + mocks.window.getSelection.returns({ + type: 'Range' + }) + headerDetailCtrl.editSubject(false) + expect(headerDetailCtrl.editMode).to.be.false + + it "save on keydown Enter", () -> + event = {} + event.which = 13 + headerDetailCtrl.saveSubject = sinon.stub() + headerDetailCtrl.onKeyDown(event) + expect(headerDetailCtrl.saveSubject).have.been.called + + it "don't save on keydown ESC", () -> + event = {} + event.which = 27 + headerDetailCtrl.editSubject = sinon.stub() + headerDetailCtrl.onKeyDown(event) + expect(headerDetailCtrl.item.subject).to.be.equal(headerDetailCtrl.originalSubject) + expect(headerDetailCtrl.editSubject).have.been.calledWith(false) diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade index d56ae7e4..81e957df 100644 --- a/app/modules/stories/header/story-header.jade +++ b/app/modules/stories/header/story-header.jade @@ -56,7 +56,12 @@ ) span.item-ref {{'#' + vm.item.user_story_extra_info.ref}} span {{::vm.item.user_story_extra_info.subject}} - + tg-belong-to-epics( + ng-if="::vm.item.user_story_extra_info.epics" + epics="::vm.item.user_story_extra_info.epics" + format="pill" + project="vm.project" + ) //- User Stories generated from issue .item-generated-us(ng-if="vm.item.generated_user_stories.length") From 1ad2dd70b358b45f0dc4fe6d3d0a6de5db05cfd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 18 Aug 2016 09:00:22 +0200 Subject: [PATCH 185/315] Fix header tests --- app/modules/stories/header/story-header.jade | 12 ++++++------ bower.json | 2 +- e2e/helpers/detail-helper.js | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/modules/stories/header/story-header.jade b/app/modules/stories/header/story-header.jade index 81e957df..82615b20 100644 --- a/app/modules/stories/header/story-header.jade +++ b/app/modules/stories/header/story-header.jade @@ -1,24 +1,24 @@ -.detail-title-wrapper +.detail-title-wrapper.e2e-story-header h2.detail-title-text.ng-animate-disabled( ng-show="!vm.editMode" ng-hide="vm.editMode" ) span.detail-number {{'#' + vm.item.ref}} - span.detail-subject( + span.detail-subject.e2e-title-subject( ng-click="vm.editSubject(true)" ng-if="vm.permissions.canEdit" ) {{vm.item.subject}} - span.detail-subject( + span.detail-subject.e2e-title-subject( ng-if="!vm.permissions.canEdit" ) {{vm.item.subject}} - tg-svg.detail-edit( + tg-svg.detail-edit.e2e-detail-edit( ng-if="vm.permissions.canEdit" svg-icon="icon-edit" ng-click="vm.editSubject(true)" ) .edit-title-wrapper(ng-if="vm.editMode") - input.edit-title-input( + input.edit-title-input.e2e-title-input( type="text" ng-model="vm.item.subject" maxlength="500" @@ -26,7 +26,7 @@ required ng-keydown="vm.onKeyDown($event)" ) - button.edit-title-button( + button.edit-title-button.e2e-title-button( ng-click="vm.saveSubject()" tg-loading="vm.loadingSubject" ) diff --git a/bower.json b/bower.json index 7cd4a904..d8c928f0 100644 --- a/bower.json +++ b/bower.json @@ -69,7 +69,7 @@ "angular-translate-loader-partial": "~2.10.0", "angular-translate-loader-static-files": "~2.10.0", "angular-translate-interpolation-messageformat": "~2.10.0", - "ngInfiniteScroll": "1.3.0", + "ngInfiniteScroll": "^1.3.0", "immutable": "~3.8.1", "bluebird": "~3.3.5", "intro.js": "~2.1.0", diff --git a/e2e/helpers/detail-helper.js b/e2e/helpers/detail-helper.js index 1148d21c..78daae62 100644 --- a/e2e/helpers/detail-helper.js +++ b/e2e/helpers/detail-helper.js @@ -2,22 +2,22 @@ var utils = require('../utils'); var helper = module.exports; helper.title = function() { - let el = $('span[tg-editable-subject]'); + let el = $('.e2e-story-header'); let obj = { el: el, getTitle: function() { - return el.$('.view-subject').getText(); + return el.$('.e2e-title-subject').getText(); }, setTitle: function(title) { - el.$('.view-subject').click(); - el.$('.edit-subject input').clear().sendKeys(title); + el.$('.e2e-detail-edit').click(); + el.$('.e2e-title-input').clear().sendKeys(title); }, save: async function() { - el.$('.save').click(); + el.$('.e2e-title-button').click(); await browser.waitForAngular(); } }; From e291f98a461de50fdf250d6800a724af60bed230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 19 Aug 2016 10:29:27 +0200 Subject: [PATCH 186/315] Add e2e tests for epics --- .../assigned-to-selector.jade | 4 +- .../components/assigned-to/assigned-to.jade | 12 +- .../epics/dashboard/epic-row/epic-row.jade | 10 +- .../dashboard/epics-table/epics-table.jade | 10 +- conf.e2e.js | 1 + e2e/helpers/epics-helper.js | 176 ++++++++++++++++++ e2e/suites/epics/epic-dashboard.e2e.js | 67 +++++++ e2e/utils/nav.js | 9 + 8 files changed, 271 insertions(+), 18 deletions(-) create mode 100644 e2e/helpers/epics-helper.js create mode 100644 e2e/suites/epics/epic-dashboard.e2e.js diff --git a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade index e74b5bb3..435a7979 100644 --- a/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade +++ b/app/modules/components/assigned-to/assigned-to-selector/assigned-to-selector.jade @@ -15,12 +15,12 @@ tg-lightbox-close ng-if="vm.assigned" ) tg-assigned-item(member="member") - tg-svg.unassign-epic( + tg-svg.unassign-epic.e2e-unassign( svg-icon="icon-close" svg-title-translate="COMMON.ASSIGNED_TO.REMOVE_ASSIGNED" ng-click="vm.onRemoveAssigned()" ) - li(ng-repeat="member in vm.nonAssignedMembers | filter: vm.assignToMember.name | limitTo:6") + li.e2e-assigned-to-selector(ng-repeat="member in vm.nonAssignedMembers | filter: vm.assignToMember.name | limitTo:6") tg-assigned-item.assigned-members-option( member="member" ng-click="vm.onAssignTo({'member': member})" diff --git a/app/modules/components/assigned-to/assigned-to.jade b/app/modules/components/assigned-to/assigned-to.jade index 3eb7fe76..a03af5a3 100644 --- a/app/modules/components/assigned-to/assigned-to.jade +++ b/app/modules/components/assigned-to/assigned-to.jade @@ -1,24 +1,24 @@ -img.assigned-to( +img.assigned-to.e2e-assigned-to-image( ng-if="vm.assignedTo && vm.has_permissions" tg-avatar="vm.assignedTo" alt="{{vm.assignedTo.get('full_name_display')}}" title="{{vm.assignedTo.get('full_name_display')}}" ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" ) -img.assigned-to( +img.assigned-to.e2e-assigned-to-image( ng-if="vm.assignedTo && !vm.has_permissions" tg-avatar="vm.assignedTo" alt="{{vm.assignedTo.get('full_name_display')}}" title="{{vm.assignedTo.get('full_name_display')}}" ) -img.assigned-to( +img.assigned-to.e2e-assigned-to-image( ng-if="!vm.assignedTo && vm.has_permissions" src="/#{v}/images/unnamed.png" - alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + alt="{{'EPICS.DASHBOARD.UNASSIGNED' | translate}}" ng-click="vm.onSelectAssignedTo(vm.assignedTo, vm.project)" ) -img.assigned-to( +img.assigned-to.e2e-assigned-to-image( ng-if="!vm.assignedTo && !vm.has_permissions" src="/#{v}/images/unnamed.png" - alt="{{EPICS.DASHBOARD.UNASSIGNED | translate}}" + alt="{{'EPICS.DASHBOARD.UNASSIGNED' | translate}}" ) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 1644ab12..452d928a 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,4 +1,4 @@ -.epic-row( +.epic-row.e2e-epic-row( ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed'), 'unfold': vm.displayUserStories}" ng-click="vm.requestUserStories(vm.epic)" ) @@ -31,7 +31,7 @@ .sprint( ng-if="vm.column.sprint" ) - .assigned + .assigned.e2e-assigned-to tg-assigned-to-component( assigned-to="vm.epic.get('assigned_to_extra_info')" project="vm.project" @@ -51,13 +51,13 @@ ng-style="{'color': vm.epic.getIn(['status_extra_info', 'color'])}" tg-loading="vm.loadingStatus" ) - span {{vm.epic.getIn(['status_extra_info', 'name'])}} + span.e2e-epic-status {{vm.epic.getIn(['status_extra_info', 'name'])}} tg-svg( svg-icon="icon-arrow-down" ) ul.epic-statuses(ng-if="vm.displayStatusList") - li( + li.e2e-edit-epic-status( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" ng-click="vm.updateEpicStatus(status.id)" ) {{status.name}} @@ -70,7 +70,7 @@ .epic-stories-wrapper(ng-if="vm.displayUserStories && vm.epicStories") .epic-story(tg-repeat="story in vm.epicStories track by story.get('id')") - tg-story-row( + tg-story-row.e2e-story( epic="vm.epic" story="story" project="vm.project" diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index e82bdab0..1056460c 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -11,8 +11,8 @@ mixin epicSwitch(name, model) span.check-text.check-yes(translate="COMMON.YES") span.check-text.check-no(translate="COMMON.NO") -.epics-table - .epics-table-header +.epics-table.e2e-epic-table + .epics-table-header.e2e-epics-table-header .vote( translate="EPICS.TABLE.VOTES" ng-if="vm.column.votes" @@ -42,10 +42,10 @@ mixin epicSwitch(name, model) ng-if="vm.column.progress" ) .epics-table-options-wrapper(ng-mouseleave="vm.displayOptions = false") - button.epics-table-option-button(ng-click="vm.displayOptions = true") + button.epics-table-option-button.e2e-epics-column-button(ng-click="vm.displayOptions = true") span(translate="EPICS.TABLE.VIEW_OPTIONS") tg-svg(svg-icon="icon-arrow-down") - form.epics-table-dropdown(ng-show="vm.displayOptions") + form.epics-table-dropdown.e2e-epics-column-dropdown(ng-show="vm.displayOptions") .fieldset label.epics-table-options-vote( translate="EPICS.TABLE.VOTES" @@ -90,7 +90,7 @@ mixin epicSwitch(name, model) +epicSwitch('switch-progress', 'vm.column.progress') .epics-table-body(tg-epic-sortable) .epics-table-body-row(tg-repeat="epic in vm.epics track by epic.get('id')") - tg-epic-row( + tg-epic-row.e2e-epic( epic="epic" project="vm.project" column="vm.column" diff --git a/conf.e2e.js b/conf.e2e.js index a88f4a79..d7ca0a01 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -38,6 +38,7 @@ var config = { issues: "e2e/suites/issues/*.e2e.js", tasks: "e2e/suites/tasks/*.e2e.js", userProfile: "e2e/suites/user-profile/*.e2e.js", + epics: "e2e/suites/epics/*.e2e.js", userStories: "e2e/suites/user-stories/*.e2e.js", backlog: "e2e/suites/backlog.e2e.js", home: "e2e/suites/home.e2e.js", diff --git a/e2e/helpers/epics-helper.js b/e2e/helpers/epics-helper.js new file mode 100644 index 00000000..4985b757 --- /dev/null +++ b/e2e/helpers/epics-helper.js @@ -0,0 +1,176 @@ +var utils = require('../utils'); + +var helper = module.exports; + +helper.epic = function() { + let el = $$('.e2e-epic'); + + let obj = { + el: el, + displayUserStoriesinEpic: async function() { + await utils.common.takeScreenshot("epics", "epics-child-closed"); + let storiesCount = await el.count(); + let epicChildren; + for (var i = 0; i < storiesCount; i++) { + let story = await el.get(i); + story.click(); + epicChildren = await story.$$('.e2e-story').count(); + if (epicChildren > 0) { + await utils.common.takeScreenshot("epics", "epics-child-open"); + break; + } + } + return epicChildren; + }, + getAssignedTo: async function() { + return await el.get(0).$('.e2e-assigned-to-image').getAttribute("title"); + }, + resetAssignedTo: async function() { + el.get(0).$('.e2e-assigned-to-image').click(); + $$('.e2e-assigned-to-selector').get(0).click(); + }, + editAssignedTo: async function() { + el.get(0).$('.e2e-assigned-to-image').click(); + $$('.e2e-assigned-to-selector').last().click(); + }, + removeAssignedTo: async function() { + el.get(0).$('.e2e-assigned-to-image').click(); + $$('.e2e-unassign').click(); + return el.get(0).$('.e2e-assigned-to-image').getAttribute("alt"); + }, + resetStatus: function() { + el.get(0).$('.e2e-epic-status').click(); + el.get(0).$$('.e2e-edit-epic-status').get(0).click(); + }, + getStatus: function() { + return el.get(0).$('.e2e-epic-status').getText(); + }, + editStatus: function() { + el.get(0).$('.e2e-epic-status').click(); + el.get(0).$$('.e2e-edit-epic-status').last().click(); + }, + getColumns: function() { + return $$('.e2e-epics-table-header > div').count(); + }, + removeColumns: function() { + $('.e2e-epics-column-button').click(); + $$('.e2e-epics-column-dropdown .check').first().click(); + } + } + + return obj; +} + +// helper.title = function() { +// let el = $('.e2e-story-header'); +// +// let obj = { +// el: el, +// +// getTitle: function() { +// return el.$('.e2e-title-subject').getText(); +// }, +// +// setTitle: function(title) { +// el.$('.e2e-detail-edit').click(); +// el.$('.e2e-title-input').clear().sendKeys(title); +// }, +// +// save: async function() { +// el.$('.e2e-title-button').click(); +// await browser.waitForAngular(); +// } +// }; +// +// return obj; +// }; + +// +// helper.getCreateIssueLightbox = function() { +// let el = $('div[tg-lb-create-issue]'); +// +// let obj = { +// el: el, +// waitOpen: function() { +// return utils.lightbox.open(el); +// }, +// waitClose: function() { +// return utils.lightbox.close(el); +// }, +// subject: function() { +// return el.$$('input').first(); +// }, +// tags: function() { +// return el.$('.tag-input'); +// }, +// submit: function() { +// el.$('button[type="submit"]').click(); +// } +// }; +// +// return obj; +// }; +// +// helper.getBulkCreateLightbox = function() { +// let el = $('div[tg-lb-create-bulk-issues]'); +// +// let obj = { +// el: el, +// waitOpen: function() { +// return utils.lightbox.open(el); +// }, +// textarea: function() { +// return el.$('textarea'); +// }, +// submit: function() { +// el.$('button[type="submit"]').click(); +// }, +// waitClose: function() { +// return utils.lightbox.close(el); +// } +// }; +// +// return obj; +// }; +// +// helper.openNewIssueLb = function() { +// $('.new-issue .button-green').click(); +// }; +// +// helper.openBulk = function() { +// $('.new-issue .button-bulk').click(); +// }; +// +// helper.clickColumn = function(index) { +// $$('.row.title > div').get(index).click(); +// }; +// +// helper.getTable = function() { +// return $('.basic-table'); +// }; +// +// helper.openAssignTo = function(index) { +// $$('.issue-assignedto').get(index).click(); +// }; +// +// helper.getAssignTo = function(index) { +// return $$('.assigned-field figcaption').get(index).getText(); +// }; +// +// helper.clickPagination = function(index) { +// $$('.paginator li').get(index).click(); +// }; +// +// helper.getIssues = function() { +// return $$('.row.table-main'); +// }; +// +// helper.parseIssue = async function(elm) { +// let obj = {}; +// +// obj.ref = await elm.$$('.subject span').get(0).getText(); +// obj.ref = obj.ref.replace('#', ''); +// obj.subject = await elm.$$('.subject span').get(1).getText(); +// +// return obj; +// }; diff --git a/e2e/suites/epics/epic-dashboard.e2e.js b/e2e/suites/epics/epic-dashboard.e2e.js new file mode 100644 index 00000000..369a906a --- /dev/null +++ b/e2e/suites/epics/epic-dashboard.e2e.js @@ -0,0 +1,67 @@ +var utils = require('../../utils'); +var epicsHelper = require('../../helpers/epics-helper'); + +var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); +var expect = chai.expect; + +describe('Epics Dashboard', function(){ + let usUrl = ''; + + before(async function(){ + await utils.nav + .init() + .project('Project Example 0') + .epics() + .go(); + + usUrl = await browser.getCurrentUrl(); + }); + + it('screenshot', async function() { + await utils.common.takeScreenshot("epics", "dashboard"); + }); + + it('display child stories', async function() { + let epic = epicsHelper.epic(); + let childStoriesNum = await epic.displayUserStoriesinEpic(); + expect(childStoriesNum).to.be.above(0); + }); + + it('change epic assigned from dashboard', async function() { + let epic = epicsHelper.epic(); + await epic.resetAssignedTo(); + let currentAssigned = await epic.getAssignedTo(); + await epic.editAssignedTo(); + let newAssigned = await epic.getAssignedTo(); + expect(currentAssigned).to.be.not.equal(newAssigned); + }); + + it('remove assigned from dashboard', async function() { + let epic = epicsHelper.epic(); + await epic.resetAssignedTo(); + let unAssigned = await epic.removeAssignedTo(); + console.log(unAssigned); + expect(unAssigned).to.be.equal('Unassigned'); + }); + + it('change status from dashboard', async function() { + let epic = epicsHelper.epic(); + await epic.resetStatus(); + let currentStatus = await epic.getStatus(); + await epic.editStatus(); + let newStatus = await epic.getStatus(); + expect(currentStatus).to.be.not.equal(newStatus); + }); + + it('remove columns from dashboard', async function() { + let epic = epicsHelper.epic(); + let currentColumns = await epic.getColumns(); + await epic.removeColumns(); + let newColumns = await epic.getColumns(); + expect(currentColumns).to.be.above(newColumns); + }); + +}) diff --git a/e2e/utils/nav.js b/e2e/utils/nav.js index 3712f716..17710dbe 100644 --- a/e2e/utils/nav.js +++ b/e2e/utils/nav.js @@ -46,6 +46,11 @@ var actions = { return common.waitLoader(); }, + epics: async function() { + await common.link($('#nav-epics a')); + + return common.waitLoader(); + }, backlog: async function() { await common.link($$('#nav-backlog a').first()); @@ -101,6 +106,10 @@ var nav = { this.actions.push(actions.issue.bind(null, index)); return this; }, + epics: function(index) { + this.actions.push(actions.epics.bind(null, index)); + return this; + }, backlog: function(index) { this.actions.push(actions.backlog.bind(null, index)); return this; From 5dc99133e5a5446404594a7b3058a0b355959a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 22 Aug 2016 08:30:24 +0200 Subject: [PATCH 187/315] Moved to common --- .../detail/header/detail-header.controller.coffee} | 0 .../detail/header/detail-header.controller.spec.coffee} | 0 .../detail/header/detail-header.directive.coffee} | 0 .../detail/header/detail-header.jade} | 0 .../detail/header/detail-header.scss} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename app/modules/{stories/header/story-header.controller.coffee => components/detail/header/detail-header.controller.coffee} (100%) rename app/modules/{stories/header/story-header.controller.spec.coffee => components/detail/header/detail-header.controller.spec.coffee} (100%) rename app/modules/{stories/header/story-header.directive.coffee => components/detail/header/detail-header.directive.coffee} (100%) rename app/modules/{stories/header/story-header.jade => components/detail/header/detail-header.jade} (100%) rename app/modules/{stories/header/story-header.scss => components/detail/header/detail-header.scss} (100%) diff --git a/app/modules/stories/header/story-header.controller.coffee b/app/modules/components/detail/header/detail-header.controller.coffee similarity index 100% rename from app/modules/stories/header/story-header.controller.coffee rename to app/modules/components/detail/header/detail-header.controller.coffee diff --git a/app/modules/stories/header/story-header.controller.spec.coffee b/app/modules/components/detail/header/detail-header.controller.spec.coffee similarity index 100% rename from app/modules/stories/header/story-header.controller.spec.coffee rename to app/modules/components/detail/header/detail-header.controller.spec.coffee diff --git a/app/modules/stories/header/story-header.directive.coffee b/app/modules/components/detail/header/detail-header.directive.coffee similarity index 100% rename from app/modules/stories/header/story-header.directive.coffee rename to app/modules/components/detail/header/detail-header.directive.coffee diff --git a/app/modules/stories/header/story-header.jade b/app/modules/components/detail/header/detail-header.jade similarity index 100% rename from app/modules/stories/header/story-header.jade rename to app/modules/components/detail/header/detail-header.jade diff --git a/app/modules/stories/header/story-header.scss b/app/modules/components/detail/header/detail-header.scss similarity index 100% rename from app/modules/stories/header/story-header.scss rename to app/modules/components/detail/header/detail-header.scss From d430587d31c9928647ac2a6aa2961a19cee0be9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 22 Aug 2016 12:53:18 +0200 Subject: [PATCH 188/315] Epic styles fixes for blocked --- app/modules/epics/create-epic/create-epic.jade | 3 +-- app/modules/epics/dashboard/epic-row/epic-row.scss | 1 + app/styles/dependencies/mixins/epics-dashboard.scss | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index c7ccf9c2..8fdd54a7 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -7,7 +7,7 @@ tg-lightbox-close ) fieldset // TODO ADD COLOR SELECTOR - //- tg-color-selector(on-select-dropdown-color="vm.newEpic.color = color") + tg-color-selector(on-select-dropdown-color="vm.newEpic.color = color") input( type="text" name="subject" @@ -89,4 +89,3 @@ tg-lightbox-close type="submit" translate="EPICS.CREATE.CREATE_EPIC" ) - diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 3cf55907..91dbda04 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -75,6 +75,7 @@ .progress-bar { background: $mass-white; max-width: 40vw; + padding-right: 1rem; width: 100%; } .progress-status { diff --git a/app/styles/dependencies/mixins/epics-dashboard.scss b/app/styles/dependencies/mixins/epics-dashboard.scss index c7e83fa4..75781d58 100644 --- a/app/styles/dependencies/mixins/epics-dashboard.scss +++ b/app/styles/dependencies/mixins/epics-dashboard.scss @@ -43,6 +43,8 @@ } .progress { flex-shrink: 3; + margin-right: 1rem; + position: relative; } .sprint { overflow: hidden; @@ -50,7 +52,4 @@ white-space: nowrap; width: 90%; } - .progress { - position: relative; - } } From 794ca91a7b71702e54118d3b1496e45e53b282bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 22 Aug 2016 12:54:06 +0200 Subject: [PATCH 189/315] Create epics unit test --- .../create-epic.controller.spec.coffee | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 app/modules/epics/create-epic/create-epic.controller.spec.coffee diff --git a/app/modules/epics/create-epic/create-epic.controller.spec.coffee b/app/modules/epics/create-epic/create-epic.controller.spec.coffee new file mode 100644 index 00000000..3453206d --- /dev/null +++ b/app/modules/epics/create-epic/create-epic.controller.spec.coffee @@ -0,0 +1,63 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: epic-row.controller.spec.coffee +### + +describe "EpicRow", -> + createEpicCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + epics: { + post: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + it "create Epic", (done) -> + createEpicCtrl = controller "CreateEpicCtrl" + createEpicCtrl.project = { + id: 7 + } + createEpicCtrl.newEpic = { + project: createEpicCtrl.project.id + } + createEpicCtrl.onReloadEpics = sinon.stub() + promise = mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().resolve() + + createEpicCtrl.createEpic().then () -> + expect(createEpicCtrl.onReloadEpics).have.been.called + done() From 3b1f7108953546fc778a2c96caf5181b1a0d6faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 22 Aug 2016 15:51:08 +0200 Subject: [PATCH 190/315] E2E tests for create epic --- .../epics/create-epic/create-epic.jade | 16 +++++++------- .../epics/dashboard/epics-dashboard.jade | 2 +- e2e/helpers/epics-helper.js | 21 ++++++++++++++++++- e2e/suites/epics/epic-dashboard.e2e.js | 11 ++++++++++ 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 8fdd54a7..7d7120ce 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -8,7 +8,7 @@ tg-lightbox-close fieldset // TODO ADD COLOR SELECTOR tg-color-selector(on-select-dropdown-color="vm.newEpic.color = color") - input( + input.e2e-create-epic-subject( type="text" name="subject" maxlength="140" @@ -18,7 +18,7 @@ tg-lightbox-close required ) fieldset - select( + select.e2e-create-epic-status( id="epic-status" name="status" ng-model="vm.newEpic.status" @@ -33,7 +33,7 @@ tg-lightbox-close ng-model="vm.newEpic.tags" ) fieldset - textarea( + textarea.e2e-create-epic-description( ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}" ng-model="vm.newEpic.description" ) @@ -49,7 +49,7 @@ tg-lightbox-close ng-model="vm.newEpic.team_requirement" id="team-requirement" ) - label.requirement.trans-button( + label.requirement.trans-button.e2e-create-epic-team-requirement( for="team-requirement" translate="EPICS.CREATE.TEAM_REQUIREMENT" ) @@ -60,7 +60,7 @@ tg-lightbox-close ng-model="vm.newEpic.client_requirement" id="client-requirement" ) - label.requirement.trans-button( + label.requirement.trans-button.e2e-create-epic-client-requirement( for="client-requirement" translate="EPICS.CREATE.CLIENT_REQUIREMENT" ) @@ -72,12 +72,12 @@ tg-lightbox-close id="blocked" ng-click="displayBlockedReason = !displayBlockedReason" ) - label.requirement.trans-button.blocked( + label.requirement.trans-button.blocked.e2e-create-epic-blocked( for="blocked" translate="EPICS.CREATE.BLOCKED" ) fieldset(ng-if="displayBlockedReason") - input( + input.e2e-create-epic-blocked-note( type="text" name="blocked_note" maxlength="140" @@ -85,7 +85,7 @@ tg-lightbox-close placeholder="{{'EPICS.CREATE.BLOCKED_NOTE_PLACEHOLDER' | translate}}" ) fieldset - input.button-green.create-epic-button( + input.button-green.create-epic-button.e2e-create-epic-button( type="submit" translate="EPICS.CREATE.CREATE_EPIC" ) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index dd9f88ee..69ff744d 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -8,7 +8,7 @@ i18n-section-name="{{ vm.sectionName }}" ) .action-buttons(ng-if="vm.epics.size") - button.button-green( + button.button-green.e2e-create-epic( translate="EPICS.DASHBOARD.ADD" title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", ng-click="vm.onCreateEpic()" diff --git a/e2e/helpers/epics-helper.js b/e2e/helpers/epics-helper.js index 4985b757..305f7a27 100644 --- a/e2e/helpers/epics-helper.js +++ b/e2e/helpers/epics-helper.js @@ -7,8 +7,24 @@ helper.epic = function() { let obj = { el: el, + getEpics: async function() { + return el.count(); + }, + createEpic: async function(date, description) { + $('.e2e-create-epic').click(); + utils.common.takeScreenshot("epics", "epics-create-epic"); + $('.e2e-create-epic-subject').clear().sendKeys(date + description); + $('.e2e-create-epic-status').click(); + $$('.e2e-create-epic-status > option').get(0).click(); + $('.e2e-create-epic-description').clear().sendKeys(date + description); + $('.e2e-create-epic-client-requirement').click(); + $('.e2e-create-epic-team-requirement').click(); + $('.e2e-create-epic-blocked').click(); + $('.e2e-create-epic-blocked-note').clear().sendKeys(date + description); + $('.e2e-create-epic-button').click(); + }, displayUserStoriesinEpic: async function() { - await utils.common.takeScreenshot("epics", "epics-child-closed"); + utils.common.takeScreenshot("epics", "epics-child-closed"); let storiesCount = await el.count(); let epicChildren; for (var i = 0; i < storiesCount; i++) { @@ -31,6 +47,7 @@ helper.epic = function() { }, editAssignedTo: async function() { el.get(0).$('.e2e-assigned-to-image').click(); + utils.common.takeScreenshot("epics", "epics-edit-assigned"); $$('.e2e-assigned-to-selector').last().click(); }, removeAssignedTo: async function() { @@ -47,6 +64,7 @@ helper.epic = function() { }, editStatus: function() { el.get(0).$('.e2e-epic-status').click(); + utils.common.takeScreenshot("epics", "epics-edit-status"); el.get(0).$$('.e2e-edit-epic-status').last().click(); }, getColumns: function() { @@ -54,6 +72,7 @@ helper.epic = function() { }, removeColumns: function() { $('.e2e-epics-column-button').click(); + utils.common.takeScreenshot("epics", "epics-edit-columns"); $$('.e2e-epics-column-dropdown .check').first().click(); } } diff --git a/e2e/suites/epics/epic-dashboard.e2e.js b/e2e/suites/epics/epic-dashboard.e2e.js index 369a906a..370cc6af 100644 --- a/e2e/suites/epics/epic-dashboard.e2e.js +++ b/e2e/suites/epics/epic-dashboard.e2e.js @@ -64,4 +64,15 @@ describe('Epics Dashboard', function(){ expect(currentColumns).to.be.above(newColumns); }); + it.only('create Epic', async function() { + let date = Date.now(); + let description = Math.random().toString(36).substring(7); + let epic = epicsHelper.epic(); + let currentEpicsNum = await epic.getEpics(); + await epic.createEpic(date, description); + let newEpicsNum = await epic.getEpics(); + console.log(currentEpicsNum, newEpicsNum); + expect(newEpicsNum).to.be.above(currentEpicsNum); + }); + }) From f84087a0efb711820eb43d52e2f6ac373864fbc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 23 Aug 2016 14:57:35 +0200 Subject: [PATCH 191/315] Fix new header directive URL --- .../belong-to-epics.directive.coffee | 4 +- .../header/detail-header.directive.coffee | 2 +- app/partials/issue/issues-detail.jade | 1 + app/partials/task/task-detail.jade | 59 +------------------ app/partials/us/us-detail.jade | 1 + 5 files changed, 7 insertions(+), 60 deletions(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index 455adc1d..1e7d8fa7 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -29,7 +29,9 @@ BelongToEpicsDirective = () -> scope.project = Immutable.fromJS(scope.project) scope.getTemplateUrl = () -> - return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" + if attrs.format + return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" + return "components/belong-to-epics/belong-to-epics-pill.html" return { link: link, diff --git a/app/modules/components/detail/header/detail-header.directive.coffee b/app/modules/components/detail/header/detail-header.directive.coffee index 81f0f9a4..10267d0f 100644 --- a/app/modules/components/detail/header/detail-header.directive.coffee +++ b/app/modules/components/detail/header/detail-header.directive.coffee @@ -36,7 +36,7 @@ DetailHeaderDirective = () -> requiredPerm: "@" }, controllerAs: "vm", - templateUrl:"stories/header/story-header.html" + templateUrl:"components/detail/header/detail-header.html" } diff --git a/app/partials/issue/issues-detail.jade b/app/partials/issue/issues-detail.jade index 771cf4fd..e85c9a3d 100644 --- a/app/partials/issue/issues-detail.jade +++ b/app/partials/issue/issues-detail.jade @@ -23,6 +23,7 @@ div.wrapper( required-perm="modify_issue" ng-class="{blocked: issue.is_blocked}" ng-if="project && issue" + format="text" ) .subheader tg-tag-line.tags-block( diff --git a/app/partials/task/task-detail.jade b/app/partials/task/task-detail.jade index 59b6e4be..2a331a5b 100644 --- a/app/partials/task/task-detail.jade +++ b/app/partials/task/task-detail.jade @@ -32,65 +32,8 @@ div.wrapper( required-perm="modify_task" ng-class="{blocked: task.is_blocked}" ng-if="project && task" + type="text" ) - //- h3.us-related-task(ng-if="us") - //- | {{ 'TASK.OWNER_US'|translate }} - //- a( - //- href="" - //- tg-check-permission="view_us" - //- tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" - //- title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" - //- ) - //- span(tg-bo-ref="us.ref") - //- span(tg-bo-bind="us.subject") - //- div.us-title(ng-class="{blocked: task.is_blocked}") - //- h2.us-title-text - //- span.us-number(tg-bo-ref="task.ref") - //- span.us-name( - //- tg-editable-subject - //- ng-model="task" - //- required-perm="modify_task" - //- ) - //- - //- h3.us-related-task(ng-if="us") - //- | {{ 'TASK.OWNER_US'|translate }} - //- a( - //- href="" - //- tg-check-permission="view_us" - //- tg-nav="project-userstories-detail:project=project.slug,ref=us.ref" - //- title="{{'TASK.TITLE_LINK_GO_OWNER' | translate}}" - //- ) - //- span(tg-bo-ref="us.ref") - //- span(tg-bo-bind="us.subject") - //- - //- p.external-reference(ng-if="task.external_reference") - //- a( - //- tg-bo-href="task.external_reference[1]", - //- target="_blank" - //- title="{{'TASK.TITLE_LINK_GO_ORIGIN' | translate}}" - //- ) - //- | {{ "TASK.ORIGIN_US"| translate }} - //- span {{ task.external_reference[1] }} - //- - //- p.block-desc-container(ng-show="task.is_blocked") - //- span.block-description-title(translate="COMMON.BLOCKED") - //- span.block-description( - //- ng-bind="task.blocked_note || ('TASK.BLOCKED_DESCRIPTION' | translate)" - //- ) - //- - //- div.issue-nav - //- a( - //- ng-show="previousUrl" - //- tg-bo-href="previousUrl" - //- title="{{'TASK.PREVIOUS' | translate}}" - //- ) - //- tg-svg(svg-icon="icon-arrow-left") - //- a( - //- ng-show="nextUrl" - //- tg-bo-href="nextUrl" - //- title="{{'TASK.NEXT' | translate}}" - //- ) - //- tg-svg(svg-icon="icon-arrow-right") .subheader tg-tag-line.tags-block( ng-if="task && project" diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index b35b512d..6c04fb9f 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -32,6 +32,7 @@ div.wrapper( required-perm="modify_us" ng-class="{blocked: us.is_blocked}" ng-if="project && us" + type="text" ) .subheader tg-tag-line.tags-block( From ad01386a797ae8158030d9c3fe238613d29bd058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 24 Aug 2016 14:36:16 +0200 Subject: [PATCH 192/315] Epic in Admin > Project > Default Values --- .../modules/admin/project-profile.coffee | 8 ++-- app/locales/taiga/locale-en.json | 9 +++-- .../modules/admin/default-values.jade | 40 ++++++++++++------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 06fba778..4b8c247d 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -89,16 +89,16 @@ class ProjectProfileController extends mixOf(taiga.Controller, taiga.PageMixin) @scope.projectId = project.id @scope.project = project - @scope.pointsList = _.sortBy(project.points, "order") + @scope.epicStatusList = _.sortBy(project.epic_statuses, "order") @scope.usStatusList = _.sortBy(project.us_statuses, "order") + @scope.pointsList = _.sortBy(project.points, "order") @scope.taskStatusList = _.sortBy(project.task_statuses, "order") - @scope.prioritiesList = _.sortBy(project.priorities, "order") - @scope.severitiesList = _.sortBy(project.severities, "order") @scope.issueTypesList = _.sortBy(project.issue_types, "order") @scope.issueStatusList = _.sortBy(project.issue_statuses, "order") + @scope.prioritiesList = _.sortBy(project.priorities, "order") + @scope.severitiesList = _.sortBy(project.severities, "order") @scope.$emit('project:loaded', project) - @scope.projectTags = _.map @scope.project.tags, (it) => return [it, @scope.project.tags_colors[it]] diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index c79fd001..2feafc66 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -720,13 +720,14 @@ "DEFAULT_DELETE_MESSAGE": "the invitation to {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Default value for points selector", - "LABEL_US": "Default value for US status selector", "LABEL_TASK_STATUS": "Default value for task status selector", - "LABEL_PRIORITY": "Default value for priority selector", - "LABEL_SEVERITY": "Default value for severity selector", "LABEL_ISSUE_TYPE": "Default value for issue type selector", - "LABEL_ISSUE_STATUS": "Default value for issue status selector" + "LABEL_ISSUE_STATUS": "Default value for issue status selector", + "LABEL_PRIORITY": "Default value for priority selector", + "LABEL_SEVERITY": "Default value for severity selector" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Write a name for the new status" diff --git a/app/partials/includes/modules/admin/default-values.jade b/app/partials/includes/modules/admin/default-values.jade index a8ab9e6e..cd3b3745 100644 --- a/app/partials/includes/modules/admin/default-values.jade +++ b/app/partials/includes/modules/admin/default-values.jade @@ -1,20 +1,40 @@ section.default-values form + + //- Epics + fieldset + label(for="default-value-epic", translate="ADMIN.DEFAULT_VALUES.LABEL_EPIC_STATUS") + select(id="default-value-epic", ng-model="project.default_epic_status", + ng-options="s.id as s.name for s in epicStatusList") + + //- User stories + fieldset + label(for="default-value-us", translate="ADMIN.DEFAULT_VALUES.LABEL_US_STATUS") + select(id="default-value-us", ng-model="project.default_us_status", + ng-options="s.id as s.name for s in usStatusList") + fieldset label(for="default-points", translate="ADMIN.DEFAULT_VALUES.LABEL_POINTS") select(id="default-points", ng-model="project.default_points", ng-options="s.id as s.name for s in pointsList") - fieldset - label(for="default-value-us", translate="ADMIN.DEFAULT_VALUES.LABEL_US") - select(id="default-value-us", ng-model="project.default_us_status", - ng-options="s.id as s.name for s in usStatusList") - + //- Tasks fieldset label(for="default-value-task", translate="ADMIN.DEFAULT_VALUES.LABEL_TASK_STATUS") select(id="default-value-task", ng-model="project.default_task_status", ng-options="s.id as s.name for s in taskStatusList") + //- Issues + fieldset + label(for="default-value-issue-type", translate="ADMIN.DEFAULT_VALUES.LABEL_ISSUE_TYPE") + select(id="default-value-issue-type", ng-model="project.default_issue_type", + ng-options="s.id as s.name for s in issueTypesList") + + fieldset + label(for="default-value-issue-status", translate="ADMIN.DEFAULT_VALUES.LABEL_ISSUE_STATUS") + select(id="default-value-issue-status", ng-model="project.default_issue_status", + ng-options="s.id as s.name for s in issueStatusList") + fieldset label(for="default-value-priority", translate="ADMIN.DEFAULT_VALUES.LABEL_PRIORITY") select(id="default-value-priority", ng-model="project.default_priority", @@ -25,16 +45,6 @@ section.default-values select(id="default-value-severity", ng-model="project.default_severity", ng-options="s.id as s.name for s in severitiesList") - fieldset - label(for="default-value-issue-type", translate="ADMIN.DEFAULT_VALUES.LABEL_ISSUE_TYPE") - select(id="default-value-issue-type", ng-model="project.default_issue_type", - ng-options="s.id as s.name for s in issueTypesList") - - fieldset - label(for="default-value-issue-status", translate="ADMIN.DEFAULT_VALUES.LABEL_ISSUE_STATUS") - select(id="default-value-issue-status", ng-model="project.default_issue_status", - ng-options="s.id as s.name for s in issueStatusList") - fieldset button.button-green.submit-button(type="submit", title="{{'COMMON.SAVE' | translate}}") span(translate="COMMON.SAVE") From a9fca5e43ba90c1ad815cebcbe661377f96938f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 24 Aug 2016 15:21:57 +0200 Subject: [PATCH 193/315] Epic in Admin > Project > Reports --- .../modules/admin/project-profile.coffee | 20 +++++++++++++++++++ app/coffee/modules/resources.coffee | 1 + app/coffee/modules/resources/projects.coffee | 12 +++++++---- app/locales/taiga/locale-en.json | 1 + app/partials/admin/admin-project-reports.jade | 1 + 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/admin/project-profile.coffee b/app/coffee/modules/admin/project-profile.coffee index 4b8c247d..65eb759c 100644 --- a/app/coffee/modules/admin/project-profile.coffee +++ b/app/coffee/modules/admin/project-profile.coffee @@ -425,6 +425,10 @@ class CsvExporterController extends taiga.Controller @._generateUuid() +class CsvExporterEpicsController extends CsvExporterController + type: "epics" + + class CsvExporterUserstoriesController extends CsvExporterController type: "userstories" @@ -437,6 +441,7 @@ class CsvExporterIssuesController extends CsvExporterController type: "issues" +module.controller("CsvExporterEpicsController", CsvExporterEpicsController) module.controller("CsvExporterUserstoriesController", CsvExporterUserstoriesController) module.controller("CsvExporterTasksController", CsvExporterTasksController) module.controller("CsvExporterIssuesController", CsvExporterIssuesController) @@ -446,6 +451,21 @@ module.controller("CsvExporterIssuesController", CsvExporterIssuesController) ## CSV Directive ############################################################################# +CsvEpicDirective = ($translate) -> + link = ($scope) -> + $scope.sectionTitle = "ADMIN.CSV.SECTION_TITLE_EPIC" + + return { + controller: "CsvExporterEpicsController", + controllerAs: "ctrl", + templateUrl: "admin/project-csv.html", + link: link, + scope: true + } + +module.directive("tgCsvEpic", ["$translate", CsvEpicDirective]) + + CsvUsDirective = ($translate) -> link = ($scope) -> $scope.sectionTitle = "ADMIN.CSV.SECTION_TITLE_US" diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index df127c5e..afb7043d 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -161,6 +161,7 @@ urls = { "webhooklogs-resend": "/webhooklogs/%s/resend" # Reports - CSV + "epics-csv": "/epics/csv?uuid=%s" "userstories-csv": "/userstories/csv?uuid=%s" "tasks-csv": "/tasks/csv?uuid=%s" "issues-csv": "/issues/csv?uuid=%s" diff --git a/app/coffee/modules/resources/projects.coffee b/app/coffee/modules/resources/projects.coffee index 108012cd..dcb48a72 100644 --- a/app/coffee/modules/resources/projects.coffee +++ b/app/coffee/modules/resources/projects.coffee @@ -61,18 +61,22 @@ resourceProvider = ($config, $repo, $http, $urls, $auth, $q, $translate) -> url = $urls.resolve("bulk-update-projects-order") return $http.post(url, bulkData) + service.regenerate_epics_csv_uuid = (projectId) -> + url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_epics_csv_uuid" + return $http.post(url) + service.regenerate_userstories_csv_uuid = (projectId) -> url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_userstories_csv_uuid" return $http.post(url) - service.regenerate_issues_csv_uuid = (projectId) -> - url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_issues_csv_uuid" - return $http.post(url) - service.regenerate_tasks_csv_uuid = (projectId) -> url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_tasks_csv_uuid" return $http.post(url) + service.regenerate_issues_csv_uuid = (projectId) -> + url = "#{$urls.resolve("projects")}/#{projectId}/regenerate_issues_csv_uuid" + return $http.post(url) + service.leave = (projectId) -> url = "#{$urls.resolve("projects")}/#{projectId}/leave" return $http.post(url) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 2feafc66..5e8d7ed9 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -559,6 +559,7 @@ "REGENERATE_SUBTITLE": "You going to change the CSV data access url. The previous url will be disabled. Are you sure?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "user stories reports", "SECTION_TITLE_TASK": "tasks reports", "SECTION_TITLE_ISSUE": "issues reports", diff --git a/app/partials/admin/admin-project-reports.jade b/app/partials/admin/admin-project-reports.jade index 1ad4c439..a3e669b9 100644 --- a/app/partials/admin/admin-project-reports.jade +++ b/app/partials/admin/admin-project-reports.jade @@ -18,6 +18,7 @@ div.wrapper( p(translate="ADMIN.REPORTS.DESCRIPTION") + div.admin-attributes-section(tg-csv-epic) div.admin-attributes-section(tg-csv-us) div.admin-attributes-section(tg-csv-task) div.admin-attributes-section(tg-csv-issue) From 605ff1480e9e92085c39b3fde5a3cccc5f3672d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 24 Aug 2016 16:52:46 +0200 Subject: [PATCH 194/315] Epic in Admin > Attributes > Status --- app/coffee/modules/resources.coffee | 2 + app/coffee/modules/resources/epics.coffee | 55 +++++++++++++++++++ app/locales/taiga/locale-en.json | 3 +- .../admin/admin-project-values-status.jade | 6 ++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 app/coffee/modules/resources/epics.coffee diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index afb7043d..ae917665 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -81,6 +81,7 @@ urls = { "project-transfer-start": "/projects/%s/transfer_start" # Project Values - Choises + "epic-statuses": "/epic-statuses" "userstory-statuses": "/userstory-statuses" "points": "/points" "task-statuses": "/task-statuses" @@ -223,6 +224,7 @@ module.run([ "$tgRolesResourcesProvider", "$tgUserSettingsResourcesProvider", "$tgSprintsResourcesProvider", + "$tgEpicsResourcesProvider", "$tgUserstoriesResourcesProvider", "$tgTasksResourcesProvider", "$tgIssuesResourcesProvider", diff --git a/app/coffee/modules/resources/epics.coffee b/app/coffee/modules/resources/epics.coffee new file mode 100644 index 00000000..9793f485 --- /dev/null +++ b/app/coffee/modules/resources/epics.coffee @@ -0,0 +1,55 @@ +### +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino Garcia +# Copyright (C) 2014-2016 David Barragán Merino +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Juan Francisco Alcántara +# Copyright (C) 2014-2016 Xavi Julian +# +# 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/epics.coffee +### + + +taiga = @.taiga + +generateHash = taiga.generateHash + + +resourceProvider = ($repo, $storage) -> + service = {} + hashSuffix = "epics-queryparams" + + service.listValues = (projectId, type) -> + params = {"project": projectId} + service.storeQueryParams(projectId, params) + return $repo.queryMany(type, params) + + service.storeQueryParams = (projectId, params) -> + ns = "#{projectId}:#{hashSuffix}" + hash = generateHash([projectId, ns]) + $storage.set(hash, params) + + service.getQueryParams = (projectId) -> + ns = "#{projectId}:#{hashSuffix}" + hash = generateHash([projectId, ns]) + return $storage.get(hash) or {} + + return (instance) -> + instance.epics = service + + +module = angular.module("taigaResources") +module.factory("$tgEpicsResourcesProvider", ["$tgRepo", "$tgStorage", resourceProvider]) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 5e8d7ed9..7fa886c2 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -609,7 +609,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Specify the statuses your user stories, tasks and issues will go through", - "US_TITLE": "US Statuses", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Task Statuses", "ISSUE_TITLE": "Issue Statuses" }, diff --git a/app/partials/admin/admin-project-values-status.jade b/app/partials/admin/admin-project-values-status.jade index 8eab5f9b..64540f3a 100644 --- a/app/partials/admin/admin-project-values-status.jade +++ b/app/partials/admin/admin-project-values-status.jade @@ -15,6 +15,12 @@ div.wrapper(ng-controller="ProjectValuesSectionController", include ../includes/components/mainTitle p.admin-subtitle(translate="ADMIN.PROJECT_VALUES_STATUS.SUBTITLE") + div.admin-attributes-section(tg-project-values, type="epic-statuses", + ng-controller="ProjectValuesController as ctrl", + ng-init="section='admin'; resource='epics'; type='epic-statuses'; sectionName='ADMIN.PROJECT_VALUES_STATUS.EPIC_TITLE'" + objName="status") + include ../includes/modules/admin/project-status + div.admin-attributes-section(tg-project-values, type="userstory-statuses", ng-controller="ProjectValuesController as ctrl", ng-init="section='admin'; resource='userstories'; type='userstory-statuses'; sectionName='ADMIN.PROJECT_VALUES_STATUS.US_TITLE'", From fbfde871e2992cc64c371a5490e9d597dccede4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 24 Aug 2016 17:04:43 +0200 Subject: [PATCH 195/315] Epic in Admin > Attributes > Custom Fields --- app/coffee/modules/resources.coffee | 6 ++++-- .../modules/resources/custom-attributes-values.coffee | 3 +++ app/coffee/modules/resources/custom-attributes.coffee | 3 +++ app/locales/taiga/locale-en.json | 2 ++ app/partials/admin/admin-project-values-custom-fields.jade | 7 +++++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index ae917665..9085f7da 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -146,14 +146,16 @@ urls = { "attachments/wiki_page": "/wiki/attachments" # Custom Attributess + "custom-attributes/epic": "/epic-custom-attributes" "custom-attributes/userstory": "/userstory-custom-attributes" - "custom-attributes/issue": "/issue-custom-attributes" "custom-attributes/task": "/task-custom-attributes" + "custom-attributes/issue": "/issue-custom-attributes" # Custom Attributess - Values + "custom-attributes-values/epic": "/epics/custom-attributes-values" "custom-attributes-values/userstory": "/userstories/custom-attributes-values" - "custom-attributes-values/issue": "/issues/custom-attributes-values" "custom-attributes-values/task": "/tasks/custom-attributes-values" + "custom-attributes-values/issue": "/issues/custom-attributes-values" # Webhooks "webhooks": "/webhooks" diff --git a/app/coffee/modules/resources/custom-attributes-values.coffee b/app/coffee/modules/resources/custom-attributes-values.coffee index f5a38b2c..904d506e 100644 --- a/app/coffee/modules/resources/custom-attributes-values.coffee +++ b/app/coffee/modules/resources/custom-attributes-values.coffee @@ -29,6 +29,9 @@ resourceProvider = ($repo) -> return $repo.queryOne(resource, objectId) service = { + epic: { + get: (objectId) -> _get(objectId, "custom-attributes-values/epic") + } userstory: { get: (objectId) -> _get(objectId, "custom-attributes-values/userstory") } diff --git a/app/coffee/modules/resources/custom-attributes.coffee b/app/coffee/modules/resources/custom-attributes.coffee index 520ec2d2..88ae4872 100644 --- a/app/coffee/modules/resources/custom-attributes.coffee +++ b/app/coffee/modules/resources/custom-attributes.coffee @@ -32,6 +32,9 @@ resourceProvider = ($repo) -> return $repo.queryMany(resource, {project: projectId}) service = { + epic:{ + list: (projectId) -> _list(projectId, "custom-attributes/epic") + } userstory:{ list: (projectId) -> _list(projectId, "custom-attributes/userstory") } diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 7fa886c2..298d93a3 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -572,6 +572,8 @@ "CUSTOM_FIELDS": { "TITLE": "Custom Fields", "SUBTITLE": "Specify the custom fields for your user stories, tasks and issues", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "User stories custom fields", "US_ADD": "Add a custom field in user stories", "TASK_DESCRIPTION": "Tasks custom fields", diff --git a/app/partials/admin/admin-project-values-custom-fields.jade b/app/partials/admin/admin-project-values-custom-fields.jade index 691079b5..19cb292b 100644 --- a/app/partials/admin/admin-project-values-custom-fields.jade +++ b/app/partials/admin/admin-project-values-custom-fields.jade @@ -17,6 +17,13 @@ div.wrapper( include ../includes/components/mainTitle p.admin-subtitle(translate="ADMIN.CUSTOM_FIELDS.SUBTITLE") + div.admin-attributes-section( + tg-project-custom-attributes, + ng-controller="ProjectCustomAttributesController as ctrl", + ng-init="type='epic'; customFieldSectionTitle='ADMIN.CUSTOM_FIELDS.EPIC_DESCRIPTION'; customFieldButtonTitle='ADMIN.CUSTOM_FIELDS.EPIC_ADD'" + ) + include ../includes/modules/admin/admin-custom-attributes + div.admin-attributes-section( tg-project-custom-attributes, ng-controller="ProjectCustomAttributesController as ctrl", From dcd26a954ec45d2908d15193aeca5bb6540f8c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 24 Aug 2016 19:14:33 +0200 Subject: [PATCH 196/315] Epic in Admin > Permissions --- app/coffee/modules/admin/roles.coffee | 12 ++++++++++++ app/locales/taiga/locale-en.json | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/app/coffee/modules/admin/roles.coffee b/app/coffee/modules/admin/roles.coffee index f1815b99..c282f7c6 100644 --- a/app/coffee/modules/admin/roles.coffee +++ b/app/coffee/modules/admin/roles.coffee @@ -353,6 +353,18 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm, $compile) -> categories = [] + epicPermissions = [ + { key: "view_epics", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.VIEW_EPICS"} + { key: "add_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.ADD_EPICS"} + { key: "modify_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.MODIFY_EPICS"} + { key: "comment_epic", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.COMMENT_EPICS"} + { key: "delete_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.DELETE_EPICS"} + ] + categories.push({ + name: "COMMON.PERMISIONS_CATEGORIES.EPICS.NAME" , + permissions: setActivePermissions(epicPermissions) + }) + milestonePermissions = [ { key: "view_milestones", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.VIEW_SPRINTS"} { key: "add_milestone", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.ADD_SPRINTS"} diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 298d93a3..3403ff03 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -256,6 +256,14 @@ "MARKDOWN_HELP": "Markdown syntax help" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "View sprints", From f8dd7408d2eee562c50964329f96869adf9268f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 25 Aug 2016 15:24:57 +0200 Subject: [PATCH 197/315] Use new tag-line-common component in Add-Epic form --- .../tags/color-selector/color-selector.jade | 2 +- .../tag-line-common.directive.coffee | 2 +- .../create-epic/create-epic.controller.coffee | 23 ++++++++++++++++++- .../epics/create-epic/create-epic.jade | 22 +++++++++++------- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/app/modules/components/tags/color-selector/color-selector.jade b/app/modules/components/tags/color-selector/color-selector.jade index c66e83aa..54402386 100644 --- a/app/modules/components/tags/color-selector/color-selector.jade +++ b/app/modules/components/tags/color-selector/color-selector.jade @@ -4,7 +4,7 @@ ng-class="{'empty-color': !vm.color}" ng-style="{'background': vm.color}" ) - .color-selector-dropdown(ng-show="vm.displaycolorList") + .color-selector-dropdown(ng-if="vm.displaycolorList") ul.color-selector-dropdown-list.e2e-color-dropdown li.color-selector-option( ng-repeat="color in vm.colorList" diff --git a/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee b/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee index 536c229a..1fd9e8f6 100644 --- a/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee +++ b/app/modules/components/tags/tag-line-common/tag-line-common.directive.coffee @@ -33,7 +33,7 @@ TagLineCommonDirective = () -> ctrl.colorArray = ctrl._createColorsArray(ctrl.project.tags_colors) el.on "keydown", ".tag-input", (event) -> - if event.keyCode == 27 && ctrl.newTag.name.length + if event.keyCode == 27 ctrl.addTag = false ctrl.newTag.name = "" diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index c80ef89c..638a2480 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -17,6 +17,9 @@ # File: create-epic.controller.coffee ### +taiga = @.taiga +trim = taiga.trim + module = angular.module("taigaEpics") class CreateEpicController @@ -25,11 +28,29 @@ class CreateEpicController ] constructor: (@rs) -> + @.newEpic = { + color: null + projecti: @.project.id + status: @.project.default_epic_status + tags: [] + } @.attachments = Immutable.List() createEpic: () -> - @.newEpic.project = @.project.id return @rs.epics.post(@.newEpic).then () => @.onReloadEpics() + selectColor: (color) -> + @.newEpic.color = color + + addTag: (name, color) -> + name = trim(name.toLowerCase()) + + if not _.find(@.newEpic.tags, (it) -> it[0] == name) + @.newEpic.tags.push([name, color]) + + deleteTag: (tag) -> + _.remove @.newEpic.tags, (it) -> it[0] == tag[0] + + module.controller("CreateEpicCtrl", CreateEpicController) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 7d7120ce..7d1e7e73 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -6,8 +6,10 @@ tg-lightbox-close ng-submit="vm.createEpic()" ) fieldset - // TODO ADD COLOR SELECTOR - tg-color-selector(on-select-dropdown-color="vm.newEpic.color = color") + tg-color-selector( + color="vm.newEpic.color", + on-select-color="vm.selectColor(color)" + ) input.e2e-create-epic-subject( type="text" name="subject" @@ -25,13 +27,17 @@ tg-lightbox-close ) option( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" - ng-value="status.id" + ng-value="::status.id" ng-selected="vm.project.default_epic_status" - ) {{status.name}} - fieldset.tags-block( - tg-lb-tag-line - ng-model="vm.newEpic.tags" - ) + ) {{::status.name}} + fieldset.tags-block + tg-tag-line-common( + project="vm.project" + tags="vm.newEpic.tags" + permissions="add_epic" + on-add-tag="vm.addTag(name, color)" + on-delete-tag="vm.deleteTag(tag)" + ) fieldset textarea.e2e-create-epic-description( ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}" From 3d858cf82aedc6006a987b4cd0b28dd39fd39598 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 26 Aug 2016 12:11:12 +0200 Subject: [PATCH 198/315] Epic detail --- app/coffee/app.coffee | 14 +- app/coffee/modules/common/filters.coffee | 26 ++ app/coffee/modules/epics.coffee | 2 +- app/coffee/modules/epics/detail.coffee | 337 ++++++++++++++++++ app/coffee/modules/resources.coffee | 8 + app/coffee/modules/resources/epics.coffee | 26 +- app/locales/taiga/locale-en.json | 21 ++ .../belong-to-epics/belong-to-epics-text.jade | 2 +- .../belong-to-epics.directive.coffee | 3 - .../detail/header/detail-header.jade | 2 - .../watch-button.controller.coffee | 3 +- .../epics/dashboard/epic-row/epic-row.jade | 2 +- .../related-userstories-controller.coffee | 33 ++ ...lated-userstories-create.controller.coffee | 92 +++++ ...-userstories-create.controller.spec.coffee | 185 ++++++++++ ...elated-userstories-create.directive.coffee | 79 ++++ .../related-userstories-create.jade | 153 ++++++++ .../related-userstories-create.scss | 78 ++++ ...related-userstories.controller.spec.coffee | 66 ++++ .../related-userstories.directive.coffee | 37 ++ .../related-userstories.jade | 23 ++ .../related-userstories.scss | 147 ++++++++ .../related-userstory-row.controller.coffee | 63 ++++ ...lated-userstory-row.controller.spec.coffee | 169 +++++++++ .../related-userstory-row.directive.coffee | 42 +++ .../related-userstory-row.jade | 44 +++ .../resources/epics-resource.service.coffee | 25 ++ .../userstories-resource.service.coffee | 16 + app/partials/epic/epic-detail.jade | 127 +++++++ app/styles/modules/common/wizard.scss | 1 - conf.e2e.js | 82 ++--- e2e/helpers/detail-helper.js | 42 ++- e2e/helpers/epic-detail-helper.js | 76 ++++ ...cs-helper.js => epics-dashboard-helper.js} | 13 +- e2e/helpers/index.js | 2 + e2e/helpers/us-detail-helper.js | 39 -- e2e/shared/detail.js | 38 +- .../admin/attributes/custom-fields.e2e.js | 63 +++- e2e/suites/admin/members.e2e.js | 2 +- e2e/suites/epics/epic-dashboard.e2e.js | 38 +- e2e/suites/epics/epic-detail.e2e.js | 100 ++++++ .../user-stories/user-story-detail.e2e.js | 30 +- e2e/utils/nav.js | 14 + gulpfile.js | 1 + run-e2e.js | 1 + 45 files changed, 2214 insertions(+), 153 deletions(-) create mode 100644 app/coffee/modules/epics/detail.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories-controller.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.spec.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.directive.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade create mode 100644 app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss create mode 100644 app/modules/epics/related-userstories/related-userstories.controller.spec.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories.directive.coffee create mode 100644 app/modules/epics/related-userstories/related-userstories.jade create mode 100644 app/modules/epics/related-userstories/related-userstories.scss create mode 100644 app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee create mode 100644 app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee create mode 100644 app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.directive.coffee create mode 100644 app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade create mode 100644 app/partials/epic/epic-detail.jade create mode 100644 e2e/helpers/epic-detail-helper.js rename e2e/helpers/{epics-helper.js => epics-dashboard-helper.js} (94%) create mode 100644 e2e/suites/epics/epic-detail.e2e.js diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 97c3efbd..fb81877e 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -46,7 +46,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $animateProvider.classNameFilter(/^(?:(?!ng-animate-disabled).)*$/) - # wait until the trasnlation is ready to resolve the page + # wait until the translation is ready to resolve the page originalWhen = $routeProvider.when $routeProvider.when = (path, route) -> @@ -162,6 +162,15 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) + # Epics + $routeProvider.when("/project/:pslug/epic/:epicref", + { + templateUrl: "epic/epic-detail.html", + loader: true, + section: "epics" + } + ) + $routeProvider.when("/project/:pslug/backlog", { templateUrl: "backlog/backlog.html", @@ -793,6 +802,7 @@ modules = [ "taigaPlugins", "taigaIntegrations", "taigaComponents", + # new modules "taigaProfile", "taigaHome", @@ -801,7 +811,7 @@ modules = [ "taigaDiscover", "taigaHistory", "taigaWikiHistory", - 'taigaEpics', + "taigaEpics", # template cache "templates", diff --git a/app/coffee/modules/common/filters.coffee b/app/coffee/modules/common/filters.coffee index 7590b45c..17696c55 100644 --- a/app/coffee/modules/common/filters.coffee +++ b/app/coffee/modules/common/filters.coffee @@ -74,3 +74,29 @@ sizeFormat = => return @.taiga.sizeFormat module.filter("sizeFormat", sizeFormat) + + +toMutableFilter = -> + toMutable = (js) -> + return js.toJS() + + memoizedMutable = _.memoize(toMutable) + + return (input) -> + if input instanceof Immutable.List + return memoizedMutable(input) + + return input + +module.filter("toMutable", toMutableFilter) + + +byRefFilter = ($filterFilter)-> + return (userstories, filter) -> + if filter?.startsWith("#") + cleanRef= filter.substr(1) + return _.filter(userstories, (us) => String(us.ref).startsWith(cleanRef)) + + return $filterFilter(userstories, filter) + +module.filter("byRef", ["filterFilter", byRefFilter]) diff --git a/app/coffee/modules/epics.coffee b/app/coffee/modules/epics.coffee index 15941253..743e70d4 100644 --- a/app/coffee/modules/epics.coffee +++ b/app/coffee/modules/epics.coffee @@ -19,7 +19,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # -# File: modules/projects.coffee +# File: modules/epics.coffee ### module = angular.module("taigaEpics", []) diff --git a/app/coffee/modules/epics/detail.coffee b/app/coffee/modules/epics/detail.coffee new file mode 100644 index 00000000..f5a35849 --- /dev/null +++ b/app/coffee/modules/epics/detail.coffee @@ -0,0 +1,337 @@ +### +# Copyright (C) 2014-2016 Andrey Antukh +# Copyright (C) 2014-2016 Jesús Espino Garcia +# Copyright (C) 2014-2016 David Barragán Merino +# Copyright (C) 2014-2016 Alejandro Alonso +# Copyright (C) 2014-2016 Juan Francisco Alcántara +# Copyright (C) 2014-2016 Xavi Julian +# +# 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/epics/detail.coffee +### + +taiga = @.taiga + +mixOf = @.taiga.mixOf +toString = @.taiga.toString +joinStr = @.taiga.joinStr +groupBy = @.taiga.groupBy +bindOnce = @.taiga.bindOnce +bindMethods = @.taiga.bindMethods + +module = angular.module("taigaEpics") + +############################################################################# +## Epic Detail Controller +############################################################################# + +class EpicDetailController extends mixOf(taiga.Controller, taiga.PageMixin) + @.$inject = [ + "$scope", + "$rootScope", + "$tgRepo", + "$tgConfirm", + "$tgResources", + "tgResources" + "$routeParams", + "$q", + "$tgLocation", + "$log", + "tgAppMetaService", + "$tgAnalytics", + "$tgNavUrls", + "$translate", + "$tgQueueModelTransformation", + "tgErrorHandlingService" + ] + + constructor: (@scope, @rootscope, @repo, @confirm, @rs, @rs2, @params, @q, @location, + @log, @appMetaService, @analytics, @navUrls, @translate, @modelTransform, @errorHandlingService) -> + bindMethods(@) + + @scope.epicRef = @params.epicref + @scope.sectionName = @translate.instant("EPIC.SECTION_NAME") + @.initializeEventHandlers() + + promise = @.loadInitialData() + + # On Success + promise.then => + @._setMeta() + @.initializeOnDeleteGoToUrl() + + # On Error + promise.then null, @.onInitialDataError.bind(@) + + _setMeta: -> + title = @translate.instant("EPIC.PAGE_TITLE", { + epicRef: "##{@scope.epic.ref}" + epicSubject: @scope.epic.subject + projectName: @scope.project.name + }) + description = @translate.instant("EPIC.PAGE_DESCRIPTION", { + epicStatus: @scope.statusById[@scope.epic.status]?.name or "--" + epicDescription: angular.element(@scope.epic.description_html or "").text() + }) + @appMetaService.setAll(title, description) + + initializeEventHandlers: -> + @scope.$on "attachment:create", => + @analytics.trackEvent("attachment", "create", "create attachment on epic", 1) + + @scope.$on "comment:new", => + @.loadEpic() + + @scope.$on "custom-attributes-values:edit", => + @rootscope.$broadcast("object:updated") + + initializeOnDeleteGoToUrl: -> + ctx = {project: @scope.project.slug} + @scope.onDeleteGoToUrl = @navUrls.resolve("project-epics", ctx) + + loadProject: -> + return @rs.projects.getBySlug(@params.pslug).then (project) => + @scope.projectId = project.id + @scope.project = project + @scope.immutableProject = Immutable.fromJS(project._attrs) + @scope.$emit('project:loaded', project) + @scope.statusList = project.epic_statuses + @scope.statusById = groupBy(project.epic_statuses, (x) -> x.id) + return project + + loadEpic: -> + return @rs.epics.getByRef(@scope.projectId, @params.epicref).then (epic) => + @scope.epic = epic + @scope.immutableEpic = Immutable.fromJS(epic._attrs) + @scope.epicId = epic.id + @scope.commentModel = epic + + @modelTransform.setObject(@scope, 'epic') + + if @scope.epic.neighbors.previous?.ref? + ctx = { + project: @scope.project.slug + ref: @scope.epic.neighbors.previous.ref + } + @scope.previousUrl = @navUrls.resolve("project-epics-detail", ctx) + + if @scope.epic.neighbors.next?.ref? + ctx = { + project: @scope.project.slug + ref: @scope.epic.neighbors.next.ref + } + @scope.nextUrl = @navUrls.resolve("project-epics-detail", ctx) + + loadUserstories: -> + return @rs2.userstories.listInEpic(@scope.epicId).then (data) => + @scope.userstories = data + + loadInitialData: -> + promise = @.loadProject() + return promise.then (project) => + @.fillUsersAndRoles(project.members, project.roles) + @.loadEpic().then(=> @.loadUserstories()) + + ### + # Note: This methods (onUpvote() and onDownvote()) are related to tg-vote-button. + # See app/modules/components/vote-button for more info + ### + onUpvote: -> + onSuccess = => + @.loadEpic() + @rootscope.$broadcast("object:updated") + onError = => + @confirm.notify("error") + + return @rs.epics.upvote(@scope.epicId).then(onSuccess, onError) + + onDownvote: -> + onSuccess = => + @.loadEpic() + @rootscope.$broadcast("object:updated") + onError = => + @confirm.notify("error") + + return @rs.epics.downvote(@scope.epicId).then(onSuccess, onError) + + ### + # Note: This methods (onWatch() and onUnwatch()) are related to tg-watch-button. + # See app/modules/components/watch-button for more info + ### + onWatch: -> + onSuccess = => + @.loadEpic() + @rootscope.$broadcast("object:updated") + onError = => + @confirm.notify("error") + + return @rs.epics.watch(@scope.epicId).then(onSuccess, onError) + + onUnwatch: -> + onSuccess = => + @.loadEpic() + @rootscope.$broadcast("object:updated") + onError = => + @confirm.notify("error") + + return @rs.epics.unwatch(@scope.epicId).then(onSuccess, onError) + + onSelectColor: (color) -> + onSelectColorSuccess = () => + @rootscope.$broadcast("object:updated") + @confirm.notify('success') + + onSelectColorError = () => + @confirm.notify('error') + + transform = @modelTransform.save (epic) -> + epic.color = color + return epic + + return transform.then(onSelectColorSuccess, onSelectColorError) + +module.controller("EpicDetailController", EpicDetailController) + + +############################################################################# +## Epic status display directive +############################################################################# + +EpicStatusDisplayDirective = ($template, $compile) -> + # Display if an epic is open or closed and its status. + # + # Example: + # tg-epic-status-display(ng-model="epic") + # + # Requirements: + # - Epic object (ng-model) + # - scope.statusById object + + template = $template.get("common/components/status-display.html", true) + + link = ($scope, $el, $attrs) -> + render = (epic) -> + status = $scope.statusById[epic.status] + + html = template({ + is_closed: status.is_closed + status: status + }) + + html = $compile(html)($scope) + $el.html(html) + + $scope.$watch $attrs.ngModel, (epic) -> + render(epic) if epic? + + $scope.$on "$destroy", -> + $el.off() + + return { + link: link + restrict: "EA" + require: "ngModel" + } + +module.directive("tgEpicStatusDisplay", ["$tgTemplate", "$compile", EpicStatusDisplayDirective]) + + +############################################################################# +## Epic status button directive +############################################################################# + +EpicStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $modelTransform, $compile, $translate, $template) -> + # Display the status of epic and you can edit it. + # + # Example: + # tg-epic-status-button(ng-model="epic") + # + # Requirements: + # - Epic object (ng-model) + # - scope.statusById object + # - $scope.project.my_permissions + + template = $template.get("common/components/status-button.html", true) + + link = ($scope, $el, $attrs, $model) -> + isEditable = -> + return $scope.project.my_permissions.indexOf("modify_epic") != -1 + + render = (epic) => + status = $scope.statusById[epic.status] + + html = $compile(template({ + status: status + statuses: $scope.statusList + editable: isEditable() + }))($scope) + + $el.html(html) + + save = (status) -> + currentLoading = $loading() + .target($el) + .start() + + transform = $modelTransform.save (epic) -> + epic.status = status + + return epic + + onSuccess = -> + $rootScope.$broadcast("object:updated") + currentLoading.finish() + + onError = -> + $confirm.notify("error") + currentLoading.finish() + + transform.then(onSuccess, onError) + + $el.on "click", ".js-edit-status", (event) -> + event.preventDefault() + event.stopPropagation() + return if not isEditable() + + $el.find(".pop-status").popover().open() + + $el.on "click", ".status", (event) -> + event.preventDefault() + event.stopPropagation() + return if not isEditable() + + target = angular.element(event.currentTarget) + + $.fn.popover().closeAll() + + save(target.data("status-id")) + + $scope.$watch () -> + return $model.$modelValue?.status + , () -> + epic = $model.$modelValue + render(epic) if epic + + $scope.$on "$destroy", -> + $el.off() + + return { + link: link + restrict: "EA" + require: "ngModel" + } + +module.directive("tgEpicStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQueueModelTransformation", + "$compile", "$translate", "$tgTemplate", EpicStatusButtonDirective]) diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 9085f7da..20b9fa9a 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -95,6 +95,12 @@ urls = { # Epics "epics": "/epics" + "epic-upvote": "/epics/%s/upvote" + "epic-downvote": "/epics/%s/downvote" + "epic-watch": "/epics/%s/watch" + "epic-unwatch": "/epics/%s/unwatch" + "epic-related-userstories": "/epics/%s/related_userstories" + "epic-related-userstories-bulk-create": "/epics/%s/related_userstories/bulk_create" # User stories "userstories": "/userstories" @@ -134,12 +140,14 @@ urls = { "wiki-links": "/wiki-links" # History + "history/epic": "/history/epic" "history/us": "/history/userstory" "history/issue": "/history/issue" "history/task": "/history/task" "history/wiki": "/history/wiki/%s" # Attachments + "attachments/epic": "/epics/attachments" "attachments/us": "/userstories/attachments" "attachments/issue": "/issues/attachments" "attachments/task": "/tasks/attachments" diff --git a/app/coffee/modules/resources/epics.coffee b/app/coffee/modules/resources/epics.coffee index 9793f485..480395ce 100644 --- a/app/coffee/modules/resources/epics.coffee +++ b/app/coffee/modules/resources/epics.coffee @@ -28,10 +28,16 @@ taiga = @.taiga generateHash = taiga.generateHash -resourceProvider = ($repo, $storage) -> +resourceProvider = ($repo, $http, $urls, $storage) -> service = {} hashSuffix = "epics-queryparams" + service.getByRef = (projectId, ref) -> + params = service.getQueryParams(projectId) + params.project = projectId + params.ref = ref + return $repo.queryOne("epics", "by_ref", params) + service.listValues = (projectId, type) -> params = {"project": projectId} service.storeQueryParams(projectId, params) @@ -47,9 +53,25 @@ resourceProvider = ($repo, $storage) -> hash = generateHash([projectId, ns]) return $storage.get(hash) or {} + service.upvote = (epicId) -> + url = $urls.resolve("epic-upvote", epicId) + return $http.post(url) + + service.downvote = (epicId) -> + url = $urls.resolve("epic-downvote", epicId) + return $http.post(url) + + service.watch = (epicId) -> + url = $urls.resolve("epic-watch", epicId) + return $http.post(url) + + service.unwatch = (epicId) -> + url = $urls.resolve("epic-unwatch", epicId) + return $http.post(url) + return (instance) -> instance.epics = service module = angular.module("taigaResources") -module.factory("$tgEpicsResourcesProvider", ["$tgRepo", "$tgStorage", resourceProvider]) +module.factory("$tgEpicsResourcesProvider", ["$tgRepo","$tgHttp", "$tgUrls", "$tgStorage", resourceProvider]) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 3403ff03..cd73c545 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -47,6 +47,7 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", "CARD": { "ASSIGN_TO": "Assign To", "EDIT": "Edit card" @@ -1061,6 +1062,26 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with a user story", + "RELATED_WITH": "Related with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "Whats' the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}", diff --git a/app/modules/components/belong-to-epics/belong-to-epics-text.jade b/app/modules/components/belong-to-epics/belong-to-epics-text.jade index f8b935d0..db9614b9 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics-text.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics-text.jade @@ -6,5 +6,5 @@ span.belong-to-epic-text-wrapper(tg-repeat="epic in epics track by epic.get('id' ) a.belong-to-epic-text( href="" - tg-nav="project-epics-detail:project=vm.project.get('slug')" + tg-nav="project-epics-detail:project=epic.getIn(['project', 'slug']),ref=epic.get('ref')" ) #{hash}{{epic.get('id')}} {{epic.get('subject')}} diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index 1e7d8fa7..08d4ac43 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -25,9 +25,6 @@ BelongToEpicsDirective = () -> if scope.epics && !scope.epics.isIterable scope.epics = Immutable.fromJS(scope.epics) - if scope.project && !scope.project.isIterable - scope.project = Immutable.fromJS(scope.project) - scope.getTemplateUrl = () -> if attrs.format return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" diff --git a/app/modules/components/detail/header/detail-header.jade b/app/modules/components/detail/header/detail-header.jade index 82615b20..317065c0 100644 --- a/app/modules/components/detail/header/detail-header.jade +++ b/app/modules/components/detail/header/detail-header.jade @@ -41,7 +41,6 @@ ng-if="::vm.item.epics" epics="::vm.item.epics" format="text" - project="project" ) //- Task belongs to US @@ -60,7 +59,6 @@ ng-if="::vm.item.user_story_extra_info.epics" epics="::vm.item.user_story_extra_info.epics" format="pill" - project="vm.project" ) //- User Stories generated from issue diff --git a/app/modules/components/watch-button/watch-button.controller.coffee b/app/modules/components/watch-button/watch-button.controller.coffee index 99514424..e7cbae9c 100644 --- a/app/modules/components/watch-button/watch-button.controller.coffee +++ b/app/modules/components/watch-button/watch-button.controller.coffee @@ -45,7 +45,8 @@ class WatchButtonController perms = { userstories: 'modify_us', issues: 'modify_issue', - tasks: 'modify_task' + tasks: 'modify_task', + epics: 'modify_epic' } return perms[name] diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 452d928a..2dc410b5 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -15,7 +15,7 @@ .name(ng-if="vm.column.name") - var hash = "#"; a( - tg-nav="project-epics-detail:project=vm.project.get('slug')" + tg-nav="project-epics-detail:project=vm.project.slug,ref=vm.epic.get('ref')" ng-attr-title="{{::vm.epic.get('subject')}}" ) #{hash}{{::vm.epic.get('ref')}} {{::vm.epic.get('subject')}} span.epic-pill( diff --git a/app/modules/epics/related-userstories/related-userstories-controller.coffee b/app/modules/epics/related-userstories/related-userstories-controller.coffee new file mode 100644 index 00000000..8042fa8f --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-controller.coffee @@ -0,0 +1,33 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: related-userstories.controller.coffee +### + +module = angular.module("taigaEpics") + +class RelatedUserStoriesController + @.$inject = ["tgResources"] + + constructor: (@rs) -> + @.sectionName = "Epics" + @.showCreateRelatedUserstoriesLightbox = false + + loadRelatedUserstories: () -> + @rs.userstories.listInEpic(@.epic.get('id')).then (data) => + @.userstories = data + +module.controller("RelatedUserStoriesCtrl", RelatedUserStoriesController) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.coffee b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.coffee new file mode 100644 index 00000000..8b51a2c2 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.coffee @@ -0,0 +1,92 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: related-userstory-create.controller.coffee +### + +module = angular.module("taigaEpics") + +class RelatedUserstoriesCreateController + @.$inject = [ + "tgCurrentUserService", + "tgResources", + "$tgConfirm", + "$tgAnalytics" + ] + + constructor: (@currentUserService, @rs, @confirm, @analytics) -> + @.projects = @currentUserService.projects.get("all") + @.projectUserstories = Immutable.List() + @.loading = false + + selectProject: (selectedProjectId, onSelectedProject) -> + @rs.userstories.listAllInProject(selectedProjectId).then (data) => + excludeIds = @.epicUserstories.map((us) -> us.get('id')) + filteredData = data.filter((us) -> excludeIds.indexOf(us.get('id')) == -1) + @.projectUserstories = filteredData + if onSelectedProject + onSelectedProject() + + saveRelatedUserStory: (selectedUserstoryId, onSavedRelatedUserstory) -> + # This method assumes the following methods are binded to the controller: + # - validateExistingUserstoryForm + # - setExistingUserstoryFormErrors + # - loadRelatedUserstories + return if not @.validateExistingUserstoryForm() + + @.loading = true + + onError = (data) => + @.loading = false + @confirm.notify("error") + @.setExistingUserstoryFormErrors(data) + + onSuccess = () => + @analytics.trackEvent("epic related user story", "create", "create related user story on epic", 1) + @.loading = false + if onSavedRelatedUserstory + onSavedRelatedUserstory() + @.loadRelatedUserstories() + + epicId = @.epic.get('id') + @rs.epics.addRelatedUserstory(epicId, selectedUserstoryId).then(onSuccess, onError) + + bulkCreateRelatedUserStories: (selectedProjectId, userstoriesText, onCreatedRelatedUserstory) -> + # This method assumes the following methods are binded to the controller: + # - validateNewUserstoryForm + # - setNewUserstoryFormErrors + # - loadRelatedUserstories + return if not @.validateNewUserstoryForm() + + @.loading = true + + onError = (data) => + @.loading = false + @confirm.notify("error") + @.setNewUserstoryFormErrors(data) + + onSuccess = () => + @analytics.trackEvent("epic related user story", "create", "create related user story on epic", 1) + @.loading = false + if onCreatedRelatedUserstory + onCreatedRelatedUserstory() + @.loadRelatedUserstories() + + epicId = @.epic.get('id') + @rs.epics.bulkCreateRelatedUserStories(epicId, selectedProjectId, userstoriesText).then(onSuccess, onError) + + +module.controller("RelatedUserstoriesCreateCtrl", RelatedUserstoriesCreateController) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.spec.coffee new file mode 100644 index 00000000..f3bc84b1 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.controller.spec.coffee @@ -0,0 +1,185 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: related-userstories-create.controller.spec.coffee +### + +describe "RelatedUserstoriesCreate", -> + RelatedUserstoriesCreateCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgCurrentUserService = () -> + mocks.tgCurrentUserService = { + projects: { + get: sinon.stub() + } + } + + provide.value "tgCurrentUserService", mocks.tgCurrentUserService + + _mockTgConfirm = () -> + mocks.tgConfirm = { + askOnDelete: sinon.stub() + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + + _mockTgResources = () -> + mocks.tgResources = { + userstories: { + listAllInProject: sinon.stub() + } + epics: { + deleteRelatedUserstory: sinon.stub() + addRelatedUserstory: sinon.stub() + bulkCreateRelatedUserStories: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mockTgAnalytics = () -> + mocks.tgAnalytics = { + trackEvent: sinon.stub() + } + + provide.value "$tgAnalytics", mocks.tgAnalytics + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgCurrentUserService() + _mockTgConfirm() + _mockTgResources() + _mockTgAnalytics() + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + RelatedUserstoriesCreateCtrl = controller "RelatedUserstoriesCreateCtrl" + + it "select project", (done) -> + # This test tries to reproduce a project containing userstories 11 and 12 where 11 + # is yet related to the epic + RelatedUserstoriesCreateCtrl.epicUserstories = Immutable.fromJS([ + { + id: 11 + } + ]) + + onSelectedProjectCallback = sinon.stub() + userstories = Immutable.fromJS([ + { + id: 11 + }, + { + + id: 12 + } + ]) + filteredUserstories = Immutable.fromJS([ + { + + id: 12 + } + ]) + + promise = mocks.tgResources.userstories.listAllInProject.withArgs(1).promise().resolve(userstories) + RelatedUserstoriesCreateCtrl.selectProject(1, onSelectedProjectCallback).then () -> + expect(RelatedUserstoriesCreateCtrl.projectUserstories.toJS()).to.eql(filteredUserstories.toJS()) + done() + + it "save related user story success", (done) -> + RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm = sinon.stub() + RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm.returns(true) + onSavedRelatedUserstoryCallback = sinon.stub() + onSavedRelatedUserstoryCallback.returns(true) + RelatedUserstoriesCreateCtrl.loadRelatedUserstories = sinon.stub() + RelatedUserstoriesCreateCtrl.epic = Immutable.fromJS({ + id: 1 + }) + promise = mocks.tgResources.epics.addRelatedUserstory.withArgs(1, 11).promise().resolve(true) + RelatedUserstoriesCreateCtrl.saveRelatedUserStory(11, onSavedRelatedUserstoryCallback).then () -> + expect(RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm).have.been.calledOnce + expect(onSavedRelatedUserstoryCallback).have.been.calledOnce + expect(mocks.tgResources.epics.addRelatedUserstory).have.been.calledWith(1, 11) + expect(mocks.tgAnalytics.trackEvent).have.been.calledWith("epic related user story", "create", "create related user story on epic", 1) + expect(RelatedUserstoriesCreateCtrl.loadRelatedUserstories).have.been.calledOnce + done() + + it "save related user story error", (done) -> + RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm = sinon.stub() + RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm.returns(true) + onSavedRelatedUserstoryCallback = sinon.stub() + RelatedUserstoriesCreateCtrl.setExistingUserstoryFormErrors = sinon.stub() + RelatedUserstoriesCreateCtrl.setExistingUserstoryFormErrors.returns({}) + RelatedUserstoriesCreateCtrl.epic = Immutable.fromJS({ + id: 1 + }) + promise = mocks.tgResources.epics.addRelatedUserstory.withArgs(1, 11).promise().reject(new Error("error")) + RelatedUserstoriesCreateCtrl.saveRelatedUserStory(11, onSavedRelatedUserstoryCallback).then () -> + expect(RelatedUserstoriesCreateCtrl.validateExistingUserstoryForm).have.been.calledOnce + expect(onSavedRelatedUserstoryCallback).to.not.have.been.called + expect(mocks.tgResources.epics.addRelatedUserstory).have.been.calledWith(1, 11) + expect(mocks.tgConfirm.notify).have.been.calledWith("error") + expect(RelatedUserstoriesCreateCtrl.setExistingUserstoryFormErrors).have.been.calledOnce + done() + + it "bulk create related user stories success", (done) -> + RelatedUserstoriesCreateCtrl.validateNewUserstoryForm = sinon.stub() + RelatedUserstoriesCreateCtrl.validateNewUserstoryForm.returns(true) + onCreatedRelatedUserstoryCallback = sinon.stub() + onCreatedRelatedUserstoryCallback.returns(true) + RelatedUserstoriesCreateCtrl.loadRelatedUserstories = sinon.stub() + RelatedUserstoriesCreateCtrl.epic = Immutable.fromJS({ + id: 1 + }) + promise = mocks.tgResources.epics.bulkCreateRelatedUserStories.withArgs(1, 22, 'a\nb').promise().resolve(true) + RelatedUserstoriesCreateCtrl.bulkCreateRelatedUserStories(22, 'a\nb', onCreatedRelatedUserstoryCallback).then () -> + expect(RelatedUserstoriesCreateCtrl.validateNewUserstoryForm).have.been.calledOnce + expect(onCreatedRelatedUserstoryCallback).have.been.calledOnce + expect(mocks.tgResources.epics.bulkCreateRelatedUserStories).have.been.calledWith(1, 22, 'a\nb') + expect(mocks.tgAnalytics.trackEvent).have.been.calledWith("epic related user story", "create", "create related user story on epic", 1) + expect(RelatedUserstoriesCreateCtrl.loadRelatedUserstories).have.been.calledOnce + done() + + it "bulk create related user stories error", (done) -> + RelatedUserstoriesCreateCtrl.validateNewUserstoryForm = sinon.stub() + RelatedUserstoriesCreateCtrl.validateNewUserstoryForm.returns(true) + onCreatedRelatedUserstoryCallback = sinon.stub() + RelatedUserstoriesCreateCtrl.setNewUserstoryFormErrors = sinon.stub() + RelatedUserstoriesCreateCtrl.setNewUserstoryFormErrors.returns({}) + RelatedUserstoriesCreateCtrl.epic = Immutable.fromJS({ + id: 1 + }) + promise = mocks.tgResources.epics.bulkCreateRelatedUserStories.withArgs(1, 22, 'a\nb').promise().reject(new Error("error")) + RelatedUserstoriesCreateCtrl.bulkCreateRelatedUserStories(22, 'a\nb', onCreatedRelatedUserstoryCallback).then () -> + expect(RelatedUserstoriesCreateCtrl.validateNewUserstoryForm).have.been.calledOnce + expect(onCreatedRelatedUserstoryCallback).to.not.have.been.called + expect(mocks.tgResources.epics.bulkCreateRelatedUserStories).have.been.calledWith(1, 22, 'a\nb') + expect(mocks.tgConfirm.notify).have.been.calledWith("error") + expect(RelatedUserstoriesCreateCtrl.setNewUserstoryFormErrors).have.been.calledOnce + done() diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.directive.coffee b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.directive.coffee new file mode 100644 index 00000000..9ecd4a03 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.directive.coffee @@ -0,0 +1,79 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: related-userstory-create.directive.coffee +### + +module = angular.module('taigaEpics') + +RelatedUserstoriesCreateDirective = (@lightboxService) -> + link = (scope, el, attrs, ctrl) -> + newUserstoryForm = el.find(".new-user-story-form").checksley() + existingUserstoryForm = el.find(".existing-user-story-form").checksley() + + ctrl.validateNewUserstoryForm = => + return newUserstoryForm.validate() + + ctrl.setNewUserstoryFormErrors = (errors) => + newUserstoryForm.setErrors(errors) + + ctrl.validateExistingUserstoryForm = => + return existingUserstoryForm.validate() + + ctrl.setExistingUserstoryFormErrors = (errors) => + existingUserstoryForm.setErrors(errors) + + scope.showLightbox = (selectedProjectId) -> + scope.selectProject(selectedProjectId).then () => + lightboxService.open(el.find(".lightbox-create-related-user-stories")) + + scope.closeLightbox = () -> + scope.selectedUserstory = null + scope.searchUserstory = "" + scope.relatedUserstoriesText = "" + lightboxService.close(el.find(".lightbox-create-related-user-stories")) + + scope.$watch 'vm.project', (project) -> + if project? + scope.selectedProject = project.get('id') + + scope.selectProject = (selectedProjectId) -> + scope.selectedUserstory = null + scope.searchUserstory = "" + ctrl.selectProject(selectedProjectId) + + scope.onUpdateSearchUserstory = () -> + scope.selectedUserstory = null + + return { + link: link, + templateUrl:"epics/related-userstories/related-userstories-create/related-userstories-create.html", + controller: "RelatedUserstoriesCreateCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + showCreateRelatedUserstoriesLightbox: "=" + project: "=" + epic: "=" + epicUserstories: "=" + loadRelatedUserstories:"&" + } + + } + +RelatedUserstoriesCreateDirective.$inject = ["lightboxService",] + +module.directive("tgRelatedUserstoriesCreate", RelatedUserstoriesCreateDirective) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade new file mode 100644 index 00000000..468acbbb --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade @@ -0,0 +1,153 @@ +a.add-button.e2e-add-userstory-button( + href="" + ng-click="showLightbox(vm.project.get('id'))" +) + tg-svg(svg-icon="icon-add") + +div.lightbox.lightbox-create-related-user-stories + tg-lightbox-close + + div.form + h2.title(translate="EPIC.CREATE_RELATED_USERSTORIES") + + .related-with-selector-title + legend(translate="EPIC.RELATED_WITH") + + .related-with-selector + fieldset + input( + type="radio" + name="related-with-selector" + id="new-user-story" + value="new-user-story" + ng-model="relatedWithSelector" + ng-init="relatedWithSelector='new-user-story'" + ) + label.e2e-new-userstory-label(for="new-user-story") + span.name {{ 'EPIC.NEW_USERSTORY' | translate}} + + fieldset + input( + type="radio" + name="related-with-selector" + id="existing-user-story" + value="existing-user-story" + ng-model="relatedWithSelector" + ) + label.e2e-existing-user-story-label(for="existing-user-story") + span.name {{ 'EPIC.EXISTING_USERSTORY' | translate}} + + .project-selector-title + legend( + ng-if="relatedWithSelector=='new-user-story'" + translate="EPIC.CHOOSE_PROJECT_FOR_CREATION" + ) + + legend( + ng-if="relatedWithSelector=='existing-user-story'" + translate="EPIC.CHOOSE_PROJECT_FROM" + ) + + .project-selector() + select( + ng-model="selectedProject" + ng-change="selectProject(selectedProject)" + data-required="true" + required + ng-options="p.id as p.name for p in vm.projects | toMutable" + ) + + div(ng-show="relatedWithSelector=='new-user-story'") + .new-user-story-selector + .new-user-story-title + legend( + ng-show="creationMode=='single-new-user-story'" + translate="EPIC.SUBJECT" + ) + + legend( + ng-show="creationMode=='bulk-new-user-stories'" + translate="EPIC.SUBJECT_BULK_MODE" + ) + .new-user-story-options + fieldset + input( + type="radio" + name="new-user-story-selector" + id="single-new-user-story" + value="single-new-user-story" + ng-model="creationMode" + ng-init="creationMode='single-new-user-story'" + ) + label.e2e-single-creation-label(for="single-new-user-story") + tg-svg(svg-icon="icon-add") + + fieldset + input( + type="radio" + name="new-user-story-selector" + id="bulk-new-user-stories" + value="bulk-new-user-stories" + ng-model="creationMode" + ) + label.e2e-bulk-creation-label(for="bulk-new-user-stories") + tg-svg(svg-icon="icon-bulk") + + form.new-user-story-form + .single-creation(ng-show="creationMode=='single-new-user-story'") + input.e2e-new-userstory-input-text( + type="text" + ng-model="relatedUserstoriesText" + data-required="true" + ) + + .bulk-creation(ng-show="creationMode=='bulk-new-user-stories'") + textarea.e2e-new-userstories-input-textarea( + ng-model="relatedUserstoriesText" + data-required="true" + ) + + a.button-green.e2e-create-userstory-button( + href="" + ng-click="vm.bulkCreateRelatedUserStories(selectedProject, relatedUserstoriesText, closeLightbox)" + tg-loading="vm.loading" + ) + span( + translate="COMMON.SAVE" + ) + + .existing-user-story(ng-show="relatedWithSelector=='existing-user-story'") + .existing-user-story-title + legend(translate="EPIC.CHOOSE_USERSTORY") + + input.userstory.e2e-filter-userstories-input( + type="text" + placeholder="{{'EPIC.FILTER_USERSTORIES' | translate}}" + ng-model="searchUserstory" + ng-change="onUpdateSearchUserstory()" + ) + + form.existing-user-story-form + select.userstory.e2e-userstories-select( + size="5" + ng-model="selectedUserstory" + required + data-required="true" + ) + - var hash = "#"; + option.hidden( + value="" + ) + option( + ng-repeat="us in vm.projectUserstories | toMutable | byRef:searchUserstory track by us.id" + value="{{ ::us.id }}" + ) #{hash}{{::us.ref}} {{::us.subject}} + + a.button-green.e2e-select-related-userstory-button( + href="" + ng-click="vm.saveRelatedUserStory(selectedUserstory, closeLightbox)" + tg-loading="vm.loading" + ) + span( + translate="COMMON.SAVE" + ) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss new file mode 100644 index 00000000..82412585 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss @@ -0,0 +1,78 @@ +.lightbox-create-related-user-stories { + .related-with-selector-title, + .project-selector-title, + .new-user-story-title, + .existing-user-story-title { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + } + .related-with-selector, + .new-user-story-selector { + display: flex; + input { + display: none; + } + fieldset { + &:first-child { + margin-right: .5rem; + } + } + } + .project-selector, + .single-creation { + margin-bottom: 1rem; + } + input { + &:checked+label { + background: $primary-light; + color: $white; + transition: background .2s ease-in; + &:hover { + background: $primary-light; + } + } + +label { + background: rgba($whitish, .7); + cursor: pointer; + display: block; + padding: 2rem 1rem; + text-align: center; + transition: background .2s ease-in; + &:hover { + background: rgba($primary-light, .3); + transition: background .2s ease-in; + } + .icon { + fill: currentColor; + margin-top: .25rem; + vertical-align: text-top; + } + .name { + @include font-size(large); + text-transform: uppercase; + } + } + } + .new-user-story-selector { + display: flex; + justify-content: space-between; + .new-user-story-options { + display: flex; + } + fieldset { + width: auto; + } + label { + height: 1.5rem; + padding: 0; + width: 1.5rem; + } + } + + .existing-user-story { + .button-green { + margin-top: 1rem; + } + } +} diff --git a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee new file mode 100644 index 00000000..9162c935 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee @@ -0,0 +1,66 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: related-userstories.controller.spec.coffee +### + +describe "RelatedUserStories", -> + RelatedUserStoriesCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + userstories: { + listInEpic: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + RelatedUserStoriesCtrl = controller "RelatedUserStoriesCtrl" + + it "load related userstories", (done) -> + userstories = Immutable.fromJS([ + { + id: 1 + } + ]) + + RelatedUserStoriesCtrl.epic = Immutable.fromJS({ + id: 66 + }) + + promise = mocks.tgResources.userstories.listInEpic.withArgs(66).promise().resolve(userstories) + RelatedUserStoriesCtrl.loadRelatedUserstories().then () -> + expect(RelatedUserStoriesCtrl.userstories).is.equal(userstories) + done() diff --git a/app/modules/epics/related-userstories/related-userstories.directive.coffee b/app/modules/epics/related-userstories/related-userstories.directive.coffee new file mode 100644 index 00000000..e3db9be8 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories.directive.coffee @@ -0,0 +1,37 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: related-userstories.directive.coffee +### + +module = angular.module('taigaEpics') + +RelatedUserStoriesDirective = () -> + return { + templateUrl:"epics/related-userstories/related-userstories.html", + controller: "RelatedUserStoriesCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + userstories: '=', + project: '=' + epic: '=' + } + } + +RelatedUserStoriesDirective.$inject = [] + +module.directive("tgRelatedUserstories", RelatedUserStoriesDirective) diff --git a/app/modules/epics/related-userstories/related-userstories.jade b/app/modules/epics/related-userstories/related-userstories.jade new file mode 100644 index 00000000..ecf642de --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories.jade @@ -0,0 +1,23 @@ +section.related-userstories + .related-userstories-header + span.related-userstories-title(translate="COMMON.RELATED_USERSTORIES") + tg-related-userstories-create( + tg-check-permission="modify_epic" + show-create-related-userstories-lightbox="vm.showCreateRelatedUserstoriesLightbox" + project="vm.project" + epic="vm.epic" + epic-userstories="vm.userstories" + load-related-userstories="vm.loadRelatedUserstories()" + ) + + .related-userstories-body + div(tg-repeat="us in vm.userstories track by us.get('id')") + tg-related-userstory-row.row( + ng-class="{closed: us.get('is_closed'), blocked: us.get('is_blocked')}" + userstory="us" + epic="vm.epic" + project="vm.project" + load-related-userstories="vm.loadRelatedUserstories()" + ) + + div(tg-related-userstories-create-form) diff --git a/app/modules/epics/related-userstories/related-userstories.scss b/app/modules/epics/related-userstories/related-userstories.scss new file mode 100644 index 00000000..62bc0b46 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories.scss @@ -0,0 +1,147 @@ +.related-userstories { + margin-bottom: 2rem; + position: relative; +} + +.related-userstories-header { + align-content: center; + align-items: center; + background: $mass-white; + display: flex; + justify-content: space-between; + min-height: 36px; + .related-userstories-title { + @include font-size(medium); + @include font-type(bold); + margin-left: 1rem; + } + .add-button { + background: $grayer; + border: 0; + display: inline-block; + padding: .5rem; + transition: background .25s; + &:hover, + &.is-active { + background: $primary-light; + } + svg { + fill: $white; + height: 1.25rem; + margin-bottom: -.2rem; + width: 1.25rem; + } + } +} + +.related-userstories-body { + width: 100%; + .row { + @include font-size(small); + align-items: center; + border-bottom: 1px solid $whitish; + display: flex; + padding: .5rem 0 .5rem .5rem; + &:hover { + .userstory-settings { + opacity: 1; + transition: all .2s ease-in; + } + } + .userstory-name { + flex: 1; + } + .userstory-settings { + flex-shrink: 0; + width: 60px; + } + .status { + flex-shrink: 0; + width: 125px; + } + .assigned-to-column { + flex-shrink: 0; + width: 150px; + img { + flex-basis: 35px; + // width & height they are only required for IE + height: 35px; + width: 35px; + } + } + .project { + flex-basis: 100px; + img { + width: 40px; + } + } + } + + .userstory-name { + display: flex; + margin-right: 1rem; + + span { + margin-right: .25rem; + } + } + .status { + position: relative; + } + .closed { + border-left: 10px solid $whitish; + color: $whitish; + a, + svg { + fill: $whitish; + } + .userstory-name a { + color: $whitish; + text-decoration: line-through; + + } + } + .blocked { + background: rgba($red-light, .2); + border-left: 10px solid $red-light; + } + .userstory-settings { + align-items: center; + display: flex; + opacity: 0; + svg { + @include svg-size(1.1rem); + fill: $gray-light; + margin-right: .5rem; + transition: fill .2s ease-in; + &:hover { + fill: $gray; + } + } + a { + &:hover { + cursor: pointer; + } + } + } + .delete-userstory { + &:hover { + .icon-trash { + fill: $red-light; + } + } + } + .avatar { + align-items: center; + display: flex; + img { + flex-basis: 35px; + // width & height they are only required for IE + height: 35px; + width: 35px; + } + figcaption { + margin-left: .5rem; + } + } +} diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee new file mode 100644 index 00000000..ef58ab9b --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee @@ -0,0 +1,63 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: reñated-userstory-row.controller.coffee +### + +module = angular.module("taigaEpics") + +class RelatedUserstoryRowController + @.$inject = [ + "tgAvatarService", + "$translate", + "$tgConfirm", + "tgResources" + ] + + constructor: (@avatarService, @translate, @confirm, @rs) -> + + setAvatarData: () -> + member = @.userstory.get('assigned_to_extra_info') + @.avatar = @avatarService.getAvatar(member) + + getAssignedToFullNameDisplay: () -> + if @.userstory.get('assigned_to') + return @.userstory.getIn(['assigned_to_extra_info', 'full_name_display']) + + return @translate.instant("COMMON.ASSIGNED_TO.NOT_ASSIGNED") + + onDeleteRelatedUserstory: () -> + title = @translate.instant('EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY') + message = @translate.instant('EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY', { + subject: @.userstory.get('subject') + }) + + return @confirm.askOnDelete(title, message) + .then (askResponse) => + onError = () => + message = @translate.instant('EPIC.ERROR_DELETE_RELATED_USERSTORY', {errorMessage: message}) + @confirm.notify("error", null, message) + askResponse.finish(false) + + onSuccess = () => + @.loadRelatedUserstories() + askResponse.finish() + + epicId = @.epic.get('id') + userstoryId = @.userstory.get('id') + @rs.epics.deleteRelatedUserstory(epicId, userstoryId).then(onSuccess, onError) + +module.controller("RelatedUserstoryRowCtrl", RelatedUserstoryRowController) diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee new file mode 100644 index 00000000..c300b372 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee @@ -0,0 +1,169 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: related-userstory-row.controller.spec.coffee +### + +describe "RelatedUserstoryRow", -> + RelatedUserstoryRowCtrl = null + provide = null + controller = null + mocks = {} + + _mockTgConfirm = () -> + mocks.tgConfirm = { + askOnDelete: sinon.stub() + notify: sinon.stub() + } + + provide.value "$tgConfirm", mocks.tgConfirm + + _mockTgAvatarService = () -> + mocks.tgAvatarService = { + getAvatar: sinon.stub() + } + + provide.value "tgAvatarService", mocks.tgAvatarService + + _mockTranslate = () -> + mocks.translate = { + instant: sinon.stub() + } + + provide.value "$translate", mocks.translate + + _mockTgResources = () -> + mocks.tgResources = { + epics: { + deleteRelatedUserstory: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgConfirm() + _mockTgAvatarService() + _mockTranslate() + _mockTgResources() + + return null + + beforeEach -> + module "taigaEpics" + + _mocks() + + inject ($controller) -> + controller = $controller + + RelatedUserstoryRowCtrl = controller "RelatedUserstoryRowCtrl" + + it "set avatar data", (done) -> + RelatedUserstoryRowCtrl.userstory = Immutable.fromJS({ + assigned_to_extra_info: { + id: 3 + } + }) + member = RelatedUserstoryRowCtrl.userstory.get("assigned_to_extra_info") + avatar = { + url: "http://taiga.io" + bg: "#AAAAAA" + } + mocks.tgAvatarService.getAvatar.withArgs(member).returns(avatar) + RelatedUserstoryRowCtrl.setAvatarData() + expect(mocks.tgAvatarService.getAvatar).have.been.calledWith(member) + expect(RelatedUserstoryRowCtrl.avatar).is.equal(avatar) + done() + + it "get assigned to full name display for existing user", (done) -> + RelatedUserstoryRowCtrl.userstory = Immutable.fromJS({ + assigned_to: 1 + assigned_to_extra_info: { + full_name_display: "Beta tester" + } + }) + + expect(RelatedUserstoryRowCtrl.getAssignedToFullNameDisplay()).is.equal("Beta tester") + done() + + it "get assigned to full name display for unassigned user story", (done) -> + RelatedUserstoryRowCtrl.userstory = Immutable.fromJS({ + assigned_to: null + }) + mocks.translate.instant.withArgs("COMMON.ASSIGNED_TO.NOT_ASSIGNED").returns("Unassigned") + expect(RelatedUserstoryRowCtrl.getAssignedToFullNameDisplay()).is.equal("Unassigned") + done() + + it "delete related userstory success", (done) -> + RelatedUserstoryRowCtrl.epic = Immutable.fromJS({ + id: 123 + }) + RelatedUserstoryRowCtrl.userstory = Immutable.fromJS({ + subject: "Deleting" + id: 124 + }) + + RelatedUserstoryRowCtrl.loadRelatedUserstories = sinon.stub() + + askResponse = { + finish: sinon.spy() + } + + mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY").returns("title") + mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") + + mocks.tgConfirm.askOnDelete = sinon.stub() + mocks.tgConfirm.askOnDelete.withArgs("title", "message").promise().resolve(askResponse) + + promise = mocks.tgResources.epics.deleteRelatedUserstory.withArgs(123, 124).promise().resolve(true) + RelatedUserstoryRowCtrl.onDeleteRelatedUserstory().then () -> + expect(mocks.tgResources.epics.deleteRelatedUserstory).have.been.calledWith(123, 124) + expect(RelatedUserstoryRowCtrl.loadRelatedUserstories).have.been.calledOnce + expect(askResponse.finish).have.been.calledOnce + done() + + it "delete related userstory error", (done) -> + RelatedUserstoryRowCtrl.epic = Immutable.fromJS({ + id: 123 + }) + RelatedUserstoryRowCtrl.userstory = Immutable.fromJS({ + subject: "Deleting" + id: 124 + }) + + RelatedUserstoryRowCtrl.loadRelatedUserstories = sinon.stub() + + askResponse = { + finish: sinon.spy() + } + + mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY").returns("title") + mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") + mocks.translate.instant.withArgs("EPIC.ERROR_DELETE_RELATED_USERSTORY", {errorMessage: "message"}).returns("error message") + + mocks.tgConfirm.askOnDelete = sinon.stub() + mocks.tgConfirm.askOnDelete.withArgs("title", "message").promise().resolve(askResponse) + + promise = mocks.tgResources.epics.deleteRelatedUserstory.withArgs(123, 124).promise().reject(new Error("error")) + RelatedUserstoryRowCtrl.onDeleteRelatedUserstory().then () -> + expect(mocks.tgResources.epics.deleteRelatedUserstory).have.been.calledWith(123, 124) + expect(RelatedUserstoryRowCtrl.loadRelatedUserstories).to.not.have.been.called + expect(askResponse.finish).have.been.calledWith(false) + expect(mocks.tgConfirm.notify).have.been.calledWith("error", null, "error message") + done() diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.directive.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.directive.coffee new file mode 100644 index 00000000..02ea4ebd --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.directive.coffee @@ -0,0 +1,42 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: related-userstory-row.directive.coffee +### + +module = angular.module('taigaEpics') + +RelatedUserstoryRowDirective = () -> + link = (scope, el, attrs, ctrl) -> + ctrl.setAvatarData() + + return { + link: link, + templateUrl:"epics/related-userstories/related-userstory-row/related-userstory-row.html", + controller: "RelatedUserstoryRowCtrl", + controllerAs: "vm", + bindToController: true, + scope: { + userstory: '=' + epic: '=' + project: '=' + loadRelatedUserstories:"&" + } + } + +RelatedUserstoryRowDirective.$inject = [] + +module.directive("tgRelatedUserstoryRow", RelatedUserstoryRowDirective) diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade new file mode 100644 index 00000000..7c7b8a41 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade @@ -0,0 +1,44 @@ +.userstory-name + - var hash = "#"; + a( + tg-nav="project-userstories-detail:project=vm.userstory.getIn(['project_extra_info', 'slug']),ref=vm.userstory.get('ref')" + ng-attr-title="{{vm.userstory.get('subject')}}" + ) #{hash}{{vm.userstory.get('ref')}} {{vm.userstory.get('subject')}} + + tg-belong-to-epics( + format="pill" + ng-if="vm.userstory.get('epics')" + epics="vm.userstory.get('epics')" + ) + +.userstory-settings + a.delete-userstory.e2e-delete-userstory( + tg-check-permission="modify_epic" + title="{{'COMMON.DELETE' | translate}}" + href="" + ng-click="vm.onDeleteRelatedUserstory()" + ) + tg-svg(svg-icon="icon-trash") + +.project( + tg-nav="project:project=vm.userstory.getIn(['project_extra_info', 'slug'])" +) + img( + tg-project-logo-small-src="::vm.userstory.get('project_extra_info')" + alt="{{::vm.userstory.getIn(['project_extra_info', 'name'])}}" + ) + +.status + span.userstory-status(ng-style="{'color': vm.userstory.getIn(['status_extra_info', 'color'])}") {{vm.userstory.getIn(['status_extra_info', 'name'])}} + +.assigned-to-column + figure.avatar + img( + style="background-color: {{ vm.avatar.bg }}" + src="{{ vm.avatar.url }}" + alt="{{ vm.avatar.full_name_display }}" + ) + + figcaption {{ vm.getAssignedToFullNameDisplay() }} + +div(tg-related-userstories-create-form) diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index 21129745..82d48c11 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -51,6 +51,31 @@ Resource = (urlsService, http) -> return http.post(url, params) + service.addRelatedUserstory = (epicId, userstoryId) -> + url = urlsService.resolve("epic-related-userstories", epicId) + + params = { + user_story: userstoryId + epic: epicId + } + + return http.post(url, params) + + service.bulkCreateRelatedUserStories = (epicId, projectId, bulk_userstories) -> + url = urlsService.resolve("epic-related-userstories-bulk-create", epicId) + + params = { + bulk_userstories: bulk_userstories, + project_id: projectId + } + + return http.post(url, params) + + service.deleteRelatedUserstory = (epicId, userstoryId) -> + url = urlsService.resolve("epic-related-userstories", epicId) + "/#{userstoryId}" + + return http.delete(url) + return () -> return {"epics": service} diff --git a/app/modules/resources/userstories-resource.service.coffee b/app/modules/resources/userstories-resource.service.coffee index 7b7f9b90..d410036e 100644 --- a/app/modules/resources/userstories-resource.service.coffee +++ b/app/modules/resources/userstories-resource.service.coffee @@ -33,6 +33,22 @@ Resource = (urlsService, http) -> .then (result) -> return Immutable.fromJS(result.data) + service.listAllInProject = (projectId) -> + url = urlsService.resolve("userstories") + + httpOptions = { + headers: { + "x-disable-pagination": "1" + } + } + + params = { + project: projectId + } + return http.get(url, params, httpOptions) + .then (result) -> + return Immutable.fromJS(result.data) + service.listInEpic = (epicIid) -> url = urlsService.resolve("userstories") diff --git a/app/partials/epic/epic-detail.jade b/app/partials/epic/epic-detail.jade new file mode 100644 index 00000000..b03ec020 --- /dev/null +++ b/app/partials/epic/epic-detail.jade @@ -0,0 +1,127 @@ +doctype html + +div.wrapper( + ng-controller="EpicDetailController as ctrl", + ng-init="section='epics'" +) + tg-project-menu + + div.main.us-detail + div.us-detail-header.header-with-actions + include ../includes/components/mainTitle + + section.us-story-main-data + header + tg-vote-button.upvote-btn( + item="epic" + on-upvote="ctrl.onUpvote" + on-downvote="ctrl.onDownvote" + ) + + .detail-header-container + tg-color-selector( + color="epic.color", + on-select-color="ctrl.onSelectColor(color)" + ) + tg-detail-header( + item="epic" + project="project" + required-perm="modify_epic" + ng-class="{blocked: epic.is_blocked}" + ng-if="project && epic" + format="text" + ) + .subheader + tg-tag-line.tags-block( + ng-if="epic && project" + project="project" + item="epic" + permissions="modify_epic" + ) + tg-created-by-display.ticket-created-by(ng-model="epic") + + section.duty-content( + tg-editable-description + tg-editable-wysiwyg + ng-model="epic" + required-perm="modify_epic" + ) + + // Custom Fields + tg-custom-attributes-values( + ng-model="epic" + type="epic" + project="project" + required-edition-perm="modify_epic" + ) + + tg-related-userstories( + project="immutableProject" + userstories="userstories" + epic="immutableEpic" + ) + + tg-attachments-full( + obj-id="epic.id" + type="epic", + project-id="projectId" + edit-permission = "modify_epic" + ) + + tg-history-section( + ng-if="epic" + type="epic" + name="epic" + id="epic.id" + project-id="projectId" + ) + + sidebar.menu-secondary.sidebar.ticket-data + + .ticket-header + span.ticket-title( + tg-epic-status-display + ng-model="epic" + ) + span.detail-status( + tg-epic-status-button + ng-model="epic" + ) + + section.ticket-assigned-to( + tg-assigned-to + ng-model="epic" + required-perm="modify_epic" + ) + + section.ticket-watch-buttons + div.ticket-watch( + tg-watch-button + item="epic" + data-environment="ticket" + on-watch="ctrl.onWatch" + on-unwatch="ctrl.onUnwatch" + ) + div.ticket-watchers( + tg-watchers + ng-model="epic" + required-perm="modify_epic" + ) + + section.ticket-detail-settings + tg-us-team-requirement-button(ng-model="epic") + tg-us-client-requirement-button(ng-model="epic") + tg-block-button( + tg-check-permission="modify_epic", + ng-model="epic" + ) + tg-delete-button( + tg-check-permission="delete_epic", + on-delete-title="{{'EPIC.ACTION_DELETE' | translate}}", + on-delete-go-to-url="onDeleteGoToUrl", + ng-model="epic" + ) + + div.lightbox.lightbox-block(tg-lb-block, ng-model="epic", title="EPIC.LIGHTBOX_TITLE_BLOKING_EPIC") + div.lightbox.lightbox-select-user(tg-lb-assignedto) + div.lightbox.lightbox-select-user(tg-lb-watchers) diff --git a/app/styles/modules/common/wizard.scss b/app/styles/modules/common/wizard.scss index f0482026..5bcae75f 100644 --- a/app/styles/modules/common/wizard.scss +++ b/app/styles/modules/common/wizard.scss @@ -57,7 +57,6 @@ .icon { @include svg-size(1.5rem); fill: currentColor; - margin-right: 1rem; vertical-align: text-top; } .template-name { diff --git a/conf.e2e.js b/conf.e2e.js index d7ca0a01..cd421c09 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -53,55 +53,55 @@ var config = { onPrepare: function() { // disable by default because performance problems on IE // track mouse movements - // var trackMouse = function() { - // angular.module('trackMouse', []).run(function($document) { + var trackMouse = function() { + angular.module('trackMouse', []).run(function($document) { - // function addDot(ev) { - // var color = 'black', - // size = 6; + function addDot(ev) { + var color = 'black', + size = 6; - // switch (ev.type) { - // case 'click': - // color = 'red'; - // break; - // case 'dblclick': - // color = 'blue'; - // break; - // case 'mousemove': - // color = 'green'; - // break; - // } + switch (ev.type) { + case 'click': + color = 'red'; + break; + case 'dblclick': + color = 'blue'; + break; + case 'mousemove': + color = 'green'; + break; + } - // var dotEl = $('
') - // .css({ - // position: 'fixed', - // height: size + 'px', - // width: size + 'px', - // 'background-color': color, - // top: ev.clientY, - // left: ev.clientX, + var dotEl = $('
') + .css({ + position: 'fixed', + height: size + 'px', + width: size + 'px', + 'background-color': color, + top: ev.clientY, + left: ev.clientX, - // 'z-index': 9999, + 'z-index': 9999, - // // make sure this dot won't interfere with the mouse events of other elements - // 'pointer-events': 'none' - // }) - // .appendTo('body'); + // make sure this dot won't interfere with the mouse events of other elements + 'pointer-events': 'none' + }) + .appendTo('body'); - // setTimeout(function() { - // dotEl.remove(); - // }, 1000); - // } + setTimeout(function() { + dotEl.remove(); + }, 1000); + } - // $document.on({ - // click: addDot, - // dblclick: addDot, - // mousemove: addDot - // }); + $document.on({ + click: addDot, + dblclick: addDot, + mousemove: addDot + }); - // }); - // }; - // browser.addMockModule('trackMouse', trackMouse); + }); + }; + browser.addMockModule('trackMouse', trackMouse); browser.params.glob.back = argv.back; diff --git a/e2e/helpers/detail-helper.js b/e2e/helpers/detail-helper.js index 78daae62..a315edbb 100644 --- a/e2e/helpers/detail-helper.js +++ b/e2e/helpers/detail-helper.js @@ -86,7 +86,7 @@ helper.tags = function() { for (let tag of tags){ htmlChanges = await utils.common.outerHtmlChanges(el.$(".tags-container")); el.$('.e2e-add-tag-input').sendKeys(tag); - await browser.actions().sendKeys(protractor.Key.ENTER).perform(); + el.$('.save').click(); await htmlChanges(); } } @@ -542,3 +542,43 @@ helper.watchersLightbox = function() { return obj; }; + +helper.teamRequirement = function() { + let el = $('tg-us-team-requirement-button'); + + let obj = { + el: el, + + toggleStatus: async function(){ + await el.$("label").click(); + await browser.waitForAngular(); + }, + + isRequired: async function() { + let classes = await el.$("label").getAttribute('class'); + return classes.includes("active"); + } + }; + + return obj; +}; + +helper.clientRequirement = function() { + let el = $('tg-us-client-requirement-button'); + + let obj = { + el: el, + + toggleStatus: async function(){ + await el.$("label").click(); + await browser.waitForAngular(); + }, + + isRequired: async function() { + let classes = await el.$("label").getAttribute('class'); + return classes.includes("active"); + } + }; + + return obj; +}; diff --git a/e2e/helpers/epic-detail-helper.js b/e2e/helpers/epic-detail-helper.js new file mode 100644 index 00000000..84368661 --- /dev/null +++ b/e2e/helpers/epic-detail-helper.js @@ -0,0 +1,76 @@ +var utils = require('../utils'); +var commonHelper = require('./common-helper'); + +var helper = module.exports; + + +helper.colorEditor = function() { + let el = $('tg-color-selector'); + + let obj = { + el: el, + + open: async function(){ + await el.$(".e2e-open-color-selector").click(); + }, + + selectFirstColor: async function() { + let color = el.$$(".color-selector-option").first(); + color.click(); + await browser.waitForAngular(); + }, + + selectLastColor: async function() { + let color = el.$$(".color-selector-option").last(); + color.click(); + await browser.waitForAngular(); + } + }; + + return obj; +}; + +helper.relatedUserstories = function() { + let el = $('tg-related-userstories'); + + let obj = { + el: el, + + createNewUserStory: async function(subject) { + el.$(".e2e-add-userstory-button").click(); + el.$(".e2e-new-userstory-label").click(); + el.$(".e2e-single-creation-label").click(); + el.$(".e2e-new-userstory-input-text").sendKeys(subject); + el.$(".e2e-create-userstory-button").click(); + await browser.waitForAngular(); + }, + + createNewUserStories: async function(subject) { + el.$(".e2e-add-userstory-button").click(); + el.$(".e2e-new-userstory-label").click(); + el.$(".e2e-bulk-creation-label").click(); + el.$(".e2e-new-userstories-input-textarea").sendKeys(subject); + el.$(".e2e-create-userstory-button").click(); + await browser.waitForAngular(); + }, + + selectFirstRelatedUserstory: async function() { + el.$(".e2e-add-userstory-button").click(); + el.$(".e2e-existing-user-story-label").click(); + el.$(".e2e-filter-userstories-input").click().sendKeys("#1"); + await browser.waitForAngular(); + el.$$(".e2e-userstories-select option").get(1).click() + el.$(".e2e-select-related-userstory-button").click(); + await browser.waitForAngular(); + }, + + deleteFirstRelatedUserstory: async function() { + let relatedUSRow = el.$$("tg-related-userstory-row").first(); + browser.actions().mouseMove(relatedUSRow).perform(); + relatedUSRow.$(".e2e-delete-userstory").click(); + await utils.lightbox.confirm.ok(); + } + }; + + return obj; +} diff --git a/e2e/helpers/epics-helper.js b/e2e/helpers/epics-dashboard-helper.js similarity index 94% rename from e2e/helpers/epics-helper.js rename to e2e/helpers/epics-dashboard-helper.js index 305f7a27..787acd1c 100644 --- a/e2e/helpers/epics-helper.js +++ b/e2e/helpers/epics-dashboard-helper.js @@ -44,33 +44,38 @@ helper.epic = function() { resetAssignedTo: async function() { el.get(0).$('.e2e-assigned-to-image').click(); $$('.e2e-assigned-to-selector').get(0).click(); + await browser.waitForAngular(); }, editAssignedTo: async function() { el.get(0).$('.e2e-assigned-to-image').click(); utils.common.takeScreenshot("epics", "epics-edit-assigned"); $$('.e2e-assigned-to-selector').last().click(); + await browser.waitForAngular(); }, removeAssignedTo: async function() { el.get(0).$('.e2e-assigned-to-image').click(); - $$('.e2e-unassign').click(); + $('.e2e-unassign').click(); + await browser.waitForAngular(); return el.get(0).$('.e2e-assigned-to-image').getAttribute("alt"); }, - resetStatus: function() { + resetStatus: async function() { el.get(0).$('.e2e-epic-status').click(); el.get(0).$$('.e2e-edit-epic-status').get(0).click(); + await browser.waitForAngular(); }, getStatus: function() { return el.get(0).$('.e2e-epic-status').getText(); }, - editStatus: function() { + editStatus: async function() { el.get(0).$('.e2e-epic-status').click(); utils.common.takeScreenshot("epics", "epics-edit-status"); el.get(0).$$('.e2e-edit-epic-status').last().click(); + await browser.waitForAngular(); }, getColumns: function() { return $$('.e2e-epics-table-header > div').count(); }, - removeColumns: function() { + removeColumns: async function() { $('.e2e-epics-column-button').click(); utils.common.takeScreenshot("epics", "epics-edit-columns"); $$('.e2e-epics-column-dropdown .check').first().click(); diff --git a/e2e/helpers/index.js b/e2e/helpers/index.js index bc497ffc..307b9daf 100644 --- a/e2e/helpers/index.js +++ b/e2e/helpers/index.js @@ -13,3 +13,5 @@ module.exports.adminPermissions = require("./admin-permissions"); module.exports.adminIntegrations = require("./admin-integrations"); module.exports.issues = require("./issues-helper"); module.exports.createProject = require("./create-project-helper"); +module.exports.epicsDashboard = require("./epics-dashboard-helper"); +module.exports.epicDetail = require("./epic-detail-helper"); diff --git a/e2e/helpers/us-detail-helper.js b/e2e/helpers/us-detail-helper.js index c41f53af..b419d433 100644 --- a/e2e/helpers/us-detail-helper.js +++ b/e2e/helpers/us-detail-helper.js @@ -3,45 +3,6 @@ var commonHelper = require('./common-helper'); var helper = module.exports; -helper.teamRequirement = function() { - let el = $('tg-us-team-requirement-button'); - - let obj = { - el: el, - - toggleStatus: async function(){ - await el.$("label").click(); - await browser.waitForAngular(); - }, - - isRequired: async function() { - let classes = await el.$("label").getAttribute('class'); - return classes.includes("active"); - } - }; - - return obj; -}; - -helper.clientRequirement = function() { - let el = $('tg-us-client-requirement-button'); - - let obj = { - el: el, - - toggleStatus: async function(){ - await el.$("label").click(); - await browser.waitForAngular(); - }, - - isRequired: async function() { - let classes = await el.$("label").getAttribute('class'); - return classes.includes("active"); - } - }; - - return obj; -}; helper.relatedTaskForm = async function(form, name, status, assigned_to) { await form.$('input').sendKeys(name); diff --git a/e2e/shared/detail.js b/e2e/shared/detail.js index 3024a595..f8694cbd 100644 --- a/e2e/shared/detail.js +++ b/e2e/shared/detail.js @@ -274,12 +274,12 @@ shared.blockTesting = async function() { let descriptionText = await $('.block-description').getText(); expect(descriptionText).to.be.equal('This is a testing block reason'); - let isDisplayed = $('.block-description').isDisplayed(); + let isDisplayed = $('.block-desc-container').isDisplayed(); expect(isDisplayed).to.be.equal.true; blockHelper.unblock(); - isDisplayed = $('.block-description').isDisplayed(); + isDisplayed = $('.block-desc-container').isDisplayed(); expect(isDisplayed).to.be.equal.false; await notifications.success.close(); @@ -548,3 +548,37 @@ shared.customFields = function(typeIndex) { expect(fieldText).to.be.equal('test text2 edit'); }); }; + +shared.teamRequirementTesting = function() { + it('team requirement edition', async function() { + let requirementHelper = detailHelper.teamRequirement(); + let isRequired = await requirementHelper.isRequired(); + + // Toggle + requirementHelper.toggleStatus(); + let newIsRequired = await requirementHelper.isRequired(); + expect(isRequired).to.be.not.equal(newIsRequired); + + // Toggle again + requirementHelper.toggleStatus(); + newIsRequired = await requirementHelper.isRequired(); + expect(isRequired).to.be.equal(newIsRequired); + }); +} + +shared.clientRequirementTesting = function () { + it('client requirement edition', async function() { + let requirementHelper = detailHelper.clientRequirement(); + let isRequired = await requirementHelper.isRequired(); + + // Toggle + requirementHelper.toggleStatus(); + let newIsRequired = await requirementHelper.isRequired(); + expect(isRequired).to.be.not.equal(newIsRequired); + + // Toggle again + requirementHelper.toggleStatus(); + newIsRequired = await requirementHelper.isRequired(); + expect(isRequired).to.be.equal(newIsRequired); + }); +} diff --git a/e2e/suites/admin/attributes/custom-fields.e2e.js b/e2e/suites/admin/attributes/custom-fields.e2e.js index 0a61db8c..aef42ac7 100644 --- a/e2e/suites/admin/attributes/custom-fields.e2e.js +++ b/e2e/suites/admin/attributes/custom-fields.e2e.js @@ -16,8 +16,64 @@ describe('custom-fields', function() { }); describe('create custom fields', function() { + describe('epics', function() { + let typeIndex = 0; + + it('create', async function() { + let oldCountCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); + + await customFieldsHelper.create(typeIndex, 'test1-text', 'desc1', 1); + + // debounce :( + await utils.notifications.success.open(); + await browser.sleep(2000); + + await customFieldsHelper.create(typeIndex, 'test1-multi', 'desc1', 3); + + // debounce :( + await utils.notifications.success.open(); + await browser.sleep(2000); + + let countCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); + + expect(countCustomFields).to.be.equal(oldCountCustomFields + 2); + }); + + it('edit', async function() { + customFieldsHelper.edit(typeIndex, 0, 'edit', 'desc2', 2); + + let open = await utils.notifications.success.open(); + + expect(open).to.be.true; + + await utils.notifications.success.close(); + }); + + it('drag', async function() { + let nameOld = await customFieldsHelper.getName(typeIndex, 0); + + await customFieldsHelper.drag(typeIndex, 0, 1); + + let nameNew = await customFieldsHelper.getName(typeIndex, 1); + + expect(nameNew).to.be.equal(nameOld); + }); + + it('delete', async function() { + let oldCountCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); + + await customFieldsHelper.delete(typeIndex, 0); + + await browser.wait(async function() { + let countCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); + + return countCustomFields === oldCountCustomFields - 1; + }, 4000); + }); + }); + describe('userstories', function() { - let typeIndex = 0; + let typeIndex = 1; it('create', async function() { let oldCountCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); @@ -73,7 +129,7 @@ describe('custom-fields', function() { }); describe('tasks', function() { - let typeIndex = 1; + let typeIndex = 2; it('create', async function() { let oldCountCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); @@ -126,7 +182,7 @@ describe('custom-fields', function() { }); describe('issues', function() { - let typeIndex = 2; + let typeIndex = 3; it('create', async function() { let oldCountCustomFields = await customFieldsHelper.getCustomFiledsByType(typeIndex).count(); @@ -180,5 +236,6 @@ describe('custom-fields', function() { }, 4000); }); }); + }); }); diff --git a/e2e/suites/admin/members.e2e.js b/e2e/suites/admin/members.e2e.js index 2cbf6904..5178b139 100644 --- a/e2e/suites/admin/members.e2e.js +++ b/e2e/suites/admin/members.e2e.js @@ -8,7 +8,7 @@ var chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); var expect = chai.expect; -describe.only('admin - members', function() { +describe('admin - members', function() { before(async function(){ browser.get(browser.params.glob.host + 'project/project-0/admin/memberships'); diff --git a/e2e/suites/epics/epic-dashboard.e2e.js b/e2e/suites/epics/epic-dashboard.e2e.js index 370cc6af..9eb5d606 100644 --- a/e2e/suites/epics/epic-dashboard.e2e.js +++ b/e2e/suites/epics/epic-dashboard.e2e.js @@ -1,5 +1,5 @@ var utils = require('../../utils'); -var epicsHelper = require('../../helpers/epics-helper'); +var epicsDashboardHelper = require('../../helpers').epicsDashboard; var chai = require('chai'); var chaiAsPromised = require('chai-as-promised'); @@ -8,7 +8,7 @@ chai.use(chaiAsPromised); var expect = chai.expect; describe('Epics Dashboard', function(){ - let usUrl = ''; + let epicsUrl = ''; before(async function(){ await utils.nav @@ -17,7 +17,7 @@ describe('Epics Dashboard', function(){ .epics() .go(); - usUrl = await browser.getCurrentUrl(); + epicsUrl = await browser.getCurrentUrl(); }); it('screenshot', async function() { @@ -25,13 +25,23 @@ describe('Epics Dashboard', function(){ }); it('display child stories', async function() { - let epic = epicsHelper.epic(); + let epic = epicsDashboardHelper.epic(); let childStoriesNum = await epic.displayUserStoriesinEpic(); expect(childStoriesNum).to.be.above(0); }); + it('create Epic', async function() { + let date = Date.now(); + let description = Math.random().toString(36).substring(7); + let epic = epicsDashboardHelper.epic(); + let currentEpicsNum = await epic.getEpics(); + await epic.createEpic(date, description); + let newEpicsNum = await epic.getEpics(); + expect(newEpicsNum).to.be.above(currentEpicsNum); + }); + it('change epic assigned from dashboard', async function() { - let epic = epicsHelper.epic(); + let epic = epicsDashboardHelper.epic(); await epic.resetAssignedTo(); let currentAssigned = await epic.getAssignedTo(); await epic.editAssignedTo(); @@ -40,15 +50,14 @@ describe('Epics Dashboard', function(){ }); it('remove assigned from dashboard', async function() { - let epic = epicsHelper.epic(); + let epic = epicsDashboardHelper.epic(); await epic.resetAssignedTo(); let unAssigned = await epic.removeAssignedTo(); - console.log(unAssigned); expect(unAssigned).to.be.equal('Unassigned'); }); it('change status from dashboard', async function() { - let epic = epicsHelper.epic(); + let epic = epicsDashboardHelper.epic(); await epic.resetStatus(); let currentStatus = await epic.getStatus(); await epic.editStatus(); @@ -57,22 +66,11 @@ describe('Epics Dashboard', function(){ }); it('remove columns from dashboard', async function() { - let epic = epicsHelper.epic(); + let epic = epicsDashboardHelper.epic(); let currentColumns = await epic.getColumns(); await epic.removeColumns(); let newColumns = await epic.getColumns(); expect(currentColumns).to.be.above(newColumns); }); - it.only('create Epic', async function() { - let date = Date.now(); - let description = Math.random().toString(36).substring(7); - let epic = epicsHelper.epic(); - let currentEpicsNum = await epic.getEpics(); - await epic.createEpic(date, description); - let newEpicsNum = await epic.getEpics(); - console.log(currentEpicsNum, newEpicsNum); - expect(newEpicsNum).to.be.above(currentEpicsNum); - }); - }) diff --git a/e2e/suites/epics/epic-detail.e2e.js b/e2e/suites/epics/epic-detail.e2e.js new file mode 100644 index 00000000..66c5e34a --- /dev/null +++ b/e2e/suites/epics/epic-detail.e2e.js @@ -0,0 +1,100 @@ +var utils = require('../../utils'); +var sharedDetail = require('../../shared/detail'); +var epicDetailHelper = require('../../helpers').epicDetail; + +var chai = require('chai'); +var chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); +var expect = chai.expect; + +describe('Epic detail', async function(){ + let epicUrl = ''; + + before(async function(){ + await utils.nav + .init() + .project('Project Example 0') + .epics() + .epic(0) + .go(); + + epicUrl = await browser.getCurrentUrl(); + }); + + it('screenshot', async function() { + await utils.common.takeScreenshot("epics", "detail"); + }); + + it('color edition', async function() { + let colorEditor = epicDetailHelper.colorEditor(); + await colorEditor.open(); + await colorEditor.selectFirstColor(); + await colorEditor.open(); + await colorEditor.selectLastColor(); + await utils.common.takeScreenshot("epics", "detail color updated"); + }); + + it('title edition', sharedDetail.titleTesting); + + it('tags edition', sharedDetail.tagsTesting); + + describe('description', sharedDetail.descriptionTesting); + + describe('related userstories', function() { + let relatedUserstories = epicDetailHelper.relatedUserstories(); + it('create new user story', async function(){ + await relatedUserstories.createNewUserStory("Testing subject"); + }); + + it('create new user stories in bulk', async function(){ + await relatedUserstories.createNewUserStories("Testing subject1\nTesting subject 2"); + }); + + it('add related userstory', async function(){ + await relatedUserstories.selectFirstRelatedUserstory(); + }); + + it('delete related userstory', async function(){ + await relatedUserstories.deleteFirstRelatedUserstory(); + }) + }); + + it('status edition', sharedDetail.statusTesting.bind(this, 'Ready', 'In progress')); + + describe('assigned to edition', sharedDetail.assignedToTesting); + + describe('watchers edition', sharedDetail.watchersTesting); + + it('history', sharedDetail.historyTesting.bind(this, "epics")); + + it('block', sharedDetail.blockTesting); + + describe('team requirement edition', sharedDetail.teamRequirementTesting); + + describe('client requirement edition', sharedDetail.clientRequirementTesting); + + it('attachments', sharedDetail.attachmentTesting); + + describe('custom-fields', sharedDetail.customFields.bind(this, 0)); + + it('screenshot', async function() { + await utils.common.takeScreenshot("epics", "detail updated"); + }); + + describe('delete & redirect', function() { + it('delete', sharedDetail.deleteTesting); + + it('redirected', async function (){ + let url = await browser.getCurrentUrl(); + expect(url).not.to.be.equal(epicUrl); + }); + }); + +}); + + +/* +TODO: +# Related user stories +*/ diff --git a/e2e/suites/user-stories/user-story-detail.e2e.js b/e2e/suites/user-stories/user-story-detail.e2e.js index 9727efa6..bbc52509 100644 --- a/e2e/suites/user-stories/user-story-detail.e2e.js +++ b/e2e/suites/user-stories/user-story-detail.e2e.js @@ -36,35 +36,9 @@ describe('User story detail', function(){ describe('assigned to edition', sharedDetail.assignedToTesting); - it('team requirement edition', async function() { - let requirementHelper = usDetailHelper.teamRequirement(); - let isRequired = await requirementHelper.isRequired(); + describe('team requirement edition', sharedDetail.teamRequirementTesting); - // Toggle - requirementHelper.toggleStatus(); - let newIsRequired = await requirementHelper.isRequired(); - expect(isRequired).to.be.not.equal(newIsRequired); - - // Toggle again - requirementHelper.toggleStatus(); - newIsRequired = await requirementHelper.isRequired(); - expect(isRequired).to.be.equal(newIsRequired); - }); - - it('client requirement edition', async function() { - let requirementHelper = usDetailHelper.clientRequirement(); - let isRequired = await requirementHelper.isRequired(); - - // Toggle - requirementHelper.toggleStatus(); - let newIsRequired = await requirementHelper.isRequired(); - expect(isRequired).to.be.not.equal(newIsRequired); - - // Toggle again - requirementHelper.toggleStatus(); - newIsRequired = await requirementHelper.isRequired(); - expect(isRequired).to.be.equal(newIsRequired); - }); + describe('client requirement edition', sharedDetail.clientRequirementTesting); describe('watchers edition', sharedDetail.watchersTesting); diff --git a/e2e/utils/nav.js b/e2e/utils/nav.js index 17710dbe..b3c32daa 100644 --- a/e2e/utils/nav.js +++ b/e2e/utils/nav.js @@ -46,11 +46,21 @@ var actions = { return common.waitLoader(); }, + epics: async function() { await common.link($('#nav-epics a')); return common.waitLoader(); }, + + epic: async function(index) { + let epic = $$('.e2e-epic-row .name a').get(index); + + await common.link(epic); + + return common.waitLoader(); + }, + backlog: async function() { await common.link($$('#nav-backlog a').first()); @@ -110,6 +120,10 @@ var nav = { this.actions.push(actions.epics.bind(null, index)); return this; }, + epic: function(index) { + this.actions.push(actions.epic.bind(null, index)); + return this; + }, backlog: function(index) { this.actions.push(actions.backlog.bind(null, index)); return this; diff --git a/gulpfile.js b/gulpfile.js index b69e5c92..3fbc02a5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -130,6 +130,7 @@ paths.coffee_order = [ paths.app + "coffee/modules/backlog/*.coffee", paths.app + "coffee/modules/taskboard/*.coffee", paths.app + "coffee/modules/kanban/*.coffee", + paths.app + "coffee/modules/epics/*.coffee", paths.app + "coffee/modules/issues/*.coffee", paths.app + "coffee/modules/userstories/*.coffee", paths.app + "coffee/modules/tasks/*.coffee", diff --git a/run-e2e.js b/run-e2e.js index 77d8e48b..56d78341 100644 --- a/run-e2e.js +++ b/run-e2e.js @@ -12,6 +12,7 @@ var suites = [ 'wiki', 'admin', 'issues', + 'epics', 'tasks', 'userProfile', 'userStories', From c5f9cd54eb42f6738790947466a83fd46d08e114 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 29 Aug 2016 12:34:40 +0200 Subject: [PATCH 199/315] Fixing status style on epics dashboard --- app/modules/epics/dashboard/epic-row/epic-row.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 91dbda04..c8d78a16 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -110,6 +110,7 @@ list-style-type: none; margin: 0; position: absolute; + text-align: left; top: 2.5rem; width: 200px; z-index: 99; From 7070f181ac7579ed7b6e4e0d0d12a21e1e075cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 29 Aug 2016 12:58:17 +0200 Subject: [PATCH 200/315] Create epics lightbox --- app/coffee/utils.coffee | 13 ++++++ .../color-selector.controller.coffee | 44 ++++++++----------- .../color-selector.controller.spec.coffee | 2 +- .../color-selector.directive.coffee | 25 ++++++----- .../color-selector/color-selector.jade | 18 +++++--- .../color-selector/color-selector.scss | 0 .../tags/components/add-tag-input.jade | 1 - .../create-epic/create-epic.controller.coffee | 28 +++++++++--- .../create-epic/create-epic.directive.coffee | 2 +- .../epics/create-epic/create-epic.jade | 4 +- .../epics-dashboard.controller.coffee | 4 +- app/partials/epic/epic-detail.jade | 3 +- 12 files changed, 88 insertions(+), 56 deletions(-) rename app/modules/components/{tags => }/color-selector/color-selector.controller.coffee (63%) rename app/modules/components/{tags => }/color-selector/color-selector.controller.spec.coffee (98%) rename app/modules/components/{tags => }/color-selector/color-selector.directive.coffee (73%) rename app/modules/components/{tags => }/color-selector/color-selector.jade (64%) rename app/modules/components/{tags => }/color-selector/color-selector.scss (100%) diff --git a/app/coffee/utils.coffee b/app/coffee/utils.coffee index 1bffd7c7..b4580029 100644 --- a/app/coffee/utils.coffee +++ b/app/coffee/utils.coffee @@ -239,6 +239,17 @@ patch = (oldImmutable, newImmutable) -> return pathObj +DEFAULT_COLOR_LIST = [ + '#fce94f', '#edd400', '#c4a000', '#8ae234', '#73d216', '#4e9a06', '#d3d7cf', + '#fcaf3e', '#f57900', '#ce5c00', '#729fcf', '#3465a4', '#204a87', '#888a85', + '#ad7fa8', '#75507b', '#5c3566', '#ef2929', '#cc0000', '#a40000' +] + +getRandomDefaultColor = () -> + return _.sample(DEFAULT_COLOR_LIST) + +getDefaulColorList = () -> + return _.clone(DEFAULT_COLOR_LIST) taiga = @.taiga taiga.addClass = addClass @@ -267,3 +278,5 @@ taiga.defineImmutableProperty = defineImmutableProperty taiga.isImage = isImage taiga.isPdf = isPdf taiga.patch = patch +taiga.getRandomDefaultColor = getRandomDefaultColor +taiga.getDefaulColorList = getDefaulColorList diff --git a/app/modules/components/tags/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee similarity index 63% rename from app/modules/components/tags/color-selector/color-selector.controller.coffee rename to app/modules/components/color-selector/color-selector.controller.coffee index 2d817a15..d9779606 100644 --- a/app/modules/components/tags/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -17,41 +17,35 @@ # File: color-selector.controller.coffee ### -module = angular.module('taigaCommon') +taiga = @.taiga +getDefaulColorList = taiga.getDefaulColorList + class ColorSelectorController - constructor: () -> - @.colorList = [ - '#fce94f', - '#edd400', - '#c4a000', - '#8ae234', - '#73d216', - '#4e9a06', - '#d3d7cf', - '#fcaf3e', - '#f57900', - '#ce5c00', - '#729fcf', - '#3465a4', - '#204a87', - '#888a85', - '#ad7fa8', - '#75507b', - '#5c3566', - '#ef2929', - '#cc0000', - '#a40000' - ] + @.colorList = getDefaulColorList() + + if @.initColor + @.color = @.initColor + @.displaycolorList = false toggleColorList: () -> @.displaycolorList = !@.displaycolorList + if @.isRequired and not @.color + @.color = @.initColor + onSelectDropdownColor: (color) -> + @.color = color @.onSelectColor({color: color}) @.toggleColorList() + onKeyDown: (event) -> + if event.which == 13 # ENTER + event.stopPropagation() + if @.color or not @.isRequired + @.onSelectDropdownColor(@.color) -module.controller("ColorSelectorCtrl", ColorSelectorController) + +angular.module('taigaComponents').controller("ColorSelectorCtrl", ColorSelectorController) diff --git a/app/modules/components/tags/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee similarity index 98% rename from app/modules/components/tags/color-selector/color-selector.controller.spec.coffee rename to app/modules/components/color-selector/color-selector.controller.spec.coffee index ec212331..7456195c 100644 --- a/app/modules/components/tags/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -29,7 +29,7 @@ describe "ColorSelector", -> return null beforeEach -> - module "taigaCommon" + module "taigaComponents" _mocks() diff --git a/app/modules/components/tags/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee similarity index 73% rename from app/modules/components/tags/color-selector/color-selector.directive.coffee rename to app/modules/components/color-selector/color-selector.directive.coffee index 67e02f57..9652fb33 100644 --- a/app/modules/components/tags/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -17,21 +17,20 @@ # File: color-selector.directive.coffee ### -module = angular.module('taigaCommon') - ColorSelectorDirective = ($timeout) -> - link = (scope, el) -> - timeout = null + link = (scope, el, attrs, ctrl) -> + # Animation + _timeout = null cancel = () -> - $timeout.cancel(timeout) - timeout = null + $timeout.cancel(_timeout) + _timeout = null close = () -> - return if timeout + return if _timeout - timeout = $timeout (() -> - scope.vm.displaycolorList = false + _timeout = $timeout (() -> + scope.vm.toggleColorList() ), 400 el.find('.color-selector') @@ -45,17 +44,19 @@ ColorSelectorDirective = ($timeout) -> return { link: link, scope:{ + isRequired: "=" onSelectColor: "&", - color: "=" + initColor: "=" }, - templateUrl:"components/tags/color-selector/color-selector.html", + templateUrl:"components/color-selector/color-selector.html", controller: "ColorSelectorCtrl", controllerAs: "vm", bindToController: true } + ColorSelectorDirective.$inject = [ "$timeout" ] -module.directive("tgColorSelector", ColorSelectorDirective) +angular.module('taigaComponents').directive("tgColorSelector", ColorSelectorDirective) diff --git a/app/modules/components/tags/color-selector/color-selector.jade b/app/modules/components/color-selector/color-selector.jade similarity index 64% rename from app/modules/components/tags/color-selector/color-selector.jade rename to app/modules/components/color-selector/color-selector.jade index 54402386..7ffccb49 100644 --- a/app/modules/components/tags/color-selector/color-selector.jade +++ b/app/modules/components/color-selector/color-selector.jade @@ -12,19 +12,23 @@ ng-title="color" ng-click="vm.onSelectDropdownColor(color)" ) - li.empty-color(ng-click="vm.onSelectDropdownColor(null)") + li.empty-color( + ng-if="!vm.isRequired" + ng-click="vm.onSelectDropdownColor(null)" + ) .custom-color-selector .display-custom-color.empty-color( - ng-if="!customColor.color || customColor.color.length < 7" + ng-if="!vm.color" ) .display-custom-color( - ng-if="customColor.color.length === 7" - ng-style="{'background': customColor.color}" - ng-click="vm.onSelectDropdownColor(customColor.color)" + ng-if="vm.color" + ng-style="{'background': vm.color}" + ng-click="vm.onSelectDropdownColor(vm.color)" ) input.custom-color-input( type="text" maxlength="7" - placeholder="#000000" - ng-model="customColor.color" + placeholder="Type hex code" + ng-model="vm.color" + ng-keydown="vm.onKeyDown($event)" ) diff --git a/app/modules/components/tags/color-selector/color-selector.scss b/app/modules/components/color-selector/color-selector.scss similarity index 100% rename from app/modules/components/tags/color-selector/color-selector.scss rename to app/modules/components/color-selector/color-selector.scss diff --git a/app/modules/components/tags/components/add-tag-input.jade b/app/modules/components/tags/components/add-tag-input.jade index 7e1b11ca..5e8de537 100644 --- a/app/modules/components/tags/components/add-tag-input.jade +++ b/app/modules/components/tags/components/add-tag-input.jade @@ -21,7 +21,6 @@ tg-color-selector( ng-if="!vm.disableColorSelection" - color="vm.newTag.color", on-select-color="vm.selectColor(color)" ) diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index 638a2480..eb78e615 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -19,30 +19,40 @@ taiga = @.taiga trim = taiga.trim +getRandomDefaultColor = taiga.getRandomDefaultColor module = angular.module("taigaEpics") class CreateEpicController @.$inject = [ "tgResources" + "tgAttachmentsService" + "$q" ] - constructor: (@rs) -> + constructor: (@rs, @attachmentsService, @q) -> @.newEpic = { - color: null - projecti: @.project.id + color: getRandomDefaultColor() + project: @.project.id status: @.project.default_epic_status tags: [] } @.attachments = Immutable.List() createEpic: () -> - return @rs.epics.post(@.newEpic).then () => - @.onReloadEpics() + promise = @rs.epics.post(@.newEpic) + promise.then (response) => + @._createAttachments(response.data) + + promise.then (data) => + @.onCreateEpic() + + # Color selector selectColor: (color) -> @.newEpic.color = color + # Tags addTag: (name, color) -> name = trim(name.toLowerCase()) @@ -52,5 +62,13 @@ class CreateEpicController deleteTag: (tag) -> _.remove @.newEpic.tags, (it) -> it[0] == tag[0] + # Attachments + addAttachment: (attachment) -> + @.attachments.push(attachment) + + _createAttachments: (epic) -> + promises = _.map @.attachments.toJS(), (attachment) -> + return attachmentsService.upload(attachment.file, epic.id, epic.project, 'epic') + return @q.all(promises) module.controller("CreateEpicCtrl", CreateEpicController) diff --git a/app/modules/epics/create-epic/create-epic.directive.coffee b/app/modules/epics/create-epic/create-epic.directive.coffee index 6fd7883a..3e9e8f36 100644 --- a/app/modules/epics/create-epic/create-epic.directive.coffee +++ b/app/modules/epics/create-epic/create-epic.directive.coffee @@ -28,7 +28,7 @@ CreateEpicDirective = () -> bindToController: true, scope: { project: '=', - onReloadEpics: '&' + onCreateEpic: '&' } } diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 7d1e7e73..99efc7f9 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -7,7 +7,8 @@ tg-lightbox-close ) fieldset tg-color-selector( - color="vm.newEpic.color", + is-required="true" + init-color="vm.newEpic.color" on-select-color="vm.selectColor(color)" ) input.e2e-create-epic-subject( @@ -46,6 +47,7 @@ tg-lightbox-close fieldset tg-attachments-simple( attachments="vm.attachments" + on-add="vm.addAttachment(attachment)" ) .settings fieldset.team-requirement diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 26c2edbf..d6f23146 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -55,10 +55,10 @@ class EpicsDashboardController @lightboxFactory.create('tg-create-epic', { "class": "lightbox lightbox-create-epic open" "project": "project" - "on-reload-epics": "reloadEpics()" + "on-create-epic": "onCreateEpic()" }, { "project": @.project - "reloadEpics": @._onCreateEpic.bind(this) + "onCreateEpic": @._onCreateEpic.bind(this) }) module.controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/partials/epic/epic-detail.jade b/app/partials/epic/epic-detail.jade index b03ec020..edee4440 100644 --- a/app/partials/epic/epic-detail.jade +++ b/app/partials/epic/epic-detail.jade @@ -20,7 +20,8 @@ div.wrapper( .detail-header-container tg-color-selector( - color="epic.color", + is-required="true" + init-color="epic.color" on-select-color="ctrl.onSelectColor(color)" ) tg-detail-header( From 7aa281c52089739b78065462c878c7223364bcd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 30 Aug 2016 21:04:24 +0200 Subject: [PATCH 201/315] Improve add-epic form --- .../color-selector.controller.coffee | 10 +++++---- .../color-selector.directive.coffee | 15 ++++++------- .../color-selector/color-selector.jade | 2 +- .../create-epic/create-epic.controller.coffee | 21 +++++++++++++------ .../create-epic/create-epic.directive.coffee | 15 ++++++++++--- .../epics/create-epic/create-epic.jade | 14 ++++++------- 6 files changed, 48 insertions(+), 29 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee index d9779606..fc1214d0 100644 --- a/app/modules/components/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -28,14 +28,16 @@ class ColorSelectorController if @.initColor @.color = @.initColor - @.displaycolorList = false - - toggleColorList: () -> - @.displaycolorList = !@.displaycolorList + @.displayColorList = false + resetColor: () -> if @.isRequired and not @.color @.color = @.initColor + toggleColorList: () -> + @.displayColorList = !@.displayColorList + @.resetColor() + onSelectDropdownColor: (color) -> @.color = color @.onSelectColor({color: color}) diff --git a/app/modules/components/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee index 9652fb33..817d2e9b 100644 --- a/app/modules/components/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -30,7 +30,8 @@ ColorSelectorDirective = ($timeout) -> return if _timeout _timeout = $timeout (() -> - scope.vm.toggleColorList() + ctrl.displayColorList = false + ctrl.resetColor() ), 400 el.find('.color-selector') @@ -43,15 +44,15 @@ ColorSelectorDirective = ($timeout) -> return { link: link, - scope:{ - isRequired: "=" - onSelectColor: "&", - initColor: "=" - }, templateUrl:"components/color-selector/color-selector.html", controller: "ColorSelectorCtrl", controllerAs: "vm", - bindToController: true + bindToController: { + isRequired: "=", + onSelectColor: "&", + initColor: "=" + }, + scope: {}, } diff --git a/app/modules/components/color-selector/color-selector.jade b/app/modules/components/color-selector/color-selector.jade index 7ffccb49..80025485 100644 --- a/app/modules/components/color-selector/color-selector.jade +++ b/app/modules/components/color-selector/color-selector.jade @@ -4,7 +4,7 @@ ng-class="{'empty-color': !vm.color}" ng-style="{'background': vm.color}" ) - .color-selector-dropdown(ng-if="vm.displaycolorList") + .color-selector-dropdown(ng-if="vm.displayColorList") ul.color-selector-dropdown-list.e2e-color-dropdown li.color-selector-option( ng-repeat="color in vm.colorList" diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index eb78e615..1fcbb4d9 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -21,16 +21,16 @@ taiga = @.taiga trim = taiga.trim getRandomDefaultColor = taiga.getRandomDefaultColor -module = angular.module("taigaEpics") class CreateEpicController @.$inject = [ "tgResources" + "$tgConfirm" "tgAttachmentsService" "$q" ] - constructor: (@rs, @attachmentsService, @q) -> + constructor: (@rs, @confirm, @attachmentsService, @q) -> @.newEpic = { color: getRandomDefaultColor() project: @.project.id @@ -40,13 +40,22 @@ class CreateEpicController @.attachments = Immutable.List() createEpic: () -> - promise = @rs.epics.post(@.newEpic) + return if not @.validateForm() + @.loading = true + + promise = @rs.epics.post(@.newEpic) promise.then (response) => @._createAttachments(response.data) - - promise.then (data) => + promise.then (response) => @.onCreateEpic() + promise.then null, (response) => + @.setFormErrors(response.data) + + if response.data._error_message + confirm.notify("error", response.data._error_message) + promise.finally () => + @.loading = false # Color selector selectColor: (color) -> @@ -71,4 +80,4 @@ class CreateEpicController return attachmentsService.upload(attachment.file, epic.id, epic.project, 'epic') return @q.all(promises) -module.controller("CreateEpicCtrl", CreateEpicController) +angular.module("taigaEpics").controller("CreateEpicCtrl", CreateEpicController) diff --git a/app/modules/epics/create-epic/create-epic.directive.coffee b/app/modules/epics/create-epic/create-epic.directive.coffee index 3e9e8f36..abb527a7 100644 --- a/app/modules/epics/create-epic/create-epic.directive.coffee +++ b/app/modules/epics/create-epic/create-epic.directive.coffee @@ -20,16 +20,25 @@ module = angular.module('taigaEpics') CreateEpicDirective = () -> + link = (scope, el, attrs, ctrl) -> + form = el.find("form").checksley() + + ctrl.validateForm = => + return form.validate() + + ctrl.setFormErrors = (errors) => + form.setErrors(errors) return { + link: link, templateUrl:"epics/create-epic/create-epic.html", controller: "CreateEpicCtrl", controllerAs: "vm", - bindToController: true, - scope: { + bindToController: { project: '=', onCreateEpic: '&' - } + }, + scope: {} } CreateEpicDirective.$inject = [] diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 99efc7f9..29d3ac17 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -14,23 +14,19 @@ tg-lightbox-close input.e2e-create-epic-subject( type="text" name="subject" - maxlength="140" ng-model="vm.newEpic.subject" tg-auto-select placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}" - required + data-required="true" + data-maxlength="140" ) fieldset select.e2e-create-epic-status( id="epic-status" name="status" ng-model="vm.newEpic.status" + ng-options="s.id as s.name for s in vm.project.epic_statuses | orderBy:'order'" ) - option( - ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" - ng-value="::status.id" - ng-selected="vm.project.default_epic_status" - ) {{::status.name}} fieldset.tags-block tg-tag-line-common( project="vm.project" @@ -93,7 +89,9 @@ tg-lightbox-close placeholder="{{'EPICS.CREATE.BLOCKED_NOTE_PLACEHOLDER' | translate}}" ) fieldset - input.button-green.create-epic-button.e2e-create-epic-button( + button.button-green.create-epic-button.e2e-create-epic-button( type="submit" + tg-loading="vm.loading" + title="{{ 'EPICS.CREATE.CREATE_EPIC' | translate }}" translate="EPICS.CREATE.CREATE_EPIC" ) From a5a87a48c752e2fab49678c5fed5f4e9dfe0dd66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 31 Aug 2016 12:32:58 +0200 Subject: [PATCH 202/315] Fix some style errors in the create-epic lightbox --- app/locales/taiga/locale-en.json | 1 + .../epics/create-epic/create-epic.jade | 36 ++++++++++--------- .../epics/create-epic/create-epic.scss | 8 +++++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index cd73c545..38a18a64 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -427,6 +427,7 @@ }, "CREATE": { "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", "TEAM_REQUIREMENT": "Team requirement", "CLIENT_REQUIREMENT": "Client requirement", "BLOCKED": "Blocked", diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 29d3ac17..da51d342 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -5,21 +5,25 @@ tg-lightbox-close form( ng-submit="vm.createEpic()" ) - fieldset - tg-color-selector( - is-required="true" - init-color="vm.newEpic.color" - on-select-color="vm.selectColor(color)" - ) - input.e2e-create-epic-subject( - type="text" - name="subject" - ng-model="vm.newEpic.subject" - tg-auto-select - placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}" - data-required="true" - data-maxlength="140" - ) + .subject-container + .color-selector + fieldset + tg-color-selector( + is-required="true" + init-color="vm.newEpic.color" + on-select-color="vm.selectColor(color)" + ) + .subject + fieldset + input.e2e-create-epic-subject( + type="text" + name="subject" + ng-model="vm.newEpic.subject" + tg-auto-select + placeholder="{{'COMMON.FIELDS.SUBJECT' | translate}}" + data-required="true" + data-maxlength="140" + ) fieldset select.e2e-create-epic-status( id="epic-status" @@ -37,7 +41,7 @@ tg-lightbox-close ) fieldset textarea.e2e-create-epic-description( - ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}" + ng-attr-placeholder="{{'EPICS.CREATE.PLACEHOLDER_DESCRIPTION' | translate}}" ng-model="vm.newEpic.description" ) fieldset diff --git a/app/modules/epics/create-epic/create-epic.scss b/app/modules/epics/create-epic/create-epic.scss index ad2090c4..e778e047 100644 --- a/app/modules/epics/create-epic/create-epic.scss +++ b/app/modules/epics/create-epic/create-epic.scss @@ -7,6 +7,14 @@ max-width: 700px; width: 90%; } + .subject-container { + align-items: center; + display: flex; + .subject { + padding-left: 1rem; + width: 100%; + } + } .attachments { margin-bottom: 0; } From 61d99fea14d239898a16045b95773c0c52239ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 31 Aug 2016 21:08:46 +0200 Subject: [PATCH 203/315] Fix test about color selector --- .../color-selector.controller.spec.coffee | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee index 7456195c..09597fee 100644 --- a/app/modules/components/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -36,19 +36,14 @@ describe "ColorSelector", -> inject ($controller) -> controller = $controller - colorSelectorCtrl = controller "ColorSelectorCtrl" - colorSelectorCtrl.colorList = [ - '#fce94f', - '#edd400', - '#c4a000', - ] - colorSelectorCtrl.displaycolorList = false it "display Color Selector", () -> + colorSelectorCtrl = controller "ColorSelectorCtrl" colorSelectorCtrl.toggleColorList() - expect(colorSelectorCtrl.displaycolorList).to.be.true + expect(colorSelectorCtrl.displayColorList).to.be.true it "on select Color", () -> + colorSelectorCtrl = controller "ColorSelectorCtrl" colorSelectorCtrl.toggleColorList = sinon.stub() color = '#FFFFFF' @@ -58,3 +53,17 @@ describe "ColorSelector", -> colorSelectorCtrl.onSelectDropdownColor(color) expect(colorSelectorCtrl.toggleColorList).have.been.called expect(colorSelectorCtrl.onSelectColor).to.have.been.calledWith({color: color}) + + it "save on keydown Enter", () -> + colorSelectorCtrl = controller "ColorSelectorCtrl" + colorSelectorCtrl.onSelectDropdownColor = sinon.stub() + + event = {which: 13, stopPropagation: sinon.stub()} + color = "#fabada" + + colorSelectorCtrl.color = color + + colorSelectorCtrl.onKeyDown(event) + expect(event.stopPropagation).have.been.called + expect(colorSelectorCtrl.onSelectDropdownColor).have.been.called + expect(colorSelectorCtrl.onSelectDropdownColor).have.been.calledWith(color) From f0a6ab4c0b79f83164b391b7f75d7aa6007cf1be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 31 Aug 2016 21:09:08 +0200 Subject: [PATCH 204/315] remove a log trace --- .../discover-home-order-by.controller.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee index a2799d09..6b00be7e 100644 --- a/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee +++ b/app/modules/discover/components/discover-home-order-by/discover-home-order-by.controller.coffee @@ -44,7 +44,6 @@ class DiscoverHomeOrderByController orderBy: (type) -> @.currentOrderBy = type @.is_open = false - console.log "Ijsdfkldsfklj" @.onChange({orderBy: @.currentOrderBy}) angular.module("taigaDiscover").controller("DiscoverHomeOrderBy", DiscoverHomeOrderByController) From 547b5c64586fb950bf1263da632bb1d6c189d87d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 31 Aug 2016 21:09:35 +0200 Subject: [PATCH 205/315] Fix create-epic lightbox tests --- .../create-epic/create-epic.controller.coffee | 6 +- .../create-epic.controller.spec.coffee | 90 ++++++++++++++++--- 2 files changed, 84 insertions(+), 12 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index 1fcbb4d9..570ab10c 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -57,6 +57,8 @@ class CreateEpicController promise.finally () => @.loading = false + return promise + # Color selector selectColor: (color) -> @.newEpic.color = color @@ -76,8 +78,8 @@ class CreateEpicController @.attachments.push(attachment) _createAttachments: (epic) -> - promises = _.map @.attachments.toJS(), (attachment) -> - return attachmentsService.upload(attachment.file, epic.id, epic.project, 'epic') + promises = _.map @.attachments.toJS(), (attachment) => + return @attachmentsService.upload(attachment.file, epic.id, epic.project, 'epic') return @q.all(promises) angular.module("taigaEpics").controller("CreateEpicCtrl", CreateEpicController) diff --git a/app/modules/epics/create-epic/create-epic.controller.spec.coffee b/app/modules/epics/create-epic/create-epic.controller.spec.coffee index 3453206d..fa713fd4 100644 --- a/app/modules/epics/create-epic/create-epic.controller.spec.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.spec.coffee @@ -32,11 +32,33 @@ describe "EpicRow", -> provide.value "tgResources", mocks.tgResources + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + provide.value "$tgConfirm", mocks.tgConfirm + + _mockTgAttachmentsService = () -> + mocks.tgAttachmentsService = { + upload: sinon.stub() + } + provide.value "tgAttachmentsService", mocks.tgAttachmentsService + + _mockQ = () -> + mocks.q = { + all: sinon.spy() + } + + provide.value "$q", mocks.q + + _mocks = () -> module ($provide) -> provide = $provide _mockTgResources() - + _mockTgConfirm() + _mockTgAttachmentsService() + _mockQ() return null beforeEach -> @@ -47,17 +69,65 @@ describe "EpicRow", -> inject ($controller) -> controller = $controller - it "create Epic", (done) -> - createEpicCtrl = controller "CreateEpicCtrl" - createEpicCtrl.project = { - id: 7 + it "create Epic with invalid form", () -> + data = { + project: {id: 1, default_epic_status: 1} + validateForm: sinon.stub() + setFormErrors: sinon.stub() + onCreateEpic: sinon.stub() } - createEpicCtrl.newEpic = { - project: createEpicCtrl.project.id + createEpicCtrl = controller "CreateEpicCtrl", null, data + createEpicCtrl.attachments = Immutable.List([{file: "file1"}, {file: "file2"}]) + + data.validateForm.withArgs().returns(false) + + createEpicCtrl.createEpic() + + expect(data.validateForm).have.been.called + expect(mocks.tgResources.epics.post).not.have.been.called + + it "create Epic successfully", (done) -> + data = { + project: {id: 1, default_epic_status: 1} + validateForm: sinon.stub() + setFormErrors: sinon.stub() + onCreateEpic: sinon.stub() } - createEpicCtrl.onReloadEpics = sinon.stub() - promise = mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().resolve() + createEpicCtrl = controller "CreateEpicCtrl", null, data + createEpicCtrl.attachments = Immutable.List([{file: "file1"}, {file: "file2"}]) + + data.validateForm.withArgs().returns(true) + mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().resolve( + {data: {id: 1, project: 1}} + ) createEpicCtrl.createEpic().then () -> - expect(createEpicCtrl.onReloadEpics).have.been.called + expect(data.validateForm).have.been.called + expect(mocks.tgAttachmentsService.upload).have.been.calledTwice + expect(createEpicCtrl.onCreateEpic).have.been.called done() + + # TODO: Talk with JuanFran. How to return a response with an object when a promise is rejected? + # reject_response = {data: {_error_message: "error"}} + # + # + #it "create Epic with an API error", (done) -> + # data = { + # project: {id: 1, default_epic_status: 1} + # validateForm: sinon.stub() + # setFormErrors: sinon.stub() + # onCreateEpic: sinon.stub() + # } + # createEpicCtrl = controller "CreateEpicCtrl", null, data + # createEpicCtrl.attachments = Immutable.List([{file: "file1"}, {file: "file2"}]) + + # data.validateForm.withArgs().returns(true) + # mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().reject(new Error("error")) + + # createEpicCtrl.createEpic().then () -> + # expect(data.validateForm).have.been.called + # expect(mocks.tgAttachmentsService.upload).not.have.been.called + # expect(createEpicCtrl.onCreateEpic).not.have.been.called + # expect(data.setFormErrors).have.been.called + # expect(mocks.tgConfirm.notify).have.been.called + # done() From a12047bc1c1a5e372bf0ced3c3f7ec3f7c61f219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 1 Sep 2016 10:55:36 +0200 Subject: [PATCH 206/315] Remove some commented code --- .../create-epic.controller.spec.coffee | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.controller.spec.coffee b/app/modules/epics/create-epic/create-epic.controller.spec.coffee index fa713fd4..7dac8ef5 100644 --- a/app/modules/epics/create-epic/create-epic.controller.spec.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.spec.coffee @@ -106,28 +106,3 @@ describe "EpicRow", -> expect(mocks.tgAttachmentsService.upload).have.been.calledTwice expect(createEpicCtrl.onCreateEpic).have.been.called done() - - # TODO: Talk with JuanFran. How to return a response with an object when a promise is rejected? - # reject_response = {data: {_error_message: "error"}} - # - # - #it "create Epic with an API error", (done) -> - # data = { - # project: {id: 1, default_epic_status: 1} - # validateForm: sinon.stub() - # setFormErrors: sinon.stub() - # onCreateEpic: sinon.stub() - # } - # createEpicCtrl = controller "CreateEpicCtrl", null, data - # createEpicCtrl.attachments = Immutable.List([{file: "file1"}, {file: "file2"}]) - - # data.validateForm.withArgs().returns(true) - # mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().reject(new Error("error")) - - # createEpicCtrl.createEpic().then () -> - # expect(data.validateForm).have.been.called - # expect(mocks.tgAttachmentsService.upload).not.have.been.called - # expect(createEpicCtrl.onCreateEpic).not.have.been.called - # expect(data.setFormErrors).have.been.called - # expect(mocks.tgConfirm.notify).have.been.called - # done() From 862efde74aea20bbfa6d6ce7c48c3d7b4ed17ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 1 Sep 2016 12:00:15 +0200 Subject: [PATCH 207/315] Fix belong-to-epic to use labels instead of pill --- .../belong-to-epics/belong-to-epics-pill.jade | 2 +- .../belong-to-epics/belong-to-epics-text.jade | 8 ++++---- .../belong-to-epics/belong-to-epics.directive.coffee | 11 +++++------ .../components/belong-to-epics/belong-to-epics.scss | 9 +++++++++ 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics-pill.jade b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade index 61670efa..2ff75ba3 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics-pill.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade @@ -1,5 +1,5 @@ - var hash = "#"; -.belong-to-epic-pill-wrapper(tg-repeat="epic in epics track by epic.get('id')") +span.belong-to-epic-pill-wrapper(tg-repeat="epic in epics track by epic.get('id')") .belong-to-epic-pill( ng-style="{'background': epic.get('color')}" title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" diff --git a/app/modules/components/belong-to-epics/belong-to-epics-text.jade b/app/modules/components/belong-to-epics/belong-to-epics-text.jade index db9614b9..7d23bdbe 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics-text.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics-text.jade @@ -1,10 +1,10 @@ - var hash = "#"; span.belong-to-epic-text-wrapper(tg-repeat="epic in epics track by epic.get('id')") - .belong-to-epic-pill( - ng-style="{'background': epic.get('color')}" - title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" - ) a.belong-to-epic-text( href="" tg-nav="project-epics-detail:project=epic.getIn(['project', 'slug']),ref=epic.get('ref')" ) #{hash}{{epic.get('id')}} {{epic.get('subject')}} + span.belong-to-epic-label( + ng-style="::{'background-color': epic.get('color')}" + translate="EPICS.EPIC" + ) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index 08d4ac43..39eae9fb 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -25,19 +25,18 @@ BelongToEpicsDirective = () -> if scope.epics && !scope.epics.isIterable scope.epics = Immutable.fromJS(scope.epics) - scope.getTemplateUrl = () -> - if attrs.format - return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" - return "components/belong-to-epics/belong-to-epics-pill.html" + templateUrl = (el, attrs) -> + if attrs.format + return "components/belong-to-epics/belong-to-epics-" + attrs.format + ".html" + return "components/belong-to-epics/belong-to-epics-pill.html" return { link: link, scope: { epics: '=' }, - template : '' + templateUrl: templateUrl } -BelongToEpicsDirective.$inject = [] module.directive("tgBelongToEpics", BelongToEpicsDirective) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss index e59c4fb0..e068c2c6 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.scss +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -25,3 +25,12 @@ .belong-to-epic-text { margin-left: .25rem; } +.belong-to-epic-label { + @include font-type(light); + @include font-size(xsmall); + background: $grayer; + border-radius: .25rem; + color: $white; + margin: 0 .5rem; + padding: .1rem .25rem; +} From 5e2278b99aec1d6b4327a24ebbc242f5d037a7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 1 Sep 2016 13:08:08 +0200 Subject: [PATCH 208/315] Minor refactor --- .../epics-table/epics-table.controller.coffee | 7 +++-- .../epics-table.controller.spec.coffee | 26 ++++++++++--------- .../epics-table/epics-table.directive.coffee | 9 ++----- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee index ecc9ae69..a2032a99 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -35,14 +35,13 @@ class EpicsTableController progress: true } - toggleEpicTableOptions: () -> - @.displayOptions = !@.displayOptions - - _checkPermissions: () -> @.permissions = { canEdit: _.includes(@.project.my_permissions, 'modify_epic') } + toggleEpicTableOptions: () -> + @.displayOptions = !@.displayOptions + reorderEpics: (epic, index) -> console.log epic, index diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee index 95f19644..30978472 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee @@ -44,21 +44,23 @@ describe "EpicTable", -> expect(epicTableCtrl.displayOptions).to.be.false it "can edit", () -> - epicTableCtrl = controller "EpicsTableCtrl" - epicTableCtrl.project = { - my_permissions: [ - 'modify_epic' - ] + data = { + project: { + my_permissions: [ + 'modify_epic' + ] + } } - epicTableCtrl._checkPermissions() + epicTableCtrl = controller "EpicsTableCtrl", null, data expect(epicTableCtrl.permissions.canEdit).to.be.true it "can NOT edit", () -> - epicTableCtrl = controller "EpicsTableCtrl" - epicTableCtrl.project = { - my_permissions: [ - 'modify_us' - ] + data = { + project: { + my_permissions: [ + 'modify_us' + ] + } } - epicTableCtrl._checkPermissions() + epicTableCtrl = controller "EpicsTableCtrl", null, data expect(epicTableCtrl.permissions.canEdit).to.be.false diff --git a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee index 39827f77..ceb094a6 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -20,21 +20,16 @@ module = angular.module('taigaEpics') EpicsTableDirective = () -> - - link = (scope, el, attrs, ctrl) -> - ctrl._checkPermissions() - return { - link: link, templateUrl:"epics/dashboard/epics-table/epics-table.html", controller: "EpicsTableCtrl", controllerAs: "vm", - bindToController: true, - scope: { + bindToController: { epics: "=", project: "=", onUpdateEpic: "&" } + scope: {} } EpicsTableDirective.$inject = [] From 84332ff538e684c4e791f9bf9c90686de6419575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 1 Sep 2016 17:04:37 +0200 Subject: [PATCH 209/315] Fix tests --- .../epics-table/epics-table.controller.spec.coffee | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee index 30978472..98c5f3a0 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee @@ -38,7 +38,14 @@ describe "EpicTable", -> controller = $controller it "toggle table options", () -> - epicTableCtrl = controller "EpicsTableCtrl" + data = { + project: { + my_permissions: [ + 'modify_epic' + ] + } + } + epicTableCtrl = controller "EpicsTableCtrl", null, data epicTableCtrl.displayOptions = true epicTableCtrl.toggleEpicTableOptions() expect(epicTableCtrl.displayOptions).to.be.false From 037a3d462fe36981b27f2205be900bd9d750cc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 2 Sep 2016 13:55:21 +0200 Subject: [PATCH 210/315] Sort epics --- .../epic-sortable.directive.coffee | 35 ----------- .../epics-sortable.directive.coffee | 61 +++++++++++++++++++ .../epics-table/epics-table.controller.coffee | 5 +- .../dashboard/epics-table/epics-table.jade | 7 ++- 4 files changed, 69 insertions(+), 39 deletions(-) delete mode 100644 app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee create mode 100644 app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee diff --git a/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee b/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee deleted file mode 100644 index b0d70468..00000000 --- a/app/modules/epics/dashboard/epic-sortable/epic-sortable.directive.coffee +++ /dev/null @@ -1,35 +0,0 @@ -### -# Copyright (C) 2014-2016 Taiga Agile LLC -# -# 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: epic-sortable.directive.coffee -### - -EpicSortableDirective = ($parse) -> - link = (scope, el, attrs) -> - - drake = dragula([el[0]]) - - scope.$on "$destroy", -> - el.off() - drake.destroy() - - return { - link: link - } - -EpicSortableDirective.$inject = [] - -angular.module("taigaComponents").directive("tgEpicSortable", EpicSortableDirective) diff --git a/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee b/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee new file mode 100644 index 00000000..d9063f7a --- /dev/null +++ b/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee @@ -0,0 +1,61 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: epics-sortable.directive.coffee +### + +EpicsSortableDirective = ($parse) -> + link = (scope, el, attrs) -> + callback = $parse(attrs.tgEpicsSortable) + + drake = dragula([el[0]], { + copySortSource: false + copy: false + mirrorContainer: el[0] + moves: (item) -> + return $(item).is('div.epics-table-body-row') + }) + + drake.on 'dragend', (item) -> + itemEl = $(item) + + epic = itemEl.scope().epic + newIndex = itemEl.index() + + scope.$apply () -> + callback(scope, {epic: epic, newIndex: newIndex}) + + scroll = autoScroll(window, { + margin: 20, + pixels: 30, + scrollWhenOutside: true, + autoScroll: () -> + return this.down && drake.dragging + }) + + scope.$on "$destroy", -> + el.off() + drake.destroy() + + return { + link: link + } + +EpicsSortableDirective.$inject = [ + "$parse" +] + +angular.module("taigaComponents").directive("tgEpicsSortable", EpicsSortableDirective) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee index a2032a99..37eb275b 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -42,7 +42,8 @@ class EpicsTableController toggleEpicTableOptions: () -> @.displayOptions = !@.displayOptions - reorderEpics: (epic, index) -> - console.log epic, index + reorderEpic: (epic, newIndex) -> + console.log epic, newIndex + module.controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 1056460c..baaad4e6 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -88,8 +88,11 @@ mixin epicSwitch(name, model) for="switch-progress" ) +epicSwitch('switch-progress', 'vm.column.progress') - .epics-table-body(tg-epic-sortable) - .epics-table-body-row(tg-repeat="epic in vm.epics track by epic.get('id')") + .epics-table-body(tg-epics-sortable="vm.reorderEpic(epic, newIndex)") + .epics-table-body-row( + tg-repeat="epic in vm.epics track by epic.get('id')" + tg-bind-scope + ) tg-epic-row.e2e-epic( epic="epic" project="vm.project" From da6cc6789729479afecd162fa06623a32f8629e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 6 Sep 2016 20:51:04 +0200 Subject: [PATCH 211/315] Divide some long lines --- app/coffee/modules/kanban/main.coffee | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/coffee/modules/kanban/main.coffee b/app/coffee/modules/kanban/main.coffee index 7b875662..f50f9007 100644 --- a/app/coffee/modules/kanban/main.coffee +++ b/app/coffee/modules/kanban/main.coffee @@ -102,7 +102,8 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi filtersReloadContent: () -> @.loadUserstories().then () => - openArchived = _.difference(@kanbanUserstoriesService.archivedStatus, @kanbanUserstoriesService.statusHide) + openArchived = _.difference(@kanbanUserstoriesService.archivedStatus, + @kanbanUserstoriesService.statusHide) if openArchived.length for statusId in openArchived @.loadUserStoriesForStatus({}, statusId) @@ -131,8 +132,10 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi addNewUs: (type, statusId) -> switch type - when "standard" then @rootscope.$broadcast("usform:new", @scope.projectId, statusId, @scope.usStatusList) - when "bulk" then @rootscope.$broadcast("usform:bulk", @scope.projectId, statusId) + when "standard" then @rootscope.$broadcast("usform:new", + @scope.projectId, statusId, @scope.usStatusList) + when "bulk" then @rootscope.$broadcast("usform:bulk", + @scope.projectId, statusId) editUs: (id) -> us = @kanbanUserstoriesService.getUs(id) From 99e04c369fd5c47cb7d0c751cd7195b21a96938a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 7 Sep 2016 15:29:00 +0200 Subject: [PATCH 212/315] Refactor epics module (need tests) --- app/coffee/app.coffee | 1 + app/locales/taiga/locale-en.json | 1 + .../create-epic/create-epic.controller.coffee | 43 ++++---- .../create-epic/create-epic.directive.coffee | 7 +- .../epics/create-epic/create-epic.jade | 10 +- .../epic-row/epic-row.controller.coffee | 94 ++++++------------ .../epic-row/epic-row.directive.coffee | 14 +-- .../epics/dashboard/epic-row/epic-row.jade | 29 +++--- .../epics-dashboard.controller.coffee | 52 +++++----- .../epics/dashboard/epics-dashboard.jade | 18 ++-- .../epics-sortable.directive.coffee | 7 +- .../epics-table/epics-table.controller.coffee | 21 ++-- .../epics-table/epics-table.directive.coffee | 10 +- .../dashboard/epics-table/epics-table.jade | 3 - .../story-row/story-row.controller.coffee | 7 +- .../story-row/story-row.directive.coffee | 5 - .../epics/dashboard/story-row/story-row.jade | 6 +- app/modules/epics/epics.service.coffee | 99 +++++++++++++++++++ .../resources/epics-resource.service.coffee | 7 ++ .../utils/isolate-click.directive.coffee | 27 +++++ app/modules/utils/utils.module.coffee | 20 ++++ 21 files changed, 286 insertions(+), 195 deletions(-) create mode 100644 app/modules/epics/epics.service.coffee create mode 100644 app/modules/utils/isolate-click.directive.coffee create mode 100644 app/modules/utils/utils.module.coffee diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index fb81877e..513ee377 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -812,6 +812,7 @@ modules = [ "taigaHistory", "taigaWikiHistory", "taigaEpics", + "taigaUtils" # template cache "templates", diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 38a18a64..097fcf39 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -405,6 +405,7 @@ }, "EPICS": { "TITLE": "EPICS", + "SECTION_NAME": "Epics", "EPIC": "EPIC", "DASHBOARD": { "ADD": "+ ADD EPIC", diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index 570ab10c..119ec352 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -24,13 +24,19 @@ getRandomDefaultColor = taiga.getRandomDefaultColor class CreateEpicController @.$inject = [ - "tgResources" "$tgConfirm" - "tgAttachmentsService" - "$q" + "tgProjectService", + "tgEpicsService" ] - constructor: (@rs, @confirm, @attachmentsService, @q) -> + constructor: (@confirm, @projectService, @epicsService) -> + # NOTE: To use Checksley setFormErrors() and validateForm() + # are defined in the directive. + + # NOTE: We use project as no inmutable object to make + # the code compatible with the old code + @.project = @projectService.project.toJS() + @.newEpic = { color: getRandomDefaultColor() project: @.project.id @@ -39,25 +45,21 @@ class CreateEpicController } @.attachments = Immutable.List() + @.loading = false + createEpic: () -> return if not @.validateForm() @.loading = true - promise = @rs.epics.post(@.newEpic) - promise.then (response) => - @._createAttachments(response.data) - promise.then (response) => - @.onCreateEpic() - promise.then null, (response) => - @.setFormErrors(response.data) - - if response.data._error_message - confirm.notify("error", response.data._error_message) - promise.finally () => - @.loading = false - - return promise + @epicsService.createEpic(@.epic, @.attachments) + .then (response) => # On success + @.onCreateEpic() + .then null, (response) => # On error + @.setFormErrors(response.data) + if response.data._error_message + @confirm.notify("error", response.data._error_message) + @.loading = false # Color selector selectColor: (color) -> @@ -77,9 +79,4 @@ class CreateEpicController addAttachment: (attachment) -> @.attachments.push(attachment) - _createAttachments: (epic) -> - promises = _.map @.attachments.toJS(), (attachment) => - return @attachmentsService.upload(attachment.file, epic.id, epic.project, 'epic') - return @q.all(promises) - angular.module("taigaEpics").controller("CreateEpicCtrl", CreateEpicController) diff --git a/app/modules/epics/create-epic/create-epic.directive.coffee b/app/modules/epics/create-epic/create-epic.directive.coffee index abb527a7..fda1525d 100644 --- a/app/modules/epics/create-epic/create-epic.directive.coffee +++ b/app/modules/epics/create-epic/create-epic.directive.coffee @@ -17,8 +17,6 @@ # File: create-epic.directive.coffee ### -module = angular.module('taigaEpics') - CreateEpicDirective = () -> link = (scope, el, attrs, ctrl) -> form = el.find("form").checksley() @@ -35,12 +33,9 @@ CreateEpicDirective = () -> controller: "CreateEpicCtrl", controllerAs: "vm", bindToController: { - project: '=', onCreateEpic: '&' }, scope: {} } -CreateEpicDirective.$inject = [] - -module.directive("tgCreateEpic", CreateEpicDirective) +angular.module('taigaEpics').directive("tgCreateEpic", CreateEpicDirective) diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index da51d342..504c7216 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -33,11 +33,11 @@ tg-lightbox-close ) fieldset.tags-block tg-tag-line-common( - project="vm.project" - tags="vm.newEpic.tags" - permissions="add_epic" - on-add-tag="vm.addTag(name, color)" - on-delete-tag="vm.deleteTag(tag)" + project="vm.project" + tags="vm.newEpic.tags" + permissions="add_epic" + on-add-tag="vm.addTag(name, color)" + on-delete-tag="vm.deleteTag(tag)" ) fieldset textarea.e2e-create-epic-description( diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index 33c30bf4..06d79ca4 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -17,19 +17,23 @@ # File: epics-table.controller.coffee ### -module = angular.module("taigaEpics") - class EpicRowController @.$inject = [ - "tgResources", - "$tgConfirm" + "$tgConfirm", + "tgProjectService", + "tgEpicsService" ] - constructor: (@rs, @confirm) -> + constructor: (@confirm, @projectService, @epicsService) -> @.displayUserStories = false @.displayAssignedTo = false + @.displayStatusList = false @.loadingStatus = false + # NOTE: We use project as no inmutable object to make + # the code compatible with the old code + @.project = @projectService.project.toJS() + _calculateProgressBar: () -> if @.epic.getIn(['status_extra_info', 'is_closed']) == true @.percentage = "100%" @@ -42,68 +46,32 @@ class EpicRowController else @.percentage = "#{@.closed * 100 / @.total}%" - updateEpicStatus: (status) -> - @.loadingStatus = true - @.displayStatusList = false - patch = { - 'status': status, - 'version': @.epic.get('version') - } + canEditEpics: () -> + return @projectService.hasPermission("modify_epic") - onSuccess = => - @.loadingStatus = false - @.onUpdateEpic() - - onError = (data) => - @confirm.notify('error') - - return @rs.epics.patch(@.epic.get('id'), patch).then(onSuccess, onError) - - requestUserStories: (epic) -> + toggleUserStoryList: () -> if !@.displayUserStories - - onSuccess = (data) => - @.epicStories = data - @.displayUserStories = true - - onError = (data) => - @confirm.notify('error') - - return @rs.userstories.listInEpic(@.epic.get('id')).then(onSuccess, onError) + @epicsService.listRelatedUserStories(@.epic) + .then (userStories) => + @.epicStories = userStories + @.displayUserStories = true + .catch => + @confirm.notify('error') else @.displayUserStories = false - onRemoveAssigned: () -> - id = @.epic.get('id') - version = @.epic.get('version') - patch = { - 'assigned_to': null, - 'version': version - } + updateStatus: (statusId) -> + @.displayStatusList = false + @.loadingStatus = true + return @epicsService.updateEpicStatus(@.epic, statusId) + .catch () => + @confirm.notify('error') + .finally () => + @.loadingStatus = false - onSuccess = => - @.onUpdateEpic() + updateAssignedTo: (member) -> + return @epicsService.updateEpicAssignedTo(@.epic, member?.id) + .catch () => + @confirm.notify('error') - onError = (data) => - @confirm.notify('error') - - return @rs.epics.patch(id, patch).then(onSuccess, onError) - - onAssignTo: (member) -> - id = @.epic.get('id') - version = @.epic.get('version') - patch = { - 'assigned_to': member.id, - 'version': version - } - - onSuccess = => - @.onUpdateEpic() - @confirm.notify('success') - - onError = (data) => - @confirm.notify('error') - - return @rs.epics.patch(id, patch).then(onSuccess, onError) - -module.controller("EpicRowCtrl", EpicRowController) +angular.module("taigaEpics").controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee index 70fbb8e3..b1a6c85d 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -17,28 +17,16 @@ # File: epics-table.directive.coffee ### -module = angular.module('taigaEpics') - EpicRowDirective = () -> - - link = (scope, el, attrs, ctrl) -> - ctrl._calculateProgressBar() - return { - link: link, templateUrl:"epics/dashboard/epic-row/epic-row.html", controller: "EpicRowCtrl", controllerAs: "vm", bindToController: true, scope: { - project: '=', epic: '=', column: '=', - permissions: '=', - onUpdateEpic: "&" } } -EpicRowDirective.$inject = [] - -module.directive("tgEpicRow", EpicRowDirective) +angular.module('taigaEpics').directive("tgEpicRow", EpicRowDirective) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 2dc410b5..829ecf7a 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,10 +1,11 @@ .epic-row.e2e-epic-row( ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed'), 'unfold': vm.displayUserStories}" - ng-click="vm.requestUserStories(vm.epic)" + ng-click="vm.toggleUserStoryList()" ) tg-svg.icon-drag( svg-icon="icon-drag" ) + .vote( ng-if="vm.column.votes" ng-class="{'is-voter': vm.epic.get('is_voter')}" @@ -28,23 +29,26 @@ ) .project(ng-if="vm.column.project") - .sprint( - ng-if="vm.column.sprint" - ) - .assigned.e2e-assigned-to + + .sprint(ng-if="vm.column.sprint") + + .assigned.e2e-assigned-tio(ng-if="vm.column.assigned") tg-assigned-to-component( assigned-to="vm.epic.get('assigned_to_extra_info')" project="vm.project" - on-remove-assigned="vm.onRemoveAssigned()" - on-assign-to="vm.onAssignTo(member)" + on-remove-assigned="vm.updateAssignedTo(null)" + on-assign-to="vm.updateAssignedTo(member)" + tg-isolate-click ) + .status( - ng-if="vm.column.status && !vm.permissions.canEdit" + ng-if="vm.column.status && !vm.canEditEpics()" ) span {{vm.epic.getIn(['status_extra_info', 'name'])}} .status( - ng-if="vm.column.status && vm.permissions.canEdit" + ng-if="vm.column.status && vm.canEditEpics()" ng-mouseleave="vm.displayStatusList = false" + tg-isolate-click ) button( ng-click="vm.displayStatusList = true" @@ -59,20 +63,19 @@ ul.epic-statuses(ng-if="vm.displayStatusList") li.e2e-edit-epic-status( ng-repeat="status in vm.project.epic_statuses | orderBy:'order'" - ng-click="vm.updateEpicStatus(status.id)" + ng-click="vm.updateStatus(status.id)" ) {{status.name}} + .progress(ng-if="vm.column.progress") .progress-bar .progress-status( ng-if="::vm.percentage" ng-style="{'width':vm.percentage}" ) -.epic-stories-wrapper(ng-if="vm.displayUserStories && vm.epicStories") +.epic-stories-wrapper(ng-if="vm.displayUserStories && vm.epicStories") .epic-story(tg-repeat="story in vm.epicStories track by story.get('id')") tg-story-row.e2e-story( - epic="vm.epic" story="story" - project="vm.project" column="vm.column" ) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index d6f23146..199927a6 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -17,48 +17,50 @@ # File: epics.dashboard.controller.coffee ### -module = angular.module("taigaEpics") +taiga = @.taiga + class EpicsDashboardController @.$inject = [ - "$tgResources", - "tgResources", "$routeParams", "tgErrorHandlingService", "tgLightboxFactory", "lightboxService", - "$tgConfirm" + "$tgConfirm", + "tgProjectService", + "tgEpicsService" ] - constructor: (@rs, @resources, @params, @errorHandlingService, @lightboxFactory, @lightboxService, @confirm) -> - @.sectionName = "Epics" - @.createEpic = false + constructor: (@params, @errorHandlingService, @lightboxFactory, @lightboxService, + @confirm, @projectService, @epicsService) -> - loadProject: () -> - return @rs.projects.getBySlug(@params.pslug).then (project) => - if not project.is_epics_activated - @errorHandlingService.permissionDenied() - @.project = project - @.loadEpics() + @.sectionName = "EPICS.SECTION_NAME" - loadEpics: () -> - projectId = @.project.id - return @resources.epics.list(projectId).then (epics) => - @.epics = epics + taiga.defineImmutableProperty @, 'project', () => return @projectService.project + taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics - _onCreateEpic: () -> - @lightboxService.closeAll() - @confirm.notify("success") - @.loadEpics() + @._loadInitialData() + + _loadInitialData: () -> + @epicsService.clear() + @projectService.setProjectBySlug(@params.pslug) + .then () => + if not @.project.get("is_epics_activated") or not @projectService.hasPermission("view_epics") + @errorHandlingService.permissionDenied() + + @epicsService.fetchEpics() + + canCreateEpics: () -> + return @projectService.hasPermission("add_epic") onCreateEpic: () -> @lightboxFactory.create('tg-create-epic', { "class": "lightbox lightbox-create-epic open" - "project": "project" "on-create-epic": "onCreateEpic()" }, { - "project": @.project - "onCreateEpic": @._onCreateEpic.bind(this) + "onCreateEpic": () => + @lightboxService.closeAll() + @confirm.notify("success") }) -module.controller("EpicsDashboardCtrl", EpicsDashboardController) +angular.module("taigaEpics").controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 69ff744d..f9747455 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -4,23 +4,20 @@ header.header-with-actions h1( tg-main-title - project-name="vm.project.name" - i18n-section-name="{{ vm.sectionName }}" + project-name="vm.project.get('name')" + i18n-section-name="{{vm.sectionName}}" ) - .action-buttons(ng-if="vm.epics.size") + .action-buttons(ng-if="vm.epics.size && vm.canCreateEpics()") button.button-green.e2e-create-epic( translate="EPICS.DASHBOARD.ADD" title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}", ng-click="vm.onCreateEpic()" ) - + tg-epics-table( - ng-if="vm.project && vm.epics.size" - project="vm.project" - epics="vm.epics" - on-update-epic="vm.loadEpics()" + ng-if="vm.epics.size" ) - + section.empty-epics(ng-if="!vm.epics.size") img( src="/#{v}/images/epics-empty.png" @@ -30,11 +27,12 @@ p(translate="EPICS.EMPTY.EXPLANATION") a( translate="EPICS.EMPTY.HELP" - href="https://tree.taiga.io/support/frequently-asked-questions/who-is-taiga-for/" + href="#TODO: Link to Epics section in taiga-support" target="_blank" ng-title="EPICS.EMPTY.HELP | translate" ) button.create-epic.button-green( + ng-if="vm.canCreateEpics()" translate="EPICS.DASHBOARD.ADD" title="{{ EPICS.DASHBOARD.ADD_TITLE | translate }}" ng-click="vm.onCreateEpic()" diff --git a/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee b/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee index d9063f7a..53063281 100644 --- a/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee +++ b/app/modules/epics/dashboard/epics-sortable/epics-sortable.directive.coffee @@ -17,8 +17,10 @@ # File: epics-sortable.directive.coffee ### -EpicsSortableDirective = ($parse) -> +EpicsSortableDirective = ($parse, projectService) -> link = (scope, el, attrs) -> + return if not projectService.hasPermission("modify_epic") + callback = $parse(attrs.tgEpicsSortable) drake = dragula([el[0]], { @@ -55,7 +57,8 @@ EpicsSortableDirective = ($parse) -> } EpicsSortableDirective.$inject = [ - "$parse" + "$parse", + "tgProjectService" ] angular.module("taigaComponents").directive("tgEpicsSortable", EpicsSortableDirective) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee index 37eb275b..45e41d45 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -17,12 +17,16 @@ # File: epics-table.controller.coffee ### -module = angular.module("taigaEpics") +taiga = @.taiga + class EpicsTableController - @.$inject = [] + @.$inject = [ + "$tgConfirm", + "tgEpicsService" + ] - constructor: () -> + constructor: (@confirm, @epicsService) -> @.displayOptions = false @.displayVotes = true @.column = { @@ -35,15 +39,14 @@ class EpicsTableController progress: true } - @.permissions = { - canEdit: _.includes(@.project.my_permissions, 'modify_epic') - } + taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics toggleEpicTableOptions: () -> @.displayOptions = !@.displayOptions reorderEpic: (epic, newIndex) -> - console.log epic, newIndex + @epicsService.reorderEpic(epic, newIndex) + .then null, () => # on error + @confirm.notify("error") - -module.controller("EpicsTableCtrl", EpicsTableController) +angular.module("taigaEpics").controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee index ceb094a6..f072a3e4 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.directive.coffee @@ -17,21 +17,13 @@ # File: epics-table.directive.coffee ### -module = angular.module('taigaEpics') - EpicsTableDirective = () -> return { templateUrl:"epics/dashboard/epics-table/epics-table.html", controller: "EpicsTableCtrl", controllerAs: "vm", - bindToController: { - epics: "=", - project: "=", - onUpdateEpic: "&" - } scope: {} } -EpicsTableDirective.$inject = [] -module.directive("tgEpicsTable", EpicsTableDirective) +angular.module('taigaEpics').directive("tgEpicsTable", EpicsTableDirective) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index baaad4e6..31d29bb0 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -95,8 +95,5 @@ mixin epicSwitch(name, model) ) tg-epic-row.e2e-epic( epic="epic" - project="vm.project" column="vm.column" - on-update-epic="vm.onUpdateEpic()" - permissions="vm.permissions" ) diff --git a/app/modules/epics/dashboard/story-row/story-row.controller.coffee b/app/modules/epics/dashboard/story-row/story-row.controller.coffee index e93eca79..db82f81b 100644 --- a/app/modules/epics/dashboard/story-row/story-row.controller.coffee +++ b/app/modules/epics/dashboard/story-row/story-row.controller.coffee @@ -29,13 +29,8 @@ class StoryRowController if @.story.get('is_closed') == true @.percentage = "100%" else - tasks = @.story.get('tasks').toJS() totalTasks = @.story.get('tasks').size - areTasksCompleted = _.map(tasks, 'is_closed') - totalTasksCompleted = _.pull(areTasksCompleted, false).length + totalTasksCompleted = @.story.get('tasks').filter((it) -> it.get("is_closed")).size @.percentage = "#{totalTasksCompleted * 100 / totalTasks}%" - onSelectAssignedTo: () -> - console.log 'ng-click="vm.onSelectAssignedTo()"' - module.controller("StoryRowCtrl", StoryRowController) diff --git a/app/modules/epics/dashboard/story-row/story-row.directive.coffee b/app/modules/epics/dashboard/story-row/story-row.directive.coffee index 338f676f..13195c0a 100644 --- a/app/modules/epics/dashboard/story-row/story-row.directive.coffee +++ b/app/modules/epics/dashboard/story-row/story-row.directive.coffee @@ -20,20 +20,15 @@ module = angular.module('taigaEpics') StoryRowDirective = () -> - return { templateUrl:"epics/dashboard/story-row/story-row.html", controller: "StoryRowCtrl", controllerAs: "vm", bindToController: true, scope: { - epic: '=', story: '=', - project: '=', column: '=' } } -StoryRowDirective.$inject = [] - module.directive("tgStoryRow", StoryRowDirective) diff --git a/app/modules/epics/dashboard/story-row/story-row.jade b/app/modules/epics/dashboard/story-row/story-row.jade index d421ca0c..ae5bee27 100644 --- a/app/modules/epics/dashboard/story-row/story-row.jade +++ b/app/modules/epics/dashboard/story-row/story-row.jade @@ -1,5 +1,5 @@ .story-row( - ng-class="{'is-blocked': vm.story.is_blocked, 'is-closed': vm.story.is_closed}" + ng-class="{'is-blocked': vm.story.get('is_blocked'), 'is-closed': vm.story.get('is_closed')}" ) .vote( ng-if="vm.column.votes" @@ -11,12 +11,12 @@ .name(ng-if="vm.column.name") - var hash = "#"; a( - tg-nav="project-userstories-detail:project=vm.project.slug,ref=vm.story.get('ref')" + tg-nav="project-userstories-detail:project=vm.story.getIn(['project_extra_info', 'slug']),ref=vm.story.get('ref')" ng-attr-title="{{::vm.story.get('subject')}}" ) #{hash}{{::vm.story.get('ref')}} {{::vm.story.get('subject')}} tg-belong-to-epics( - format="pill" ng-if="vm.story.get('epics')" + format="pill" epics="vm.story.get('epics')" ) .project( diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee new file mode 100644 index 00000000..0f6da542 --- /dev/null +++ b/app/modules/epics/epics.service.coffee @@ -0,0 +1,99 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: epics.service.coffee +### + +taiga = @.taiga + +class EpicsService + @.$inject = [ + "tgProjectService", + "tgAttachmentsService" + "tgResources", + "tgXhrErrorService", + "$q" + ] + + constructor: (@projectService, @attachmentsService, @resources, @xhrError, @q) -> + @._epics = Immutable.List() + taiga.defineImmutableProperty @, "epics", () => return @._epics + + clear: () -> + @._epics = Immutable.List() + + fetchEpics: () -> + return @resources.epics.list(@projectService.project.get("id")) + .then (epics) => + @._epics = epics + .catch (xhr) => + @xhrError.response(xhr) + + listRelatedUserStories: (epic) -> + return @resources.userstories.listInEpic(epic.get('id')) + + createEpic: (epicData, attachments) -> + @.epicData.project = @projectsService.project.id + + return @resources.epics.post(@.epicData) + .then (epic) => + promises = _.map attachments.toJS(), (attachment) => + @attachmentsService.upload(attachment.file, epic.get("id"), epic.get("project"), 'epic') + @q.all(promises).then () => + @.fetchEpics() + + reorderEpic: (epic, newIndex) -> + withoutMoved = @.epics.filter (it) => it.get("id") != epic.get("id") + beforeDestination = withoutMoved.slice(0, newIndex) + + previous = beforeDestination.last() + newOrder = if !previous then 0 else epic.get("epics_order") + 1 + + previousWithTheSameOrder = beforeDestination.filter (it) => + it.get("epics_order") == previous.get("epics_order") + setOrders = Immutable.OrderedMap previousWithTheSameOrder.map (it) => + [it.get('id'), it.get("epics_order")] + + data = { + order: newOrder, + version: epic.get("version") + } + + return @resources.epics.reorder(epic.get("id"), data, setOrders) + .then () => + @.fetchEpics() + + updateEpicStatus: (epic, statusId) -> + data = { + status: statusId, + version: epic.get("version") + } + + return @resources.epics.patch(epic.get("id"), data) + .then () => + @.fetchEpics() + + updateEpicAssignedTo: (epic, userId) -> + data = { + assigned_to: userId, + version: epic.get("version") + } + + return @resources.epics.patch(epic.get("id"), data) + .then () => + @.fetchEpics() + +angular.module("taigaEpics").service("tgEpicsService", EpicsService) diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index 82d48c11..8b39793e 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -51,6 +51,13 @@ Resource = (urlsService, http) -> return http.post(url, params) + service.reorder = (id, data, setOrders) -> + url = urlsService.resolve("epics") + "/#{id}" + + options = {"headers": {"set-orders": JSON.stringify(setOrders)}} + + return http.patch(url, data, null, options) + service.addRelatedUserstory = (epicId, userstoryId) -> url = urlsService.resolve("epic-related-userstories", epicId) diff --git a/app/modules/utils/isolate-click.directive.coffee b/app/modules/utils/isolate-click.directive.coffee new file mode 100644 index 00000000..df262794 --- /dev/null +++ b/app/modules/utils/isolate-click.directive.coffee @@ -0,0 +1,27 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: isolate-click.directive.coffee +### + +IsolateClickDirective = () -> + link = (scope, el, attrs) -> + el.on 'click', (e) => + e.stopPropagation() + + return {link: link} + +angular.module("taigaUtils").directive("tgIsolateClick", IsolateClickDirective) diff --git a/app/modules/utils/utils.module.coffee b/app/modules/utils/utils.module.coffee new file mode 100644 index 00000000..d39c4e08 --- /dev/null +++ b/app/modules/utils/utils.module.coffee @@ -0,0 +1,20 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: utils.module.coffee +### + +module = angular.module("taigaUtils", []) From 14e53ec76b752e2095f3f9b2f2f70e2a6e62fec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 8 Sep 2016 09:19:21 +0200 Subject: [PATCH 213/315] Fix create-epic lightbox --- .../create-epic/create-epic.controller.coffee | 7 ++-- .../epics-dashboard.controller.coffee | 9 ++-- app/modules/epics/epics.service.coffee | 42 +++++++++---------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index 119ec352..ba4ee2f0 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -52,14 +52,15 @@ class CreateEpicController @.loading = true - @epicsService.createEpic(@.epic, @.attachments) + @epicsService.createEpic(@.newEpic, @.attachments) .then (response) => # On success @.onCreateEpic() - .then null, (response) => # On error + @.loading = false + .catch (response) => # On error + @.loading = false @.setFormErrors(response.data) if response.data._error_message @confirm.notify("error", response.data._error_message) - @.loading = false # Color selector selectColor: (color) -> diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 199927a6..b7cc1b7b 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -54,13 +54,16 @@ class EpicsDashboardController return @projectService.hasPermission("add_epic") onCreateEpic: () -> + onCreateEpic = () => + @lightboxService.closeAll() + @confirm.notify("success") + return # To prevent error https://docs.angularjs.org/error/$parse/isecdom?p0=onCreateEpic() + @lightboxFactory.create('tg-create-epic', { "class": "lightbox lightbox-create-epic open" "on-create-epic": "onCreateEpic()" }, { - "onCreateEpic": () => - @lightboxService.closeAll() - @confirm.notify("success") + "onCreateEpic": onCreateEpic.bind(this) }) angular.module("taigaEpics").controller("EpicsDashboardCtrl", EpicsDashboardController) diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee index 0f6da542..01b3de08 100644 --- a/app/modules/epics/epics.service.coffee +++ b/app/modules/epics/epics.service.coffee @@ -21,22 +21,22 @@ taiga = @.taiga class EpicsService @.$inject = [ - "tgProjectService", - "tgAttachmentsService" - "tgResources", - "tgXhrErrorService", - "$q" + 'tgProjectService', + 'tgAttachmentsService' + 'tgResources', + 'tgXhrErrorService', + '$q' ] constructor: (@projectService, @attachmentsService, @resources, @xhrError, @q) -> @._epics = Immutable.List() - taiga.defineImmutableProperty @, "epics", () => return @._epics + taiga.defineImmutableProperty @, 'epics', () => return @._epics clear: () -> @._epics = Immutable.List() fetchEpics: () -> - return @resources.epics.list(@projectService.project.get("id")) + return @resources.epics.list(@projectService.project.get('id')) .then (epics) => @._epics = epics .catch (xhr) => @@ -46,54 +46,54 @@ class EpicsService return @resources.userstories.listInEpic(epic.get('id')) createEpic: (epicData, attachments) -> - @.epicData.project = @projectsService.project.id + epicData.project = @projectService.project.get('id') - return @resources.epics.post(@.epicData) + return @resources.epics.post(epicData) .then (epic) => promises = _.map attachments.toJS(), (attachment) => - @attachmentsService.upload(attachment.file, epic.get("id"), epic.get("project"), 'epic') + @attachmentsService.upload(attachment.file, epic.get('id'), epic.get('project'), 'epic') @q.all(promises).then () => @.fetchEpics() reorderEpic: (epic, newIndex) -> - withoutMoved = @.epics.filter (it) => it.get("id") != epic.get("id") + withoutMoved = @.epics.filter (it) => it.get('id') != epic.get('id') beforeDestination = withoutMoved.slice(0, newIndex) previous = beforeDestination.last() - newOrder = if !previous then 0 else epic.get("epics_order") + 1 + newOrder = if !previous then 0 else epic.get('epics_order') + 1 previousWithTheSameOrder = beforeDestination.filter (it) => - it.get("epics_order") == previous.get("epics_order") + it.get('epics_order') == previous.get('epics_order') setOrders = Immutable.OrderedMap previousWithTheSameOrder.map (it) => - [it.get('id'), it.get("epics_order")] + [it.get('id'), it.get('epics_order')] data = { order: newOrder, - version: epic.get("version") + version: epic.get('version') } - return @resources.epics.reorder(epic.get("id"), data, setOrders) + return @resources.epics.reorder(epic.get('id'), data, setOrders) .then () => @.fetchEpics() updateEpicStatus: (epic, statusId) -> data = { status: statusId, - version: epic.get("version") + version: epic.get('version') } - return @resources.epics.patch(epic.get("id"), data) + return @resources.epics.patch(epic.get('id'), data) .then () => @.fetchEpics() updateEpicAssignedTo: (epic, userId) -> data = { assigned_to: userId, - version: epic.get("version") + version: epic.get('version') } - return @resources.epics.patch(epic.get("id"), data) + return @resources.epics.patch(epic.get('id'), data) .then () => @.fetchEpics() -angular.module("taigaEpics").service("tgEpicsService", EpicsService) +angular.module('taigaEpics').service('tgEpicsService', EpicsService) From 8b5830a33f6a08eead47a76bfed6687773fe1e88 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 8 Sep 2016 09:31:10 +0200 Subject: [PATCH 214/315] Fixing initial color in color selector --- .../color-selector/color-selector.controller.coffee | 9 ++++----- .../color-selector/color-selector.directive.coffee | 5 +++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee index fc1214d0..c30d9729 100644 --- a/app/modules/components/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -24,13 +24,12 @@ getDefaulColorList = taiga.getDefaulColorList class ColorSelectorController constructor: () -> @.colorList = getDefaulColorList() - - if @.initColor - @.color = @.initColor - @.displayColorList = false - resetColor: () -> + setColor: (color) -> + @.color = @.initColor + + resetColor: () -> if @.isRequired and not @.color @.color = @.initColor diff --git a/app/modules/components/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee index 817d2e9b..fb091711 100644 --- a/app/modules/components/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -17,6 +17,8 @@ # File: color-selector.directive.coffee ### +bindOnce = @.taiga.bindOnce + ColorSelectorDirective = ($timeout) -> link = (scope, el, attrs, ctrl) -> # Animation @@ -42,6 +44,9 @@ ColorSelectorDirective = ($timeout) -> .mouseenter(cancel) .mouseleave(close) + bindOnce scope, 'vm.initColor', (color) -> + ctrl.setColor(color) + return { link: link, templateUrl:"components/color-selector/color-selector.html", From ac751f801997431d58d318bfcc3f9bb06b9bcb8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 8 Sep 2016 09:50:38 +0200 Subject: [PATCH 215/315] Change epics resource to return Immutable objects in post and patch calls --- app/modules/resources/epics-resource.service.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index 8b39793e..a63029c9 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -45,11 +45,13 @@ Resource = (urlsService, http) -> url = urlsService.resolve("epics") + "/#{id}" return http.patch(url, patch) + .then (result) -> Immutable.fromJS(result.data) service.post = (params) -> url = urlsService.resolve("epics") return http.post(url, params) + .then (result) -> Immutable.fromJS(result.data) service.reorder = (id, data, setOrders) -> url = urlsService.resolve("epics") + "/#{id}" From 9f6df07a405e4212150268d3b0930d9b502fe1c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 8 Sep 2016 09:59:19 +0200 Subject: [PATCH 216/315] Fix epic progress bar in epics dashboard --- app/modules/epics/dashboard/epic-row/epic-row.controller.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index 06d79ca4..52902e63 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -34,6 +34,8 @@ class EpicRowController # the code compatible with the old code @.project = @projectService.project.toJS() + @._calculateProgressBar() + _calculateProgressBar: () -> if @.epic.getIn(['status_extra_info', 'is_closed']) == true @.percentage = "100%" From 7848cf826e188be7cacb082be1a0fd1e2eaa3f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 8 Sep 2016 11:40:11 +0200 Subject: [PATCH 217/315] Fix epics sort --- app/modules/epics/epics.service.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee index 01b3de08..09eb9a96 100644 --- a/app/modules/epics/epics.service.coffee +++ b/app/modules/epics/epics.service.coffee @@ -58,14 +58,15 @@ class EpicsService reorderEpic: (epic, newIndex) -> withoutMoved = @.epics.filter (it) => it.get('id') != epic.get('id') beforeDestination = withoutMoved.slice(0, newIndex) - previous = beforeDestination.last() - newOrder = if !previous then 0 else epic.get('epics_order') + 1 + + newOrder = if !previous then 0 else previous.get('epics_order') + 1 previousWithTheSameOrder = beforeDestination.filter (it) => it.get('epics_order') == previous.get('epics_order') - setOrders = Immutable.OrderedMap previousWithTheSameOrder.map (it) => + setOrders = _.fromPairs previousWithTheSameOrder.map((it) => [it.get('id'), it.get('epics_order')] + ).toJS() data = { order: newOrder, From 1d22477410b838a7349f716dcbf4f2792aa74e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 8 Sep 2016 19:10:39 +0200 Subject: [PATCH 218/315] Fix epics module unit tests --- .../create-epic/create-epic.controller.coffee | 1 - .../create-epic.controller.spec.coffee | 58 ++-- .../epic-row/epic-row.controller.coffee | 10 +- .../epic-row/epic-row.controller.spec.coffee | 287 ++++++++---------- .../epics-dashboard.controller.coffee | 14 +- .../epics-dashboard.controller.spec.coffee | 145 +++++---- .../epics/dashboard/epics-dashboard.jade | 2 +- .../epics-table.controller.spec.coffee | 46 +-- ...related-userstories.controller.spec.coffee | 17 +- app/modules/services/project.service.coffee | 28 +- 10 files changed, 289 insertions(+), 319 deletions(-) diff --git a/app/modules/epics/create-epic/create-epic.controller.coffee b/app/modules/epics/create-epic/create-epic.controller.coffee index ba4ee2f0..8ab9ef15 100644 --- a/app/modules/epics/create-epic/create-epic.controller.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.coffee @@ -39,7 +39,6 @@ class CreateEpicController @.newEpic = { color: getRandomDefaultColor() - project: @.project.id status: @.project.default_epic_status tags: [] } diff --git a/app/modules/epics/create-epic/create-epic.controller.spec.coffee b/app/modules/epics/create-epic/create-epic.controller.spec.coffee index 7dac8ef5..cd588888 100644 --- a/app/modules/epics/create-epic/create-epic.controller.spec.coffee +++ b/app/modules/epics/create-epic/create-epic.controller.spec.coffee @@ -23,42 +23,32 @@ describe "EpicRow", -> controller = null mocks = {} - _mockTgResources = () -> - mocks.tgResources = { - epics: { - post: sinon.stub() - } - } - - provide.value "tgResources", mocks.tgResources - _mockTgConfirm = () -> mocks.tgConfirm = { notify: sinon.stub() } provide.value "$tgConfirm", mocks.tgConfirm - _mockTgAttachmentsService = () -> - mocks.tgAttachmentsService = { - upload: sinon.stub() + _mockTgProjectService = () -> + mocks.tgProjectService = { + project: { + toJS: sinon.stub() + } } - provide.value "tgAttachmentsService", mocks.tgAttachmentsService + provide.value "tgProjectService", mocks.tgProjectService - _mockQ = () -> - mocks.q = { - all: sinon.spy() + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + createEpic: sinon.stub() } - - provide.value "$q", mocks.q - + provide.value "tgEpicsService", mocks.tgEpicsService _mocks = () -> module ($provide) -> provide = $provide - _mockTgResources() _mockTgConfirm() - _mockTgAttachmentsService() - _mockQ() + _mockTgProjectService() + _mockTgEpicsService() return null beforeEach -> @@ -70,8 +60,11 @@ describe "EpicRow", -> controller = $controller it "create Epic with invalid form", () -> + mocks.tgProjectService.project.toJS.withArgs().returns( + {id: 1, default_epic_status: 1} + ) + data = { - project: {id: 1, default_epic_status: 1} validateForm: sinon.stub() setFormErrors: sinon.stub() onCreateEpic: sinon.stub() @@ -84,11 +77,14 @@ describe "EpicRow", -> createEpicCtrl.createEpic() expect(data.validateForm).have.been.called - expect(mocks.tgResources.epics.post).not.have.been.called + expect(mocks.tgEpicsService.createEpic).not.have.been.called it "create Epic successfully", (done) -> + mocks.tgProjectService.project.toJS.withArgs().returns( + {id: 1, default_epic_status: 1} + ) + data = { - project: {id: 1, default_epic_status: 1} validateForm: sinon.stub() setFormErrors: sinon.stub() onCreateEpic: sinon.stub() @@ -97,12 +93,16 @@ describe "EpicRow", -> createEpicCtrl.attachments = Immutable.List([{file: "file1"}, {file: "file2"}]) data.validateForm.withArgs().returns(true) - mocks.tgResources.epics.post.withArgs(createEpicCtrl.newEpic).promise().resolve( - {data: {id: 1, project: 1}} - ) + mocks.tgEpicsService.createEpic + .withArgs( + createEpicCtrl.newEpic, + createEpicCtrl.attachments) + .promise() + .resolve( + {data: {id: 1, project: 1}} + ) createEpicCtrl.createEpic().then () -> expect(data.validateForm).have.been.called - expect(mocks.tgAttachmentsService.upload).have.been.calledTwice expect(createEpicCtrl.onCreateEpic).have.been.called done() diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index 52902e63..65a82333 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -40,13 +40,13 @@ class EpicRowController if @.epic.getIn(['status_extra_info', 'is_closed']) == true @.percentage = "100%" else - @.opened = @.epic.getIn(['user_stories_counts', 'opened']) - @.closed = @.epic.getIn(['user_stories_counts', 'closed']) - @.total = @.opened + @.closed - if @.total == 0 + opened = @.epic.getIn(['user_stories_counts', 'opened']) + closed = @.epic.getIn(['user_stories_counts', 'closed']) + total = opened + closed + if total == 0 @.percentage = "0%" else - @.percentage = "#{@.closed * 100 / @.total}%" + @.percentage = "#{closed * 100 / total}%" canEditEpics: () -> return @projectService.hasPermission("modify_epic") diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee index 6205a6df..6e287471 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee @@ -23,31 +23,34 @@ describe "EpicRow", -> controller = null mocks = {} - _mockTgResources = () -> - mocks.tgResources = { - epics: { - patch: sinon.stub() - }, - userstories: { - listInEpic: sinon.stub() - } - } - - provide.value "tgResources", mocks.tgResources - _mockTgConfirm = () -> mocks.tgConfirm = { notify: sinon.stub() } - provide.value "$tgConfirm", mocks.tgConfirm + _mockTgProjectService = () -> + mocks.tgProjectService = { + project: { + toJS: sinon.stub() + } + } + provide.value "tgProjectService", mocks.tgProjectService + + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + listRelatedUserStories: sinon.stub() + updateEpicStatus: sinon.stub() + updateEpicAssignedTo: sinon.stub() + } + provide.value "tgEpicsService", mocks.tgEpicsService + _mocks = () -> module ($provide) -> provide = $provide - _mockTgResources() _mockTgConfirm() - + _mockTgProjectService() + _mockTgEpicsService() return null beforeEach -> @@ -58,183 +61,139 @@ describe "EpicRow", -> inject ($controller) -> controller = $controller - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.displayUserStories = false - EpicRowCtrl.displayAssignedTo = false - EpicRowCtrl.loadingStatus = false - it "calculate progress bar in open US", () -> - - EpicRowCtrl = controller "EpicRowCtrl" - - EpicRowCtrl.epic = Immutable.fromJS({ - status_extra_info: { - is_closed: false - } - user_stories_counts: { - opened: 10, - closed: 10 - } - }) - - EpicRowCtrl._calculateProgressBar() - expect(EpicRowCtrl.opened).to.be.equal(10) - expect(EpicRowCtrl.closed).to.be.equal(10) - expect(EpicRowCtrl.total).to.be.equal(20) - expect(EpicRowCtrl.percentage).to.be.equal("50%") - - it "calculate progress bar in zero US", () -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.epic = Immutable.fromJS({ - status_extra_info: { - is_closed: false - } - user_stories_counts: { - opened: 0, - closed: 0 - } - }) - EpicRowCtrl._calculateProgressBar() - expect(EpicRowCtrl.opened).to.be.equal(0) - expect(EpicRowCtrl.closed).to.be.equal(0) - expect(EpicRowCtrl.total).to.be.equal(0) - expect(EpicRowCtrl.percentage).to.be.equal("0%") - - it "calculate progress bar in zero US", () -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.epic = Immutable.fromJS({ - status_extra_info: { - is_closed: true - } - }) - EpicRowCtrl._calculateProgressBar() - expect(EpicRowCtrl.percentage).to.be.equal("100%") - - it "Update Epic Status Success", (done) -> - EpicRowCtrl = controller "EpicRowCtrl" - - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1, - version: 1 - }) - - EpicRowCtrl.patch = { - 'status': 'new', - 'version': EpicRowCtrl.epic.get('version') + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + status_extra_info: { + is_closed: false + } + user_stories_counts: { + opened: 10, + closed: 10 + } + }) } - EpicRowCtrl.loadingStatus = true - EpicRowCtrl.onUpdateEpic = sinon.stub() + ctrl._calculateProgressBar() + expect(ctrl.percentage).to.be.equal("50%") - promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().resolve() + it "calculate progress bar in zero US", () -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + status_extra_info: { + is_closed: false + } + user_stories_counts: { + opened: 0, + closed: 0 + } + }) + } + expect(ctrl.percentage).to.be.equal("0%") - status = "new" - EpicRowCtrl.updateEpicStatus(status).then () -> - expect(EpicRowCtrl.loadingStatus).to.be.false - expect(EpicRowCtrl.displayStatusList).to.be.false - expect(EpicRowCtrl.onUpdateEpic).to.be.called + it "calculate progress bar in zero US", () -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + status_extra_info: { + is_closed: true + } + }) + } + expect(ctrl.percentage).to.be.equal("100%") + + it "Update Epic Status Success", (done) -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + version: 1 + }) + } + + statusId = 1 + + promise = mocks.tgEpicsService.updateEpicStatus + .withArgs(ctrl.epic, statusId) + .promise() + .resolve() + + ctrl.loadingStatus = true + ctrl.displayStatusList = true + + ctrl.updateStatus(statusId).then () -> + expect(ctrl.loadingStatus).to.be.false + expect(ctrl.displayStatusList).to.be.false done() it "Update Epic Status Error", (done) -> - EpicRowCtrl = controller "EpicRowCtrl" - - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1, - version: 1 - }) - - EpicRowCtrl.patch = { - 'status': 'new', - 'version': EpicRowCtrl.epic.get('version') + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + version: 1 + }) } - EpicRowCtrl.loadingStatus = true - EpicRowCtrl.onUpdateEpic = sinon.stub() + statusId = 1 - promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().reject(new Error('error')) + promise = mocks.tgEpicsService.updateEpicStatus + .withArgs(ctrl.epic, statusId) + .promise() + .reject(new Error('error')) - status = "new" - EpicRowCtrl.updateEpicStatus(status).then () -> + ctrl.updateStatus(statusId).then () -> + expect(ctrl.loadingStatus).to.be.false + expect(ctrl.displayStatusList).to.be.false expect(mocks.tgConfirm.notify).have.been.calledWith('error') done() it "display User Stories", (done) -> - EpicRowCtrl = controller "EpicRowCtrl" + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + }) + } - EpicRowCtrl.displayUserStories = false - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1 - }) - data = true + ctrl.displayUserStories = false - promise = mocks.tgResources.userstories.listInEpic.withArgs(EpicRowCtrl.epic.get('id')).promise().resolve(data) + data = Immutable.List() - EpicRowCtrl.requestUserStories(EpicRowCtrl.epic).then () -> - expect(EpicRowCtrl.displayUserStories).to.be.true - expect(EpicRowCtrl.epicStories).is.equal(data) + promise = mocks.tgEpicsService.listRelatedUserStories + .withArgs(ctrl.epic) + .promise() + .resolve(data) + + ctrl.toggleUserStoryList().then () -> + expect(ctrl.displayUserStories).to.be.true + expect(ctrl.epicStories).is.equal(data) done() it "display User Stories error", (done) -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.displayUserStories = false + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + }) + } - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1 - }) + ctrl.displayUserStories = false - promise = mocks.tgResources.userstories.listInEpic.withArgs(EpicRowCtrl.epic.get('id')).promise().reject(new Error('error')) + promise = mocks.tgEpicsService.listRelatedUserStories + .withArgs(ctrl.epic) + .promise() + .reject(new Error('error')) - EpicRowCtrl.requestUserStories(EpicRowCtrl.epic).then () -> + ctrl.toggleUserStoryList().then () -> + expect(ctrl.displayUserStories).to.be.false expect(mocks.tgConfirm.notify).have.been.calledWith('error') done() - it "DO NOT display User Stories", () -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.displayUserStories = true - - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1 - }) - EpicRowCtrl.requestUserStories(EpicRowCtrl.epic) - expect(EpicRowCtrl.displayUserStories).to.be.false - - it "On remove assigned", () -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1, - version: 1 - }) - EpicRowCtrl.patch = { - 'assigned_to': null, - 'version': EpicRowCtrl.epic.get('version') - } - EpicRowCtrl.onUpdateEpic = sinon.stub() - - promise = mocks.tgResources.epics.patch.withArgs(EpicRowCtrl.epic.get('id'), EpicRowCtrl.patch).promise().resolve() - - EpicRowCtrl.onRemoveAssigned().then () -> - expect(EpicRowCtrl.onUpdateEpic).to.have.been.called - - it "On assign to", (done) -> - EpicRowCtrl = controller "EpicRowCtrl" - EpicRowCtrl.epic = Immutable.fromJS({ - id: 1, - version: 1 - }) - id = EpicRowCtrl.epic.get('id') - version = EpicRowCtrl.epic.get('version') - member = { - id: 1 - } - EpicRowCtrl.patch = { - assigned_to: member.id - version: EpicRowCtrl.epic.get('version') + it "display User Stories error", -> + ctrl = controller "EpicRowCtrl", null, { + epic: Immutable.fromJS({ + id: 1 + }) } - EpicRowCtrl.onUpdateEpic = sinon.stub() + ctrl.displayUserStories = true - promise = mocks.tgResources.epics.patch.withArgs(id, EpicRowCtrl.patch).promise().resolve(member) - EpicRowCtrl.onAssignTo(member).then () -> - expect(EpicRowCtrl.onUpdateEpic).to.have.been.called - expect(mocks.tgConfirm.notify).have.been.calledWith('success') - done() + ctrl.toggleUserStoryList() + + expect(ctrl.displayUserStories).to.be.false diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index b7cc1b7b..043d8e92 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -39,16 +39,16 @@ class EpicsDashboardController taiga.defineImmutableProperty @, 'project', () => return @projectService.project taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics - @._loadInitialData() - - _loadInitialData: () -> + loadInitialData: () -> @epicsService.clear() - @projectService.setProjectBySlug(@params.pslug) + return @projectService.setProjectBySlug(@params.pslug) .then () => - if not @.project.get("is_epics_activated") or not @projectService.hasPermission("view_epics") - @errorHandlingService.permissionDenied() + if not @projectService.isEpicsDashboardEnabled() + return @errorHandlingService.notFound() + if not @projectService.hasPermission("view_epics") + return @errorHandlingService.permissionDenied() - @epicsService.fetchEpics() + return @epicsService.fetchEpics() canCreateEpics: () -> return @projectService.hasPermission("add_epic") diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee index 12b98e64..a18b7c4a 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee @@ -23,41 +23,38 @@ describe "EpicsDashboard", -> controller = null mocks = {} - _mockTgResources = () -> - mocks.tgResources = { - projects: { - getBySlug: sinon.stub() - } - } - - provide.value "$tgResources", mocks.tgResources - - _mockTgResourcesNew = () -> - mocks.tgResourcesNew = { - epics: { - list: sinon.stub() - } - } - - provide.value "tgResources", mocks.tgResourcesNew - _mockTgConfirm = () -> mocks.tgConfirm = { notify: sinon.stub() } - provide.value "$tgConfirm", mocks.tgConfirm + _mockTgProjectService = () -> + mocks.tgProjectService = { + setProjectBySlug: sinon.stub() + hasPermission: sinon.stub() + isEpicsDashboardEnabled: sinon.stub() + } + provide.value "tgProjectService", mocks.tgProjectService + + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + clear: sinon.stub() + fetchEpics: sinon.stub() + } + provide.value "tgEpicsService", mocks.tgEpicsService + _mockRouteParams = () -> - mocks.routeparams = { + mocks.routeParams = { pslug: sinon.stub() } - provide.value "$routeParams", mocks.routeparams + provide.value "$routeParams", mocks.routeParams _mockTgErrorHandlingService = () -> mocks.tgErrorHandlingService = { permissionDenied: sinon.stub() + notFound: sinon.stub() } provide.value "tgErrorHandlingService", mocks.tgErrorHandlingService @@ -76,23 +73,16 @@ describe "EpicsDashboard", -> provide.value "lightboxService", mocks.lightboxService - _mockTgConfirm = () -> - mocks.tgConfirm = { - notify: sinon.stub() - } - - provide.value "$tgConfirm", mocks.tgConfirm - _mocks = () -> module ($provide) -> provide = $provide - _mockTgResources() - _mockTgResourcesNew() + _mockTgConfirm() + _mockTgProjectService() + _mockTgEpicsService() _mockRouteParams() _mockTgErrorHandlingService() _mockTgLightboxFactory() _mockLightboxService() - _mockTgConfirm() return null @@ -104,39 +94,70 @@ describe "EpicsDashboard", -> inject ($controller) -> controller = $controller - EpicsDashboardCtrl = controller "EpicsDashboardCtrl" + it "load data because epics panel is enabled and user has permissions", (done) -> + ctrl = controller("EpicsDashboardCtrl") - it "load projects", (done) -> - EpicsDashboardCtrl = controller "EpicsDashboardCtrl" - params = mocks.routeparams.pslug - EpicsDashboardCtrl.loadEpics = sinon.stub() - project = { - is_epics_activated: false - } - promise = mocks.tgResources.projects.getBySlug.withArgs(params).promise().resolve(project) - EpicsDashboardCtrl.loadProject().then () -> + mocks.tgProjectService.setProjectBySlug + .promise() + .resolve("ok") + mocks.tgProjectService.hasPermission + .returns(true) + mocks.tgProjectService.isEpicsDashboardEnabled + .returns(true) + + ctrl.loadInitialData().then () -> + expect(mocks.tgErrorHandlingService.permissionDenied).not.have.been.called + expect(mocks.tgErrorHandlingService.notFound).not.have.been.called + expect(mocks.tgEpicsService.fetchEpics).have.been.called + done() + + it "not load data because epics panel is not enabled", (done) -> + ctrl = controller("EpicsDashboardCtrl") + + mocks.tgProjectService.setProjectBySlug + .promise() + .resolve("ok") + mocks.tgProjectService.hasPermission + .returns(true) + mocks.tgProjectService.isEpicsDashboardEnabled + .returns(false) + + ctrl.loadInitialData().then () -> + expect(mocks.tgErrorHandlingService.permissionDenied).not.have.been.called + expect(mocks.tgErrorHandlingService.notFound).have.been.called + expect(mocks.tgEpicsService.fetchEpics).not.have.been.called + done() + + it "not load data because user has not permissions", (done) -> + ctrl = controller("EpicsDashboardCtrl") + + mocks.tgProjectService.setProjectBySlug + .promise() + .resolve("ok") + mocks.tgProjectService.hasPermission + .returns(false) + mocks.tgProjectService.isEpicsDashboardEnabled + .returns(true) + + ctrl.loadInitialData().then () -> expect(mocks.tgErrorHandlingService.permissionDenied).have.been.called - expect(EpicsDashboardCtrl.project).is.equal(project) - expect(EpicsDashboardCtrl.loadEpics).have.been.called + expect(mocks.tgErrorHandlingService.notFound).not.have.been.called + expect(mocks.tgEpicsService.fetchEpics).not.have.been.called done() - it "load epics", (done) -> - EpicsDashboardCtrl = controller "EpicsDashboardCtrl" - EpicsDashboardCtrl.project = { - id: 1 - } - epics = { - id: 1 - } - promise = mocks.tgResourcesNew.epics.list.withArgs(EpicsDashboardCtrl.project.id).promise().resolve(epics) - EpicsDashboardCtrl.loadEpics().then () -> - expect(EpicsDashboardCtrl.epics).is.equal(epics) - done() + it "not load data because epics panel is not enabled and user has not permissions", (done) -> + ctrl = controller("EpicsDashboardCtrl") - it "on create epic", () -> - EpicsDashboardCtrl = controller "EpicsDashboardCtrl" - EpicsDashboardCtrl.loadEpics = sinon.stub() - EpicsDashboardCtrl._onCreateEpic() - expect(mocks.lightboxService.closeAll).have.been.called - expect(mocks.tgConfirm.notify).have.been.calledWith("success") - expect(EpicsDashboardCtrl.loadEpics).have.been.called + mocks.tgProjectService.setProjectBySlug + .promise() + .resolve("ok") + mocks.tgProjectService.hasPermission + .returns(false) + mocks.tgProjectService.isEpicsDashboardEnabled + .returns(false) + + ctrl.loadInitialData().then () -> + expect(mocks.tgErrorHandlingService.permissionDenied).not.have.been.called + expect(mocks.tgErrorHandlingService.notFound).have.been.called + expect(mocks.tgEpicsService.fetchEpics).not.have.been.called + done() diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index f9747455..1fa89a3e 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -1,4 +1,4 @@ -.wrapper(ng-init="vm.loadProject()") +.wrapper(ng-init="vm.loadInitialData()") tg-project-menu section.main(role="main") header.header-with-actions diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee index 98c5f3a0..cdd83c6c 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.spec.coffee @@ -23,10 +23,23 @@ describe "EpicTable", -> controller = null mocks = {} + _mockTgConfirm = () -> + mocks.tgConfirm = { + notify: sinon.stub() + } + provide.value "$tgConfirm", mocks.tgConfirm + + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + createEpic: sinon.stub() + } + provide.value "tgEpicsService", mocks.tgEpicsService + _mocks = () -> module ($provide) -> provide = $provide - + _mockTgConfirm() + _mockTgEpicsService() return null beforeEach -> @@ -38,36 +51,7 @@ describe "EpicTable", -> controller = $controller it "toggle table options", () -> - data = { - project: { - my_permissions: [ - 'modify_epic' - ] - } - } - epicTableCtrl = controller "EpicsTableCtrl", null, data + epicTableCtrl = controller "EpicsTableCtrl" epicTableCtrl.displayOptions = true epicTableCtrl.toggleEpicTableOptions() expect(epicTableCtrl.displayOptions).to.be.false - - it "can edit", () -> - data = { - project: { - my_permissions: [ - 'modify_epic' - ] - } - } - epicTableCtrl = controller "EpicsTableCtrl", null, data - expect(epicTableCtrl.permissions.canEdit).to.be.true - - it "can NOT edit", () -> - data = { - project: { - my_permissions: [ - 'modify_us' - ] - } - } - epicTableCtrl = controller "EpicsTableCtrl", null, data - expect(epicTableCtrl.permissions.canEdit).to.be.false diff --git a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee index 9162c935..3498e404 100644 --- a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee +++ b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee @@ -32,11 +32,17 @@ describe "RelatedUserStories", -> provide.value "tgResources", mocks.tgResources + _mockTgEpicsService = () -> + mocks.tgEpicsService = { + } + + provide.value "tgEpicsService", mocks.tgEpicsService + _mocks = () -> module ($provide) -> provide = $provide _mockTgResources() - + _mockTgEpicsService() return null beforeEach -> @@ -47,20 +53,19 @@ describe "RelatedUserStories", -> inject ($controller) -> controller = $controller - RelatedUserStoriesCtrl = controller "RelatedUserStoriesCtrl" - it "load related userstories", (done) -> + ctrl = controller "RelatedUserStoriesCtrl" userstories = Immutable.fromJS([ { id: 1 } ]) - RelatedUserStoriesCtrl.epic = Immutable.fromJS({ + ctrl.epic = Immutable.fromJS({ id: 66 }) promise = mocks.tgResources.userstories.listInEpic.withArgs(66).promise().resolve(userstories) - RelatedUserStoriesCtrl.loadRelatedUserstories().then () -> - expect(RelatedUserStoriesCtrl.userstories).is.equal(userstories) + ctrl.loadRelatedUserstories().then () -> + expect(ctrl.userstories).is.equal(userstories) done() diff --git a/app/modules/services/project.service.coffee b/app/modules/services/project.service.coffee index a6640ac5..649147b4 100644 --- a/app/modules/services/project.service.coffee +++ b/app/modules/services/project.service.coffee @@ -36,6 +36,12 @@ class ProjectService taiga.defineImmutableProperty @, "sectionsBreadcrumb", () => return @._sectionsBreadcrumb taiga.defineImmutableProperty @, "activeMembers", () => return @._activeMembers + cleanProject: () -> + @._project = null + @._activeMembers = Immutable.List() + @._section = null + @._sectionsBreadcrumb = Immutable.List() + setSection: (section) -> @._section = section @@ -44,6 +50,10 @@ class ProjectService else @._sectionsBreadcrumb = Immutable.List() + setProject: (project) -> + @._project = project + @._activeMembers = @._project.get('members').filter (member) -> member.get('is_active') + setProjectBySlug: (pslug) -> return new Promise (resolve, reject) => if !@.project || @.project.get('slug') != pslug @@ -57,23 +67,15 @@ class ProjectService else resolve() - setProject: (project) -> - @._project = project - @._activeMembers = @._project.get('members').filter (member) -> member.get('is_active') - - cleanProject: () -> - @._project = null - @._activeMembers = Immutable.List() - @._section = null - @._sectionsBreadcrumb = Immutable.List() - - hasPermission: (permission) -> - return @._project.get('my_permissions').indexOf(permission) != -1 - fetchProject: () -> pslug = @.project.get('slug') return @projectsService.getProjectBySlug(pslug).then (project) => @.setProject(project) + hasPermission: (permission) -> + return @._project.get('my_permissions').indexOf(permission) != -1 + + isEpicsDashboardEnabled: -> + return @._project.get("is_epics_activated") angular.module("taigaCommon").service("tgProjectService", ProjectService) From a275d88506e4cb712c9047eae3bcbcf30efb40f2 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 8 Sep 2016 15:16:03 +0200 Subject: [PATCH 219/315] Epic detail page --- app/modules/epics/epics.service.coffee | 22 ++++ .../related-userstories-controller.coffee | 8 +- ...ated-userstories-sortable.directive.coffee | 65 ++++++++++ ...related-userstories.controller.spec.coffee | 35 ++++++ .../related-userstories.jade | 23 ++-- .../related-userstories.scss | 108 ----------------- .../related-userstory-row.jade | 4 + .../related-userstory-row.scss | 112 ++++++++++++++++++ .../resources/epics-resource.service.coffee | 7 ++ 9 files changed, 264 insertions(+), 120 deletions(-) create mode 100644 app/modules/epics/related-userstories/related-userstories-sortable/related-userstories-sortable.directive.coffee create mode 100644 app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee index 09eb9a96..27e8f0b2 100644 --- a/app/modules/epics/epics.service.coffee +++ b/app/modules/epics/epics.service.coffee @@ -77,6 +77,28 @@ class EpicsService .then () => @.fetchEpics() + reorderRelatedUserstory: (epic, epicUserstories, userstory, newIndex) -> + withoutMoved = epicUserstories.filter (it) => it.get('id') != userstory.get('id') + beforeDestination = withoutMoved.slice(0, newIndex) + + previous = beforeDestination.last() + newOrder = if !previous then 0 else previous.get('epic_order') + 1 + + previousWithTheSameOrder = beforeDestination.filter (it) => + it.get('epic_order') == previous.get('epic_order') + + setOrders = Immutable.OrderedMap previousWithTheSameOrder.map (it) => + [it.get('id'), it.get('epic_order')] + + data = { + order: newOrder + } + epicId = epic.get('id') + userstoryId = userstory.get('id') + return @resources.epics.reorderRelatedUserstory(epicId, userstoryId, data, setOrders) + .then () => + return @.listRelatedUserStories(epic) + updateEpicStatus: (epic, statusId) -> data = { status: statusId, diff --git a/app/modules/epics/related-userstories/related-userstories-controller.coffee b/app/modules/epics/related-userstories/related-userstories-controller.coffee index 8042fa8f..4b9be953 100644 --- a/app/modules/epics/related-userstories/related-userstories-controller.coffee +++ b/app/modules/epics/related-userstories/related-userstories-controller.coffee @@ -20,9 +20,9 @@ module = angular.module("taigaEpics") class RelatedUserStoriesController - @.$inject = ["tgResources"] + @.$inject = ["tgResources", "tgEpicsService"] - constructor: (@rs) -> + constructor: (@rs, @epicsService) -> @.sectionName = "Epics" @.showCreateRelatedUserstoriesLightbox = false @@ -30,4 +30,8 @@ class RelatedUserStoriesController @rs.userstories.listInEpic(@.epic.get('id')).then (data) => @.userstories = data + reorderRelatedUserstory: (us, newIndex) -> + @epicsService.reorderRelatedUserstory(@.epic, @.userstories, us, newIndex).then (userstories) => + @.userstories = userstories + module.controller("RelatedUserStoriesCtrl", RelatedUserStoriesController) diff --git a/app/modules/epics/related-userstories/related-userstories-sortable/related-userstories-sortable.directive.coffee b/app/modules/epics/related-userstories/related-userstories-sortable/related-userstories-sortable.directive.coffee new file mode 100644 index 00000000..1989e7d5 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstories-sortable/related-userstories-sortable.directive.coffee @@ -0,0 +1,65 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: related-userstories-sortable.directive.coffee +### + +module = angular.module('taigaEpics') + +RelatedUserstoriesSortableDirective = ($parse, projectService) -> + link = (scope, el, attrs) -> + return if not projectService.hasPermission("modify_epic") + + callback = $parse(attrs.tgRelatedUserstoriesSortable) + + drake = dragula([el[0]], { + copySortSource: false + copy: false + mirrorContainer: el[0] + moves: (item) -> + return $(item).is('tg-related-userstory-row') + }) + + drake.on 'dragend', (item) -> + itemEl = $(item) + us = itemEl.scope().us + newIndex = itemEl.index() + + scope.$apply () -> + callback(scope, {us: us, newIndex: newIndex}) + + scroll = autoScroll(window, { + margin: 20, + pixels: 30, + scrollWhenOutside: true, + autoScroll: () -> + return this.down && drake.dragging + }) + + scope.$on "$destroy", -> + el.off() + drake.destroy() + + return { + link: link + } + +RelatedUserstoriesSortableDirective.$inject = [ + "$parse", + "tgProjectService" +] + +module.directive("tgRelatedUserstoriesSortable", RelatedUserstoriesSortableDirective) diff --git a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee index 3498e404..2543b085 100644 --- a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee +++ b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee @@ -34,6 +34,7 @@ describe "RelatedUserStories", -> _mockTgEpicsService = () -> mocks.tgEpicsService = { + reorderRelatedUserstory: sinon.stub() } provide.value "tgEpicsService", mocks.tgEpicsService @@ -69,3 +70,37 @@ describe "RelatedUserStories", -> ctrl.loadRelatedUserstories().then () -> expect(ctrl.userstories).is.equal(userstories) done() + + it "reorderRelatedUserstory", (done) -> + ctrl = controller "RelatedUserStoriesCtrl" + userstories = Immutable.fromJS([ + { + id: 1 + }, + { + id: 2 + } + ]) + + reorderedUserstories = Immutable.fromJS([ + { + id: 2 + }, + { + id: 1 + } + ]) + + ctrl.epic = Immutable.fromJS({ + id: 66 + }) + + + promise = mocks.tgEpicsService.reorderRelatedUserstory + .withArgs(ctrl.epic, ctrl.userstories, userstories.get(1), 0) + .promise() + .resolve(reorderedUserstories) + + ctrl.reorderRelatedUserstory(userstories.get(1), 0).then () -> + expect(ctrl.userstories).is.equal(reorderedUserstories) + done() diff --git a/app/modules/epics/related-userstories/related-userstories.jade b/app/modules/epics/related-userstories/related-userstories.jade index ecf642de..35a848b7 100644 --- a/app/modules/epics/related-userstories/related-userstories.jade +++ b/app/modules/epics/related-userstories/related-userstories.jade @@ -10,14 +10,17 @@ section.related-userstories load-related-userstories="vm.loadRelatedUserstories()" ) - .related-userstories-body - div(tg-repeat="us in vm.userstories track by us.get('id')") - tg-related-userstory-row.row( - ng-class="{closed: us.get('is_closed'), blocked: us.get('is_blocked')}" - userstory="us" - epic="vm.epic" - project="vm.project" - load-related-userstories="vm.loadRelatedUserstories()" - ) + .related-userstories-body( + tg-related-userstories-sortable="vm.reorderRelatedUserstory(us, newIndex)" + ) + tg-related-userstory-row.row( + tg-repeat="us in vm.userstories track by us.get('id')" + ng-class="{closed: us.get('is_closed'), blocked: us.get('is_blocked')}" + userstory="us" + epic="vm.epic" + project="vm.project" + load-related-userstories="vm.loadRelatedUserstories()" + tg-bind-scope + ) - div(tg-related-userstories-create-form) + div(tg-related-userstories-create-form) diff --git a/app/modules/epics/related-userstories/related-userstories.scss b/app/modules/epics/related-userstories/related-userstories.scss index 62bc0b46..67ba81a0 100644 --- a/app/modules/epics/related-userstories/related-userstories.scss +++ b/app/modules/epics/related-userstories/related-userstories.scss @@ -36,112 +36,4 @@ .related-userstories-body { width: 100%; - .row { - @include font-size(small); - align-items: center; - border-bottom: 1px solid $whitish; - display: flex; - padding: .5rem 0 .5rem .5rem; - &:hover { - .userstory-settings { - opacity: 1; - transition: all .2s ease-in; - } - } - .userstory-name { - flex: 1; - } - .userstory-settings { - flex-shrink: 0; - width: 60px; - } - .status { - flex-shrink: 0; - width: 125px; - } - .assigned-to-column { - flex-shrink: 0; - width: 150px; - img { - flex-basis: 35px; - // width & height they are only required for IE - height: 35px; - width: 35px; - } - } - .project { - flex-basis: 100px; - img { - width: 40px; - } - } - } - - .userstory-name { - display: flex; - margin-right: 1rem; - - span { - margin-right: .25rem; - } - } - .status { - position: relative; - } - .closed { - border-left: 10px solid $whitish; - color: $whitish; - a, - svg { - fill: $whitish; - } - .userstory-name a { - color: $whitish; - text-decoration: line-through; - - } - } - .blocked { - background: rgba($red-light, .2); - border-left: 10px solid $red-light; - } - .userstory-settings { - align-items: center; - display: flex; - opacity: 0; - svg { - @include svg-size(1.1rem); - fill: $gray-light; - margin-right: .5rem; - transition: fill .2s ease-in; - &:hover { - fill: $gray; - } - } - a { - &:hover { - cursor: pointer; - } - } - } - .delete-userstory { - &:hover { - .icon-trash { - fill: $red-light; - } - } - } - .avatar { - align-items: center; - display: flex; - img { - flex-basis: 35px; - // width & height they are only required for IE - height: 35px; - width: 35px; - } - figcaption { - margin-left: .5rem; - } - } } diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade index 7c7b8a41..c7790332 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade @@ -1,3 +1,7 @@ +tg-svg.icon-drag( + svg-icon="icon-drag" +) + .userstory-name - var hash = "#"; a( diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss new file mode 100644 index 00000000..64b82340 --- /dev/null +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss @@ -0,0 +1,112 @@ +tg-related-userstory-row { + @include font-size(small); + align-items: center; + border-bottom: 1px solid $whitish; + display: flex; + padding: .5rem 0 .5rem .5rem; + &:hover { + background: rgba($primary-light, .05); + .userstory-settings { + opacity: 1; + transition: all .2s ease-in; + } + .icon-drag { + opacity: 1; + } + } + .icon-drag { + @include svg-size(.75rem); + cursor: move; + fill: $whitish; + opacity: 0; + transition: opacity .1s; + } + .status { + flex-shrink: 0; + position: relative; + width: 125px; + } + .assigned-to-column { + flex-shrink: 0; + width: 150px; + img { + flex-basis: 35px; + // width & height they are only required for IE + height: 35px; + width: 35px; + } + } + .project { + flex-basis: 100px; + img { + width: 40px; + } + } + .userstory-name { + display: flex; + flex: 1; + margin-right: 1rem; + + span { + margin-right: .25rem; + } + } + .closed { + border-left: 10px solid $whitish; + color: $whitish; + a, + svg { + fill: $whitish; + } + .userstory-name a { + color: $whitish; + text-decoration: line-through; + + } + } + .blocked { + background: rgba($red-light, .2); + border-left: 10px solid $red-light; + } + .userstory-settings { + align-items: center; + display: flex; + flex-shrink: 0; + opacity: 0; + width: 60px; + svg { + @include svg-size(1.1rem); + fill: $gray-light; + margin-right: .5rem; + transition: fill .2s ease-in; + &:hover { + fill: $gray; + } + } + a { + &:hover { + cursor: pointer; + } + } + } + .delete-userstory { + &:hover { + .icon-trash { + fill: $red-light; + } + } + } + .avatar { + align-items: center; + display: flex; + img { + flex-basis: 35px; + // width & height they are only required for IE + height: 35px; + width: 35px; + } + figcaption { + margin-left: .5rem; + } + } +} diff --git a/app/modules/resources/epics-resource.service.coffee b/app/modules/resources/epics-resource.service.coffee index a63029c9..293830b9 100644 --- a/app/modules/resources/epics-resource.service.coffee +++ b/app/modules/resources/epics-resource.service.coffee @@ -70,6 +70,13 @@ Resource = (urlsService, http) -> return http.post(url, params) + service.reorderRelatedUserstory = (epicId, userstoryId, data, setOrders) -> + url = urlsService.resolve("epic-related-userstories", epicId) + "/#{userstoryId}" + + options = {"headers": {"set-orders": JSON.stringify(setOrders)}} + + return http.patch(url, data, null, options) + service.bulkCreateRelatedUserStories = (epicId, projectId, bulk_userstories) -> url = urlsService.resolve("epic-related-userstories-bulk-create", epicId) From fb6b1ec09556a611501e64d715066e2041190321 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 9 Sep 2016 08:21:31 +0200 Subject: [PATCH 220/315] Fixing epics reorder on epics dashboard --- app/modules/epics/epics.service.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee index 27e8f0b2..e5129024 100644 --- a/app/modules/epics/epics.service.coffee +++ b/app/modules/epics/epics.service.coffee @@ -69,7 +69,7 @@ class EpicsService ).toJS() data = { - order: newOrder, + epics_order: newOrder, version: epic.get('version') } From 802344104e99f1aea76d8be5f69cb559124ad86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 9 Sep 2016 08:27:01 +0200 Subject: [PATCH 221/315] Use tgEpicsService instead of tgResources --- .../related-userstories-controller.coffee | 14 ++++++++------ .../related-userstories.controller.spec.coffee | 18 ++++++------------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/app/modules/epics/related-userstories/related-userstories-controller.coffee b/app/modules/epics/related-userstories/related-userstories-controller.coffee index 4b9be953..0ab2fd1f 100644 --- a/app/modules/epics/related-userstories/related-userstories-controller.coffee +++ b/app/modules/epics/related-userstories/related-userstories-controller.coffee @@ -20,18 +20,20 @@ module = angular.module("taigaEpics") class RelatedUserStoriesController - @.$inject = ["tgResources", "tgEpicsService"] + @.$inject = ["tgEpicsService"] - constructor: (@rs, @epicsService) -> + constructor: (@epicsService) -> @.sectionName = "Epics" @.showCreateRelatedUserstoriesLightbox = false loadRelatedUserstories: () -> - @rs.userstories.listInEpic(@.epic.get('id')).then (data) => - @.userstories = data + @epicsService.listRelatedUserStories(@.epic) + .then (userstories) => + @.userstories = userstories reorderRelatedUserstory: (us, newIndex) -> - @epicsService.reorderRelatedUserstory(@.epic, @.userstories, us, newIndex).then (userstories) => - @.userstories = userstories + @epicsService.reorderRelatedUserstory(@.epic, @.userstories, us, newIndex) + .then (userstories) => + @.userstories = userstories module.controller("RelatedUserStoriesCtrl", RelatedUserStoriesController) diff --git a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee index 2543b085..30611140 100644 --- a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee +++ b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee @@ -23,17 +23,9 @@ describe "RelatedUserStories", -> controller = null mocks = {} - _mockTgResources = () -> - mocks.tgResources = { - userstories: { - listInEpic: sinon.stub() - } - } - - provide.value "tgResources", mocks.tgResources - _mockTgEpicsService = () -> mocks.tgEpicsService = { + listRelatedUserStories: sinon.stub() reorderRelatedUserstory: sinon.stub() } @@ -42,7 +34,6 @@ describe "RelatedUserStories", -> _mocks = () -> module ($provide) -> provide = $provide - _mockTgResources() _mockTgEpicsService() return null @@ -66,7 +57,11 @@ describe "RelatedUserStories", -> id: 66 }) - promise = mocks.tgResources.userstories.listInEpic.withArgs(66).promise().resolve(userstories) + promise = mocks.tgEpicsService.listRelatedUserStories + .withArgs(ctrl.epic) + .promise() + .resolve(userstories) + ctrl.loadRelatedUserstories().then () -> expect(ctrl.userstories).is.equal(userstories) done() @@ -95,7 +90,6 @@ describe "RelatedUserStories", -> id: 66 }) - promise = mocks.tgEpicsService.reorderRelatedUserstory .withArgs(ctrl.epic, ctrl.userstories, userstories.get(1), 0) .promise() From c11082d4c192796e420435e9d001e4c9e5db7439 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 9 Sep 2016 08:33:51 +0200 Subject: [PATCH 222/315] Fixing votes css for epics dashboard --- app/modules/epics/dashboard/epic-row/epic-row.scss | 4 ++++ app/modules/epics/dashboard/story-row/story-row.scss | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index c8d78a16..7c1d7814 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -84,6 +84,10 @@ } .vote { color: $gray; + &.is-voter { + color: $primary-light; + fill: $primary-light; + } } .assigned { img { diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index 51aa9fcd..df1e4f7d 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -43,6 +43,10 @@ } .vote { color: $gray; + &.is-voter { + color: $primary-light; + fill: $primary-light; + } } .project, .assigned { From 4fbe44d97ed4bb21844013d9eff140d5a0eeeefb Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 9 Sep 2016 10:10:15 +0200 Subject: [PATCH 223/315] Setting metada on epics dashboard --- app/locales/taiga/locale-en.json | 2 ++ .../epics-dashboard.controller.coffee | 13 ++++++-- .../epics-dashboard.controller.spec.coffee | 33 +++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 097fcf39..22ce0317 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -407,6 +407,8 @@ "TITLE": "EPICS", "SECTION_NAME": "Epics", "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", "DASHBOARD": { "ADD": "+ ADD EPIC", "UNASSIGNED": "Unassigned" diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index 043d8e92..f5e5de26 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -28,17 +28,26 @@ class EpicsDashboardController "lightboxService", "$tgConfirm", "tgProjectService", - "tgEpicsService" + "tgEpicsService", + "tgAppMetaService", + "$translate" ] constructor: (@params, @errorHandlingService, @lightboxFactory, @lightboxService, - @confirm, @projectService, @epicsService) -> + @confirm, @projectService, @epicsService, @appMetaService, @translate) -> @.sectionName = "EPICS.SECTION_NAME" taiga.defineImmutableProperty @, 'project', () => return @projectService.project taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics + title = @translate.instant("EPICS.PAGE_TITLE", {projectName: @.project.get('name')}) + description = @translate.instant("EPICS.PAGE_DESCRIPTION", { + projectName: @.project.get("name"), + projectDescription: @.project.get("description") + }) + @appMetaService.setAll(title, description) + loadInitialData: () -> @epicsService.clear() return @projectService.setProjectBySlug(@params.pslug) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee index a18b7c4a..c4025018 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee @@ -34,6 +34,10 @@ describe "EpicsDashboard", -> setProjectBySlug: sinon.stub() hasPermission: sinon.stub() isEpicsDashboardEnabled: sinon.stub() + project: Immutable.Map({ + "name": "testing name" + "description": "testing description" + }) } provide.value "tgProjectService", mocks.tgProjectService @@ -73,6 +77,20 @@ describe "EpicsDashboard", -> provide.value "lightboxService", mocks.lightboxService + _mockTgAppMetaService = () -> + mocks.tgAppMetaService = { + setAll: sinon.stub() + } + + provide.value "tgAppMetaService", mocks.tgAppMetaService + + _mockTranslate = () -> + mocks.translate = { + instant: sinon.stub() + } + + provide.value "$translate", mocks.translate + _mocks = () -> module ($provide) -> provide = $provide @@ -83,6 +101,8 @@ describe "EpicsDashboard", -> _mockTgErrorHandlingService() _mockTgLightboxFactory() _mockLightboxService() + _mockTgAppMetaService() + _mockTranslate() return null @@ -94,6 +114,19 @@ describe "EpicsDashboard", -> inject ($controller) -> controller = $controller + it "metada is set", (done) -> + mocks.translate.instant.withArgs("EPICS.PAGE_TITLE", { + projectName: "testing name" + }).returns("TITLE") + mocks.translate.instant.withArgs("EPICS.PAGE_DESCRIPTION", { + projectName: "testing name" + projectDescription: "testing description" + }).returns("DESCRIPTION") + + ctrl = controller("EpicsDashboardCtrl") + expect(mocks.tgAppMetaService.setAll).have.been.calledWith("TITLE", "DESCRIPTION") + done() + it "load data because epics panel is enabled and user has permissions", (done) -> ctrl = controller("EpicsDashboardCtrl") From b527d394645588bdc5d8b03afc5357377397efef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 9 Sep 2016 10:18:19 +0200 Subject: [PATCH 224/315] Fix tg-color-selector in admin panel --- .../modules/admin/project-values.coffee | 12 ++++++++-- .../includes/components/select-color.jade | 24 ++----------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index 89797b06..42df12a3 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -31,6 +31,8 @@ joinStr = @.taiga.joinStr groupBy = @.taiga.groupBy bindOnce = @.taiga.bindOnce debounce = @.taiga.debounce +getDefaulColorList = @.taiga.getDefaulColorList + module = angular.module("taigaAdmin") @@ -179,7 +181,9 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, $tra } initializeTextTranslations = -> - $scope.addNewElementText = $translate.instant("ADMIN.PROJECT_VALUES_#{objName.toUpperCase()}.ACTION_ADD") + $scope.addNewElementText = $translate.instant( + "ADMIN.PROJECT_VALUES_#{objName.toUpperCase()}.ACTION_ADD" + ) initializeNewValue() initializeTextTranslations() @@ -320,7 +324,8 @@ ProjectValuesDirective = ($log, $repo, $confirm, $location, animationFrame, $tra return {link:link} -module.directive("tgProjectValues", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", "$translate", "$rootScope", ProjectValuesDirective]) +module.directive("tgProjectValues", ["$log", "$tgRepo", "$tgConfirm", "$tgLocation", "animationFrame", + "$translate", "$rootScope", ProjectValuesDirective]) ############################################################################# @@ -331,6 +336,8 @@ ColorSelectionDirective = () -> ## Color selection Link link = ($scope, $el, $attrs, $model) -> + $scope.colorList = getDefaulColorList() + $scope.allowEmpty = false if $attrs.tgAllowEmpty $scope.allowEmpty = true @@ -369,6 +376,7 @@ ColorSelectionDirective = () -> $el.find(".select-color").hide() $el.on "keyup", "input", (event) -> + event.stopPropagation() if event.keyCode == 13 $scope.$apply -> $model.$modelValue.color = $scope.color diff --git a/app/partials/includes/components/select-color.jade b/app/partials/includes/components/select-color.jade index 448455fa..16e3e777 100644 --- a/app/partials/includes/components/select-color.jade +++ b/app/partials/includes/components/select-color.jade @@ -1,27 +1,7 @@ div.popover.select-color ul - li.color(style="background: #fce94f", data-color="#fce94f") - li.color(style="background: #edd400", data-color="#edd400") - li.color(style="background: #c4a000", data-color="#c4a000") - li.color(style="background: #8ae234", data-color="#8ae234") - li.color(style="background: #73d216", data-color="#73d216") - li.color(style="background: #4e9a06", data-color="#4e9a06") - li.color(style="background: #d3d7cf", data-color="#d3d7cf") - li.color(style="background: #fcaf3e", data-color="#fcaf3e") - li.color(style="background: #f57900", data-color="#f57900") - li.color(style="background: #ce5c00", data-color="#ce5c00") - li.color(style="background: #729fcf", data-color="#729fcf") - li.color(style="background: #3465a4", data-color="#3465a4") - li.color(style="background: #204a87", data-color="#204a87") - li.color(style="background: #888a85", data-color="#888a85") - li.color(style="background: #ad7fa8", data-color="#ad7fa8") - li.color(style="background: #75507b", data-color="#75507b") - li.color(style="background: #5c3566", data-color="#5c3566") - li.color(style="background: #ef2929", data-color="#ef2929") - li.color(style="background: #cc0000", data-color="#cc0000") - li.color(style="background: #a40000", data-color="#a40000") - li.color(style="background: #2e3436", data-color="#2e3436", ng-if="!allowEmpty") - li.color(data-color="", ng-class="{'empty-color': allowEmpty}") + li.color(ng-repeat="c in colorList" ng-style="::{background: c}", data-color="{{::c }}") + li.color.empty-color(ng-if="allowEmpty", data-color="") input(type="text", placeholder="personalized colors", ng-model="color") div.selected-color(ng-style="{'background-color': color}", ng-if="color !== null") From fb287301502bef7f25c1ba92a4947999663d445e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 9 Sep 2016 12:50:33 +0200 Subject: [PATCH 225/315] Add more tests --- .../epic-row/epic-row.controller.spec.coffee | 1 - .../epics-dashboard.controller.spec.coffee | 1 - .../story-row/story-row.controller.coffee | 5 +- .../story-row.controller.spec.coffee | 71 +++++++++++++++++++ 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 app/modules/epics/dashboard/story-row/story-row.controller.spec.coffee diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee index 6e287471..f3df7c64 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.spec.coffee @@ -74,7 +74,6 @@ describe "EpicRow", -> }) } - ctrl._calculateProgressBar() expect(ctrl.percentage).to.be.equal("50%") it "calculate progress bar in zero US", () -> diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee index c4025018..419396b5 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee @@ -18,7 +18,6 @@ ### describe "EpicsDashboard", -> - EpicsDashboardCtrl = null provide = null controller = null mocks = {} diff --git a/app/modules/epics/dashboard/story-row/story-row.controller.coffee b/app/modules/epics/dashboard/story-row/story-row.controller.coffee index db82f81b..ce959248 100644 --- a/app/modules/epics/dashboard/story-row/story-row.controller.coffee +++ b/app/modules/epics/dashboard/story-row/story-row.controller.coffee @@ -31,6 +31,9 @@ class StoryRowController else totalTasks = @.story.get('tasks').size totalTasksCompleted = @.story.get('tasks').filter((it) -> it.get("is_closed")).size - @.percentage = "#{totalTasksCompleted * 100 / totalTasks}%" + if totalTasks == 0 + @.percentage = "0%" + else + @.percentage = "#{totalTasksCompleted * 100 / totalTasks}%" module.controller("StoryRowCtrl", StoryRowController) diff --git a/app/modules/epics/dashboard/story-row/story-row.controller.spec.coffee b/app/modules/epics/dashboard/story-row/story-row.controller.spec.coffee new file mode 100644 index 00000000..9f6fe6b5 --- /dev/null +++ b/app/modules/epics/dashboard/story-row/story-row.controller.spec.coffee @@ -0,0 +1,71 @@ +### +# Copyright (C) 2014-2015 Taiga Agile LLC +# +# 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: story-row.controller.spec.coffee +### + +describe "StoryRowCtrl", -> + controller = null + + beforeEach -> + module "taigaEpics" + + inject ($controller) -> + controller = $controller + + it "calculate percentage for some closed tasks", () -> + data = { + story: Immutable.fromJS( + tasks: [ + {is_closed: true}, + {is_closed: true}, + {is_closed: true}, + {is_closed: false}, + {is_closed: false}, + ] + ) + } + + ctrl = controller "StoryRowCtrl", null, data + expect(ctrl.percentage).to.be.equal("60%") + + it "calculate percentage for closed story", () -> + data = { + story: Immutable.fromJS( + tasks: [ + {is_closed: true}, + {is_closed: true}, + {is_closed: true}, + {is_closed: false}, + {is_closed: false}, + ] + is_closed: true + ) + } + + ctrl = controller "StoryRowCtrl", null, data + expect(ctrl.percentage).to.be.equal("100%") + + it "calculate percentage for closed story", () -> + data = { + story: Immutable.fromJS( + tasks: [] + ) + } + + ctrl = controller "StoryRowCtrl", null, data + expect(ctrl.percentage).to.be.equal("0%") + From fa485e4d77fe2b324abfd1fb28a90a1697aec141 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 9 Sep 2016 12:41:06 +0200 Subject: [PATCH 226/315] Adding tests for epics service --- .../attachments-full.service.coffee | 2 +- app/modules/epics/epics.service.coffee | 15 +- app/modules/epics/epics.service.spec.coffee | 233 ++++++++++++++++++ 3 files changed, 241 insertions(+), 9 deletions(-) create mode 100644 app/modules/epics/epics.service.spec.coffee diff --git a/app/modules/components/attachments-full/attachments-full.service.coffee b/app/modules/components/attachments-full/attachments-full.service.coffee index 9e1fd874..2635f7c4 100644 --- a/app/modules/components/attachments-full/attachments-full.service.coffee +++ b/app/modules/components/attachments-full/attachments-full.service.coffee @@ -109,7 +109,7 @@ class AttachmentsFullService extends taiga.Service patch = {order: attachment.getIn(['file', 'order'])} promises.push @attachmentsService.patch(attachment.getIn(['file', 'id']), type, patch) - + return Promise.all(promises).then () => @._attachments = attachments diff --git a/app/modules/epics/epics.service.coffee b/app/modules/epics/epics.service.coffee index e5129024..4095bbf1 100644 --- a/app/modules/epics/epics.service.coffee +++ b/app/modules/epics/epics.service.coffee @@ -24,11 +24,10 @@ class EpicsService 'tgProjectService', 'tgAttachmentsService' 'tgResources', - 'tgXhrErrorService', - '$q' + 'tgXhrErrorService' ] - constructor: (@projectService, @attachmentsService, @resources, @xhrError, @q) -> + constructor: (@projectService, @attachmentsService, @resources, @xhrError) -> @._epics = Immutable.List() taiga.defineImmutableProperty @, 'epics', () => return @._epics @@ -52,14 +51,15 @@ class EpicsService .then (epic) => promises = _.map attachments.toJS(), (attachment) => @attachmentsService.upload(attachment.file, epic.get('id'), epic.get('project'), 'epic') - @q.all(promises).then () => + + Promise.all(promises).then () => @.fetchEpics() reorderEpic: (epic, newIndex) -> withoutMoved = @.epics.filter (it) => it.get('id') != epic.get('id') beforeDestination = withoutMoved.slice(0, newIndex) - previous = beforeDestination.last() + previous = beforeDestination.last() newOrder = if !previous then 0 else previous.get('epics_order') + 1 previousWithTheSameOrder = beforeDestination.filter (it) => @@ -72,7 +72,6 @@ class EpicsService epics_order: newOrder, version: epic.get('version') } - return @resources.epics.reorder(epic.get('id'), data, setOrders) .then () => @.fetchEpics() @@ -86,9 +85,9 @@ class EpicsService previousWithTheSameOrder = beforeDestination.filter (it) => it.get('epic_order') == previous.get('epic_order') - - setOrders = Immutable.OrderedMap previousWithTheSameOrder.map (it) => + setOrders = _.fromPairs previousWithTheSameOrder.map((it) => [it.get('id'), it.get('epic_order')] + ).toJS() data = { order: newOrder diff --git a/app/modules/epics/epics.service.spec.coffee b/app/modules/epics/epics.service.spec.coffee new file mode 100644 index 00000000..58efa075 --- /dev/null +++ b/app/modules/epics/epics.service.spec.coffee @@ -0,0 +1,233 @@ +### +# Copyright (C) 2014-2016 Taiga Agile LLC +# +# 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: epics.service.spec.coffee +### + +describe "tgEpicsService", -> + epicsService = provide = null + mocks = {} + + _mockTgProjectService = () -> + mocks.tgProjectService = { + project: Immutable.Map({ + "id": 1 + }) + } + + provide.value "tgProjectService", mocks.tgProjectService + + _mockTgAttachmentsService = () -> + mocks.tgAttachmentsService = { + upload: sinon.stub() + } + + provide.value "tgAttachmentsService", mocks.tgAttachmentsService + + _mockTgResources = () -> + mocks.tgResources = { + epics: { + list: sinon.stub() + post: sinon.stub() + patch: sinon.stub() + reorder: sinon.stub() + reorderRelatedUserstory: sinon.stub() + } + userstories: { + listInEpic: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _mockTgXhrErrorService = () -> + mocks.tgXhrErrorService = { + response: sinon.stub() + } + + provide.value "tgXhrErrorService", mocks.tgXhrErrorService + + _inject = (callback) -> + inject (_tgEpicsService_) -> + epicsService = _tgEpicsService_ + callback() if callback + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgProjectService() + _mockTgAttachmentsService() + _mockTgResources() + _mockTgXhrErrorService() + return null + + _setup = -> + _mocks() + + beforeEach -> + module "taigaEpics" + _setup() + _inject() + + it "clear epics", () -> + epicsService._epics = Immutable.List(Immutable.Map({ + 'id': 1 + })) + + epicsService.clear() + expect(epicsService._epics.size).to.be.equal(0) + + it "fetch epics success", () -> + epics = Immutable.fromJS([ + { id: 111 } + { id: 112 } + ]) + promise = mocks.tgResources.epics.list.withArgs(1).promise().resolve(epics) + epicsService.fetchEpics().then () -> + expect(epicsService.epics).to.be.equal(epics) + + it "fetch epics error", () -> + epics = Immutable.fromJS([ + { id: 111 } + { id: 112 } + ]) + promise = mocks.tgResources.epics.list.withArgs(1).promise().reject(new Error("error")) + epicsService.fetchEpics().then () -> + expect(mocks.tgXhrErrorService.response.withArgs(new Error("error"))).have.been.calledOnce + + it "list related userstories", () -> + epic = Immutable.fromJS({ + id: 1 + }) + epicsService.listRelatedUserStories(epic) + expect(mocks.tgResources.userstories.listInEpic.withArgs(epic.get('id'))).have.been.calledOnce + + it "createEpic", () -> + epicData = {} + epic = Immutable.fromJS({ + id: 111 + project: 1 + }) + attachments = Immutable.fromJS([ + {file: "f1"}, + {file: "f2"} + ]) + + mocks.tgResources.epics + .post + .withArgs({project: 1}) + .promise() + .resolve(epic) + + mocks.tgAttachmentsService + .upload + .promise() + .resolve() + + epicsService.fetchEpics = sinon.stub() + epicsService.createEpic(epicData, attachments).then () -> + expect(mocks.tgAttachmentsService.upload.withArgs("f1", 111, 1, "epic")).have.been.calledOnce + expect(mocks.tgAttachmentsService.upload.withArgs("f2", 111, 1, "epic")).have.been.calledOnce + expect(epicsService.fetchEpics).have.been.calledOnce + + it "Update epic status", () -> + epic = Immutable.fromJS({ + id: 1 + version: 1 + }) + + mocks.tgResources.epics + .patch + .withArgs(1, {status: 33, version: 1}) + .promise() + .resolve() + + epicsService.fetchEpics = sinon.stub() + epicsService.updateEpicStatus(epic, 33).then () -> + expect(epicsService.fetchEpics).have.been.calledOnce + + it "Update epic assigned to", () -> + epic = Immutable.fromJS({ + id: 1 + version: 1 + }) + + mocks.tgResources.epics + .patch + .withArgs(1, {assigned_to: 33, version: 1}) + .promise() + .resolve() + + epicsService.fetchEpics = sinon.stub() + epicsService.updateEpicAssignedTo(epic, 33).then () -> + expect(epicsService.fetchEpics).have.been.calledOnce + + it "reorder epic", () -> + epicsService._epics = Immutable.fromJS([ + { + id: 1 + epics_order: 1 + version: 1 + }, + { + id: 2 + epics_order: 2 + version: 1 + }, + { + id: 3 + epics_order: 3 + version: 1 + }, + ]) + + mocks.tgResources.epics.reorder + .withArgs(3, {epics_order: 2, version: 1}, {1: 1}) + .promise() + .resolve() + + epicsService.fetchEpics = sinon.stub() + epicsService.reorderEpic(epicsService._epics.get(2), 1).then () -> + expect(epicsService.fetchEpics).have.been.calledOnce + + it "reorder related userstory in epic", () -> + epic = Immutable.fromJS({ + id: 1 + }) + + epicUserstories = Immutable.fromJS([ + { + id: 1 + epic_order: 1 + }, + { + id: 2 + epic_order: 2 + }, + { + id: 3 + epic_order: 3 + }, + ]) + + mocks.tgResources.epics.reorderRelatedUserstory + .withArgs(1, 3, {order: 2}, {1: 1}) + .promise() + .resolve() + + epicsService.listRelatedUserStories = sinon.stub() + epicsService.reorderRelatedUserstory(epic, epicUserstories, epicUserstories.get(2), 1).then () -> + expect(epicsService.listRelatedUserStories.withArgs(epic)).have.been.calledOnce From e8a215517787d7dd0d427b1c95baf0ebe8a0e84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 12 Sep 2016 12:48:19 +0200 Subject: [PATCH 227/315] Fix Epics header styles --- .../components/color-selector/color-selector.scss | 1 - app/partials/epic/epic-detail.jade | 5 ++--- app/styles/modules/epics/epic-detail.scss | 10 ++++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 app/styles/modules/epics/epic-detail.scss diff --git a/app/modules/components/color-selector/color-selector.scss b/app/modules/components/color-selector/color-selector.scss index 2f06ccb0..6bf35ad9 100644 --- a/app/modules/components/color-selector/color-selector.scss +++ b/app/modules/components/color-selector/color-selector.scss @@ -15,7 +15,6 @@ .tag-color { @include color-selector-option; border: 1px solid $gray-light; - border-left: 0; border-radius: 0; margin: 0; transition: background .3s ease-out; diff --git a/app/partials/epic/epic-detail.jade b/app/partials/epic/epic-detail.jade index edee4440..977bdf35 100644 --- a/app/partials/epic/epic-detail.jade +++ b/app/partials/epic/epic-detail.jade @@ -18,8 +18,8 @@ div.wrapper( on-downvote="ctrl.onDownvote" ) - .detail-header-container - tg-color-selector( + .detail-header-container.epic-header-container(ng-class="{blocked: epic.is_blocked}") + tg-color-selector.color-selector( is-required="true" init-color="epic.color" on-select-color="ctrl.onSelectColor(color)" @@ -28,7 +28,6 @@ div.wrapper( item="epic" project="project" required-perm="modify_epic" - ng-class="{blocked: epic.is_blocked}" ng-if="project && epic" format="text" ) diff --git a/app/styles/modules/epics/epic-detail.scss b/app/styles/modules/epics/epic-detail.scss new file mode 100644 index 00000000..fe7a80b5 --- /dev/null +++ b/app/styles/modules/epics/epic-detail.scss @@ -0,0 +1,10 @@ +.epic-header-container { + display: flex; + .color-selector { + margin-right: .5rem; + } + tg-detail-header { + flex: 1; + width: 100%; + } +} From 19d0a5d0ac0ec8b15f011c0388b43c95b6ef5dda Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 9 Sep 2016 14:35:31 +0200 Subject: [PATCH 228/315] Epics in timeline --- app/locales/taiga/locale-en.json | 10 ++- app/modules/history/history/history-diff.jade | 7 +- .../history-templates/history-color.jade | 9 +++ .../profile/profile-favs/items/ticket.jade | 10 +++ .../profile-favs.controller.coffee | 9 +++ .../profile-favs.controller.spec.coffee | 76 +++++++++++++++++-- .../profile/profile-favs/profile-favs.jade | 13 ++++ .../user-timeline-item-title.service.coffee | 20 ++++- .../user-timeline-item-type.service.coffee | 37 +++++++++ .../user-timeline.service.coffee | 3 +- 10 files changed, 179 insertions(+), 15 deletions(-) create mode 100644 app/modules/history/history/history-templates/history-color.jade diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 22ce0317..6f7a4823 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -847,6 +847,8 @@ "FILTER_TYPE_ALL_TITLE": "Show all", "FILTER_TYPE_PROJECTS": "Projects", "FILTER_TYPE_PROJECT_TITLES": "Show only projects", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Stories", "FILTER_TYPE_USER_STORIES_TITLES": "Show only user stories", "FILTER_TYPE_TASKS": "Tasks", @@ -1202,7 +1204,8 @@ "SPRINT_ORDER": "sprint order", "KANBAN_ORDER": "kanban order", "TASKBOARD_ORDER": "taskboard order", - "US_ORDER": "us order" + "US_ORDER": "us order", + "COLOR": "color" } }, "BACKLOG": { @@ -1576,6 +1579,8 @@ "TASK_CREATED_WITH_US": "{{username}} has created a new task {{obj_name}} in {{project_name}} which belongs to the US {{us_name}}", "WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} created the project {{project_name}}", "MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}", "US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}", @@ -1588,9 +1593,12 @@ "TASK_UPDATED_WITH_US": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}} to {{new_value}}", "WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", "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_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} has a new member", "US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}", "US_MOVED": "{{username}} has moved the US {{obj_name}}", diff --git a/app/modules/history/history/history-diff.jade b/app/modules/history/history/history-diff.jade index aa6d53c8..58ea6d6a 100644 --- a/app/modules/history/history/history-diff.jade +++ b/app/modules/history/history/history-diff.jade @@ -43,6 +43,11 @@ ) include history-templates/history-custom-attributes +.diff-wrapper( + ng-if="vm.type == 'color'" +) + include history-templates/history-color + .diff-wrapper( ng-if="vm.type == 'team_requirement'" ) @@ -57,5 +62,3 @@ ng-if="vm.type == 'is_blocked'" ) include history-templates/blocked - - diff --git a/app/modules/history/history/history-templates/history-color.jade b/app/modules/history/history/history-templates/history-color.jade new file mode 100644 index 00000000..d97314bb --- /dev/null +++ b/app/modules/history/history/history-templates/history-color.jade @@ -0,0 +1,9 @@ +.diff-status-wrapper + span.key( + translate="ACTIVITY.FIELDS.COLOR" + ) + span.diff(ng-if="vm.diff[0]") {{vm.diff[0]}} + tg-svg( + svg-icon="icon-arrow-right" + ) + span.diff(ng-if="vm.diff[1]") {{vm.diff[1]}} diff --git a/app/modules/profile/profile-favs/items/ticket.jade b/app/modules/profile/profile-favs/items/ticket.jade index b5539492..60bee680 100644 --- a/app/modules/profile/profile-favs/items/ticket.jade +++ b/app/modules/profile/profile-favs/items/ticket.jade @@ -24,6 +24,10 @@ p span.ticket-project | {{:: vm.item.get('project_name') }} + span.ticket-type( + ng-if="::vm.item.get('type') === 'epic'" + translate="COMMON.EPIC" + ) span.ticket-type( ng-if="::vm.item.get('type') === 'userstory'" translate="COMMON.USER_STORY" @@ -44,6 +48,12 @@ ) h2 span.ticket-id(tg-bo-ref="vm.item.get('ref')") + a.ticket-title( + href="#" + ng-if="::vm.item.get('type') === 'epic'" + tg-nav="project-epics-detail:project=vm.item.get('project_slug'),ref=vm.item.get('ref')" + title="#{{ ::vm.item.get('ref') }} {{ ::vm.item.get('subject') }}" + ) {{ ::vm.item.get('subject') }} a.ticket-title( href="#" ng-if="::vm.item.get('type') === 'userstory'" diff --git a/app/modules/profile/profile-favs/profile-favs.controller.coffee b/app/modules/profile/profile-favs/profile-favs.controller.coffee index f47f7823..2fc3ac7c 100644 --- a/app/modules/profile/profile-favs/profile-favs.controller.coffee +++ b/app/modules/profile/profile-favs/profile-favs.controller.coffee @@ -28,6 +28,7 @@ class FavsBaseController _init: -> @.enableFilterByAll = true @.enableFilterByProjects = true + @.enableFilterByEpics = true @.enableFilterByUserStories = true @.enableFilterByTasks = true @.enableFilterByIssues = true @@ -101,6 +102,12 @@ class FavsBaseController @._resetList() @.loadItems() + showEpicsOnly: -> + if @.type isnt "epic" + @.type = "epic" + @._resetList() + @.loadItems() + showUserStoriesOnly: -> if @.type isnt "userstory" @.type = "userstory" @@ -134,6 +141,7 @@ class ProfileLikedController extends FavsBaseController @.tabName = 'likes' @.enableFilterByAll = false @.enableFilterByProjects = false + @.enableFilterByEpics = false @.enableFilterByUserStories = false @.enableFilterByTasks = false @.enableFilterByIssues = false @@ -158,6 +166,7 @@ class ProfileVotedController extends FavsBaseController @.tabName = 'upvotes' @.enableFilterByAll = true @.enableFilterByProjects = false + @.enableFilterByEpics = true @.enableFilterByUserStories = true @.enableFilterByTasks = true @.enableFilterByIssues = true diff --git a/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee b/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee index 89ccd289..a761fe64 100644 --- a/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee +++ b/app/modules/profile/profile-favs/profile-favs.controller.spec.coffee @@ -11,7 +11,7 @@ # 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 +# You showld have received a copy of the GNU Affero General Public License # along with this program. If not, see . # # File: profile-favs.controller.spec.coffee @@ -127,7 +127,7 @@ describe "ProfileLiked", -> expect(ctrl.q).to.be.equal(textQuery) done() - it "shou loading spinner during the call to the api", (done) -> + it "show loading spinner during the call to the api", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileLiked", $scope, {user: user}) @@ -154,7 +154,7 @@ describe "ProfileLiked", -> expect(ctrl.isLoading).to.be.false done() - it "shou no results placeholder", (done) -> + it "show no results placeholder", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileLiked", $scope, {user: user}) @@ -282,6 +282,37 @@ describe "ProfileVoted", -> expect(ctrl.q).to.be.equal(textQuery) done() + it "show only items of epics", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileVoted", $scope, {user: user}) + + type = "epic" + + items = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + + mocks.userServices.getVoted.withArgs(user.get("id"), 1, type, null).promise().resolve(items) + + expect(ctrl.items.size).to.be.equal(0) + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.null + + ctrl.showEpicsOnly().then () => + expectItems = items.get("data") + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.type + expect(ctrl.q).to.be.null + done() + it "show only items of user stories", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileVoted", $scope, {user: user}) @@ -375,7 +406,7 @@ describe "ProfileVoted", -> expect(ctrl.q).to.be.null done() - it "shou loading spinner during the call to the api", (done) -> + it "show loading spinner during the call to the api", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileVoted", $scope, {user: user}) @@ -402,7 +433,7 @@ describe "ProfileVoted", -> expect(ctrl.isLoading).to.be.false done() - it "shou no results placeholder", (done) -> + it "show no results placeholder", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileVoted", $scope, {user: user}) @@ -560,6 +591,37 @@ describe "ProfileWatched", -> expect(ctrl.q).to.be.null done() + it "show only items of epics", (done) -> + $scope = $rootScope.$new() + ctrl = $controller("ProfileWatched", $scope, {user: user}) + + type = "epic" + + items = Immutable.fromJS({ + data: [ + {id: 1}, + {id: 2}, + {id: 3} + ], + next: true + }) + + mocks.userServices.getWatched.withArgs(user.get("id"), 1, type, null).promise().resolve(items) + + expect(ctrl.items.size).to.be.equal(0) + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.null + expect(ctrl.q).to.be.null + + ctrl.showEpicsOnly().then () => + expectItems = items.get("data") + + expect(ctrl.items.equals(expectItems)).to.be.true + expect(ctrl.scrollDisabled).to.be.false + expect(ctrl.type).to.be.type + expect(ctrl.q).to.be.null + done() + it "show only items of user stories", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileWatched", $scope, {user: user}) @@ -653,7 +715,7 @@ describe "ProfileWatched", -> expect(ctrl.q).to.be.null done() - it "shou loading spinner during the call to the api", (done) -> + it "show loading spinner during the call to the api", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileWatched", $scope, {user: user}) @@ -680,7 +742,7 @@ describe "ProfileWatched", -> expect(ctrl.isLoading).to.be.false done() - it "shou no results placeholder", (done) -> + it "show no results placeholder", (done) -> $scope = $rootScope.$new() ctrl = $controller("ProfileWatched", $scope, {user: user}) diff --git a/app/modules/profile/profile-favs/profile-favs.jade b/app/modules/profile/profile-favs/profile-favs.jade index 0317836b..7f4d1d59 100644 --- a/app/modules/profile/profile-favs/profile-favs.jade +++ b/app/modules/profile/profile-favs/profile-favs.jade @@ -26,6 +26,14 @@ section.profile-favs title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS_TITLE'|translate }}" translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS'|translate }}" ) + a( + href="" + ng-if="::vm.enableFilterByEpics" + ng-click="vm.showEpicsOnly()" + ng-class="{active: vm.type === 'epic'}" + title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_EPICS_TITLE'|translate }}" + translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_EPICS'|translate }}" + ) a( href="" ng-if="::vm.enableFilterByUserStories" @@ -64,6 +72,11 @@ section.profile-favs tg-fav-item="item" item-type="project" ) + div( + ng-switch-when="epic" + tg-fav-item="item" + item-type="epic" + ) div( ng-switch-when="userstory" tg-fav-item="item" diff --git a/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee index b19769ed..84a189c3 100644 --- a/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee +++ b/app/modules/user-timeline/user-timeline-item/user-timeline-item-title.service.coffee @@ -35,7 +35,8 @@ class UserTimelineItemTitle 'priority': 'ISSUES.FIELDS.PRIORITY', 'type': 'ISSUES.FIELDS.TYPE', 'is_iocaine': 'TASK.FIELDS.IS_IOCAINE', - 'is_blocked': 'COMMON.FIELDS.IS_BLOCKED' + 'is_blocked': 'COMMON.FIELDS.IS_BLOCKED', + 'color': 'COMMON.FIELDS.COLOR' } _params: { @@ -89,6 +90,18 @@ class UserTimelineItemTitle return @._getLink(url, text) + related_us_name: (timeline, event) -> + obj = timeline.getIn(["data", "userstory"]) + url = "project-userstories-detail:project=timeline.getIn(['data', 'userstory', 'project', 'slug']),ref=timeline.getIn(['data', 'userstory', 'ref'])" + text = '#' + obj.get('ref') + ' ' + obj.get('subject') + return @._getLink(url, text) + + epic_name: (timeline, event) -> + obj = timeline.getIn(["data", "epic"]) + url = "project-epics-detail:project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['data', 'epic', 'ref'])" + text = '#' + obj.get('ref') + ' ' + obj.get('subject') + return @._getLink(url, text) + obj_name: (timeline, event) -> obj = @._getTimelineObj(timeline, event) url = @._getDetailObjUrl(event) @@ -122,9 +135,9 @@ class UserTimelineItemTitle "task": ["project-tasks-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'ref'])"], "userstory": ["project-userstories-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'ref'])"], "parent_userstory": ["project-userstories-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'userstory', 'ref'])"], - "milestone": ["project-taskboard", ":project=timeline.getIn(['data', 'project', 'slug']),sprint=timeline.getIn(['obj', 'slug'])"] + "milestone": ["project-taskboard", ":project=timeline.getIn(['data', 'project', 'slug']),sprint=timeline.getIn(['obj', 'slug'])"], + "epic": ["project-epics-detail", ":project=timeline.getIn(['data', 'project', 'slug']),ref=timeline.getIn(['obj', 'ref'])"] } - return url[event.obj][0] + url[event.obj][1] _getLink: (url, text, title) -> @@ -153,7 +166,6 @@ class UserTimelineItemTitle timeline_type.translate_params.forEach (param) => params[param] = @._translateTitleParams(param, timeline, event) - return params getTitle: (timeline, event, type) -> 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 5306ac5f..b13d6de4 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 @@ -82,6 +82,18 @@ timelineType = (timeline, event) -> key: 'TIMELINE.MILESTONE_CREATED', translate_params: ['username', 'project_name', 'obj_name'] }, + { # NewEpic + check: (timeline, event) -> + return event.obj == 'epic' && event.type == 'create' + key: 'TIMELINE.EPIC_CREATED', + translate_params: ['username', 'project_name', 'obj_name'] + }, + { # NewEpicRelatedUserstory + check: (timeline, event) -> + return event.obj == 'relateduserstory' && event.type == 'create' + key: 'TIMELINE.EPIC_RELATED_USERSTORY_CREATED', + translate_params: ['username', 'project_name', 'related_us_name', 'epic_name'] + }, { # NewUsComment check: (timeline, event) -> return timeline.getIn(['data', 'comment']) && event.obj == 'userstory' @@ -109,6 +121,15 @@ timelineType = (timeline, event) -> text = timeline.getIn(['data', 'comment_html']) return $($.parseHTML(text)).text() }, + { # NewEpicComment + check: (timeline, event) -> + return timeline.getIn(['data', 'comment']) && event.obj == 'epic' + key: 'TIMELINE.NEW_COMMENT_EPIC' + translate_params: ['username', 'obj_name'], + description: (timeline) -> + text = timeline.getIn(['data', 'comment_html']) + return $($.parseHTML(text)).text() + }, { # UsMove check: (timeline, event) -> return timeline.hasIn(['data', 'value_diff']) && @@ -258,6 +279,22 @@ timelineType = (timeline, event) -> key: 'TIMELINE.TASK_UPDATED_WITH_US_NEW_VALUE', translate_params: ['username', 'field_name', 'obj_name', 'us_name', 'new_value'] }, + { # EpicUpdated description + check: (timeline, event) -> + return event.obj == 'epic' && + event.type == 'change' && + timeline.hasIn(['data', 'value_diff']) && + timeline.getIn(['data', 'value_diff', 'key']) == 'description_diff' + key: 'TIMELINE.EPIC_UPDATED', + translate_params: ['username', 'field_name', 'obj_name'] + }, + { # EpicUpdated general + check: (timeline, event) -> + return event.obj == 'epic' && + event.type == 'change' + key: 'TIMELINE.EPIC_UPDATED_WITH_NEW_VALUE', + translate_params: ['username', 'field_name', 'obj_name', 'new_value'] + }, { # New User check: (timeline, event) -> return event.obj == 'user' && event.type == 'create' 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 61adc2a4..aecf5724 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.service.coffee +++ b/app/modules/user-timeline/user-timeline/user-timeline.service.coffee @@ -47,7 +47,8 @@ class UserTimelineService extends taiga.Service # customs 'blocked', 'moveInBacklog', - 'milestone' + 'milestone', + 'color' ] _invalid: [ From 8e3e49c1a7baa18889fc9f40800c23e8b22a19d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 12 Sep 2016 14:52:55 +0200 Subject: [PATCH 229/315] Color selector component has a full color range if color is required --- app/coffee/utils.coffee | 2 +- .../color-selector/color-selector.controller.coffee | 9 +++++++-- .../color-selector/color-selector.controller.spec.coffee | 6 ++++++ .../color-selector/color-selector.directive.coffee | 2 +- .../components/color-selector/color-selector.jade | 2 +- app/modules/epics/create-epic/create-epic.jade | 2 +- app/partials/epic/epic-detail.jade | 2 +- 7 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/coffee/utils.coffee b/app/coffee/utils.coffee index b4580029..a2d02c99 100644 --- a/app/coffee/utils.coffee +++ b/app/coffee/utils.coffee @@ -242,7 +242,7 @@ patch = (oldImmutable, newImmutable) -> DEFAULT_COLOR_LIST = [ '#fce94f', '#edd400', '#c4a000', '#8ae234', '#73d216', '#4e9a06', '#d3d7cf', '#fcaf3e', '#f57900', '#ce5c00', '#729fcf', '#3465a4', '#204a87', '#888a85', - '#ad7fa8', '#75507b', '#5c3566', '#ef2929', '#cc0000', '#a40000' + '#ad7fa8', '#75507b', '#5c3566', '#ef2929', '#cc0000', '#a40000', '#222222' ] getRandomDefaultColor = () -> diff --git a/app/modules/components/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee index c30d9729..a52ed21b 100644 --- a/app/modules/components/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -24,13 +24,18 @@ getDefaulColorList = taiga.getDefaulColorList class ColorSelectorController constructor: () -> @.colorList = getDefaulColorList() + @.checkIsColorRequired() @.displayColorList = false + checkIsColorRequired: () -> + if !@.isColorRequired + @.colorList = _.dropRight(@.colorList); + setColor: (color) -> @.color = @.initColor resetColor: () -> - if @.isRequired and not @.color + if @.isColorRequired and not @.color @.color = @.initColor toggleColorList: () -> @@ -45,7 +50,7 @@ class ColorSelectorController onKeyDown: (event) -> if event.which == 13 # ENTER event.stopPropagation() - if @.color or not @.isRequired + if @.color or not @.isColorRequired @.onSelectDropdownColor(@.color) diff --git a/app/modules/components/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee index 09597fee..f12b85cd 100644 --- a/app/modules/components/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -36,6 +36,12 @@ describe "ColorSelector", -> inject ($controller) -> controller = $controller + it.only "require Color on Selector", () -> + colorSelectorCtrl = controller "ColorSelectorCtrl" + colorSelectorCtrl.colorList = ["#000000", "#123123"] + colorSelectorCtrl.isColorRequired = false + colorSelectorCtrl.checkIsColorRequired() + expect(colorSelectorCtrl.colorList).to.be.eql(["#000000"]) it "display Color Selector", () -> colorSelectorCtrl = controller "ColorSelectorCtrl" diff --git a/app/modules/components/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee index fb091711..fc9df40a 100644 --- a/app/modules/components/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -53,7 +53,7 @@ ColorSelectorDirective = ($timeout) -> controller: "ColorSelectorCtrl", controllerAs: "vm", bindToController: { - isRequired: "=", + isColorRequired: "=", onSelectColor: "&", initColor: "=" }, diff --git a/app/modules/components/color-selector/color-selector.jade b/app/modules/components/color-selector/color-selector.jade index 80025485..7a472f02 100644 --- a/app/modules/components/color-selector/color-selector.jade +++ b/app/modules/components/color-selector/color-selector.jade @@ -13,7 +13,7 @@ ng-click="vm.onSelectDropdownColor(color)" ) li.empty-color( - ng-if="!vm.isRequired" + ng-if="!vm.isColorRequired" ng-click="vm.onSelectDropdownColor(null)" ) .custom-color-selector diff --git a/app/modules/epics/create-epic/create-epic.jade b/app/modules/epics/create-epic/create-epic.jade index 504c7216..e93a63fe 100644 --- a/app/modules/epics/create-epic/create-epic.jade +++ b/app/modules/epics/create-epic/create-epic.jade @@ -9,7 +9,7 @@ tg-lightbox-close .color-selector fieldset tg-color-selector( - is-required="true" + is-color-required="true" init-color="vm.newEpic.color" on-select-color="vm.selectColor(color)" ) diff --git a/app/partials/epic/epic-detail.jade b/app/partials/epic/epic-detail.jade index 977bdf35..dcaa775e 100644 --- a/app/partials/epic/epic-detail.jade +++ b/app/partials/epic/epic-detail.jade @@ -20,7 +20,7 @@ div.wrapper( .detail-header-container.epic-header-container(ng-class="{blocked: epic.is_blocked}") tg-color-selector.color-selector( - is-required="true" + is-color-required="true" init-color="epic.color" on-select-color="ctrl.onSelectColor(color)" ) From 182dd4720e027f0d3e82cb64168c9fbf55a2307f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 13 Sep 2016 12:58:59 +0200 Subject: [PATCH 230/315] Layout refactor for epics lightbox --- app/locales/taiga/locale-en.json | 5 +- .../related-userstories-create.jade | 79 ++++++------ .../related-userstories-create.scss | 117 +++++++++--------- 3 files changed, 100 insertions(+), 101 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 6f7a4823..e90ba578 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1075,11 +1075,10 @@ "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", - "CREATE_RELATED_USERSTORIES": "Create a relationship with a user story", - "RELATED_WITH": "Related with", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", "NEW_USERSTORY": "New user story", "EXISTING_USERSTORY": "Existing user story", - "CHOOSE_PROJECT_FOR_CREATION": "Whats' the project?", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", "SUBJECT": "Subject", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade index 468acbbb..9f549da1 100644 --- a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade @@ -4,17 +4,14 @@ a.add-button.e2e-add-userstory-button( ) tg-svg(svg-icon="icon-add") -div.lightbox.lightbox-create-related-user-stories +.lightbox.lightbox-create-related-user-stories tg-lightbox-close - div.form + .lightbox-create-related-user-stories-wrapper h2.title(translate="EPIC.CREATE_RELATED_USERSTORIES") - .related-with-selector-title - legend(translate="EPIC.RELATED_WITH") - .related-with-selector - fieldset + .related-with-selector-single input( type="radio" name="related-with-selector" @@ -26,7 +23,7 @@ div.lightbox.lightbox-create-related-user-stories label.e2e-new-userstory-label(for="new-user-story") span.name {{ 'EPIC.NEW_USERSTORY' | translate}} - fieldset + .related-with-selector-single input( type="radio" name="related-with-selector" @@ -37,40 +34,39 @@ div.lightbox.lightbox-create-related-user-stories label.e2e-existing-user-story-label(for="existing-user-story") span.name {{ 'EPIC.EXISTING_USERSTORY' | translate}} - .project-selector-title - legend( + fieldset.project-selector + label( ng-if="relatedWithSelector=='new-user-story'" translate="EPIC.CHOOSE_PROJECT_FOR_CREATION" + for="project-selector-dropdown" ) - - legend( + label( ng-if="relatedWithSelector=='existing-user-story'" translate="EPIC.CHOOSE_PROJECT_FROM" + for="project-selector-dropdown" ) - - .project-selector() select( ng-model="selectedProject" ng-change="selectProject(selectedProject)" data-required="true" required ng-options="p.id as p.name for p in vm.projects | toMutable" + id="project-selector-dropdown" ) - div(ng-show="relatedWithSelector=='new-user-story'") - .new-user-story-selector - .new-user-story-title - legend( - ng-show="creationMode=='single-new-user-story'" - translate="EPIC.SUBJECT" - ) + fieldset(ng-show="relatedWithSelector=='new-user-story'") + .new-user-story-title + label( + ng-show="creationMode=='single-new-user-story'" + translate="EPIC.SUBJECT" + ) - legend( - ng-show="creationMode=='bulk-new-user-stories'" - translate="EPIC.SUBJECT_BULK_MODE" - ) + label( + ng-show="creationMode=='bulk-new-user-stories'" + translate="EPIC.SUBJECT_BULK_MODE" + ) .new-user-story-options - fieldset + .new-user-story-option-single input( type="radio" name="new-user-story-selector" @@ -82,7 +78,7 @@ div.lightbox.lightbox-create-related-user-stories label.e2e-single-creation-label(for="single-new-user-story") tg-svg(svg-icon="icon-add") - fieldset + .new-user-story-option-single input( type="radio" name="new-user-story-selector" @@ -92,35 +88,37 @@ div.lightbox.lightbox-create-related-user-stories ) label.e2e-bulk-creation-label(for="bulk-new-user-stories") tg-svg(svg-icon="icon-bulk") + form.new-user-story-form .single-creation(ng-show="creationMode=='single-new-user-story'") input.e2e-new-userstory-input-text( type="text" ng-model="relatedUserstoriesText" - data-required="true" + required ) .bulk-creation(ng-show="creationMode=='bulk-new-user-stories'") textarea.e2e-new-userstories-input-textarea( ng-model="relatedUserstoriesText" - data-required="true" + required ) - a.button-green.e2e-create-userstory-button( + button.button-green.create-user-story.e2e-create-userstory-button.ng-animate-disabled( href="" ng-click="vm.bulkCreateRelatedUserStories(selectedProject, relatedUserstoriesText, closeLightbox)" tg-loading="vm.loading" + translate="COMMON.SAVE" + ng-show="relatedWithSelector=='new-user-story'" ) - span( - translate="COMMON.SAVE" - ) - .existing-user-story(ng-show="relatedWithSelector=='existing-user-story'") - .existing-user-story-title - legend(translate="EPIC.CHOOSE_USERSTORY") - - input.userstory.e2e-filter-userstories-input( + fieldset.existing-user-story(ng-show="relatedWithSelector=='existing-user-story'") + label( + translate="EPIC.CHOOSE_USERSTORY" + for="userstory-filter" + ) + input.userstory-filter.e2e-filter-userstories-input( + id="userstory-filter" type="text" placeholder="{{'EPIC.FILTER_USERSTORIES' | translate}}" ng-model="searchUserstory" @@ -132,7 +130,6 @@ div.lightbox.lightbox-create-related-user-stories size="5" ng-model="selectedUserstory" required - data-required="true" ) - var hash = "#"; option.hidden( @@ -143,11 +140,9 @@ div.lightbox.lightbox-create-related-user-stories value="{{ ::us.id }}" ) #{hash}{{::us.ref}} {{::us.subject}} - a.button-green.e2e-select-related-userstory-button( + button.button-green.e2e-select-related-userstory-button( href="" ng-click="vm.saveRelatedUserStory(selectedUserstory, closeLightbox)" tg-loading="vm.loading" + translate="COMMON.SAVE" ) - span( - translate="COMMON.SAVE" - ) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss index 82412585..1e397dbf 100644 --- a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.scss @@ -1,78 +1,83 @@ .lightbox-create-related-user-stories { - .related-with-selector-title, - .project-selector-title, - .new-user-story-title, - .existing-user-story-title { - display: flex; - justify-content: space-between; - margin-bottom: 1rem; + .lightbox-create-related-user-stories-wrapper { + max-width: 600px; + width: 90%; } - .related-with-selector, - .new-user-story-selector { + .related-with-selector { display: flex; + margin-bottom: 1rem; input { display: none; + &:checked+label { + background: $primary-light; + color: $white; + transition: background .2s ease-in; + } + &:checked+label:hover { + background: $primary-light; + } + +label { + background: rgba($whitish, .7); + cursor: pointer; + display: block; + padding: 2rem 1rem; + text-align: center; + text-transform: uppercase; + transition: background .2s ease-in; + } + +label:hover { + background: rgba($primary-light, .3); + transition: background .2s ease-in; + } } - fieldset { + .related-with-selector-single { + flex: 1; &:first-child { margin-right: .5rem; } } } - .project-selector, - .single-creation { + fieldset { + label { + display: inline-block; + margin-bottom: .5rem; + } + } + .new-user-story-title { + align-items: flex-end; + display: flex; + } + .existing-user-story-form, + .new-user-story-form { margin-bottom: 1rem; } - input { - &:checked+label { - background: $primary-light; - color: $white; - transition: background .2s ease-in; - &:hover { + .new-user-story-options { + display: flex; + margin-left: auto; + input { + display: none; + &:checked+label { background: $primary-light; - } - } - +label { - background: rgba($whitish, .7); - cursor: pointer; - display: block; - padding: 2rem 1rem; - text-align: center; - transition: background .2s ease-in; - &:hover { - background: rgba($primary-light, .3); + color: $white; + fill: $white; transition: background .2s ease-in; } - .icon { - fill: currentColor; - margin-top: .25rem; - vertical-align: text-top; + +label { + background: $mass-white; + color: $grayer; + cursor: pointer; + display: block; + padding: .5rem; + transition: background .2s ease-in; } - .name { - @include font-size(large); - text-transform: uppercase; + +label:hover { + background: $primary-light; + color: $white; + fill: $white; } } } - .new-user-story-selector { - display: flex; - justify-content: space-between; - .new-user-story-options { - display: flex; - } - fieldset { - width: auto; - } - label { - height: 1.5rem; - padding: 0; - width: 1.5rem; - } - } - - .existing-user-story { - .button-green { - margin-top: 1rem; - } + button { + width: 100%; } } From be715cc9e4b7baa32b329c39d4cfe2caf594b2ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 13 Sep 2016 13:06:28 +0200 Subject: [PATCH 231/315] Fix pointer in story row --- app/modules/epics/dashboard/story-row/story-row.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/modules/epics/dashboard/story-row/story-row.scss b/app/modules/epics/dashboard/story-row/story-row.scss index df1e4f7d..9ecc112c 100644 --- a/app/modules/epics/dashboard/story-row/story-row.scss +++ b/app/modules/epics/dashboard/story-row/story-row.scss @@ -6,7 +6,6 @@ align-items: center; background: $white; border-bottom: 1px solid $whitish; - cursor: pointer; display: flex; margin-left: 4rem; transition: background .2s; @@ -24,6 +23,9 @@ } .name { flex-basis: 17.5vw; + a { + cursor: pointer; + } } .progress-bar, .progress-status { @@ -48,6 +50,9 @@ fill: $primary-light; } } + .project { + cursor: pointer; + } .project, .assigned { img { From f70296ae43a274f6f92b1a190ccdb2d85ab76662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Tue, 13 Sep 2016 14:50:06 +0200 Subject: [PATCH 232/315] Fix style behaviour on related user stories --- .../related-userstory-row/related-userstory-row.scss | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss index 64b82340..ad3cabc3 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss @@ -2,6 +2,7 @@ tg-related-userstory-row { @include font-size(small); align-items: center; border-bottom: 1px solid $whitish; + cursor: move; display: flex; padding: .5rem 0 .5rem .5rem; &:hover { @@ -31,12 +32,12 @@ tg-related-userstory-row { width: 150px; img { flex-basis: 35px; - // width & height they are only required for IE height: 35px; width: 35px; } } .project { + cursor: pointer; flex-basis: 100px; img { width: 40px; @@ -46,9 +47,12 @@ tg-related-userstory-row { display: flex; flex: 1; margin-right: 1rem; - + a { + cursor: pointer; + } span { - margin-right: .25rem; + display: inline-block; + margin-left: .25rem; } } .closed { From 61e42eb4fa45d04c0b9850e0ad6e2a5a4931cd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 14 Sep 2016 08:39:06 +0200 Subject: [PATCH 233/315] Color templates in history --- .../history/history-templates/history-color.jade | 14 +++++++++++--- .../history-templates/history-templates.scss | 9 +++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/modules/history/history/history-templates/history-color.jade b/app/modules/history/history/history-templates/history-color.jade index d97314bb..89b69d87 100644 --- a/app/modules/history/history/history-templates/history-color.jade +++ b/app/modules/history/history/history-templates/history-color.jade @@ -1,9 +1,17 @@ -.diff-status-wrapper +.diff-color-wrapper span.key( translate="ACTIVITY.FIELDS.COLOR" ) - span.diff(ng-if="vm.diff[0]") {{vm.diff[0]}} + span.diff( + ng-if="vm.diff[0]" + ng-style="{background: vm.diff[0]}" + title="{{vm.diff[0]}}" + ) tg-svg( svg-icon="icon-arrow-right" ) - span.diff(ng-if="vm.diff[1]") {{vm.diff[1]}} + span.diff( + ng-if="vm.diff[1]" + ng-style="{background: vm.diff[1]}" + title="{{vm.diff[1]}}" + ) diff --git a/app/modules/history/history/history-templates/history-templates.scss b/app/modules/history/history/history-templates/history-templates.scss index 77d5d7ed..504bd69a 100644 --- a/app/modules/history/history/history-templates/history-templates.scss +++ b/app/modules/history/history/history-templates/history-templates.scss @@ -25,4 +25,13 @@ background: rgba($red-light, .3); } } + .diff-color-wrapper { + align-items: center; + display: flex; + .diff { + display: inline-block; + height: 1.2rem; + width: 1.2rem; + } + } } From 10533a933dfe604d11835f8e7aeec9c2262d4b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 14 Sep 2016 09:36:36 +0200 Subject: [PATCH 234/315] Add color to timeline --- app/locales/taiga/locale-en.json | 1 + .../user-timeline-item-type.service.coffee | 9 +++++++++ .../user-timeline-item/user-timeline-item.jade | 18 +++++++++--------- .../user-timeline/user-timeline.jade | 15 ++++++++++++--- .../user-timeline/user-timeline.scss | 9 +++++++++ 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index e90ba578..86bb1688 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1594,6 +1594,7 @@ "WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}", "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "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}}", 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 b13d6de4..9e33dcd0 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 @@ -288,6 +288,15 @@ timelineType = (timeline, event) -> key: 'TIMELINE.EPIC_UPDATED', translate_params: ['username', 'field_name', 'obj_name'] }, + { # EpicUpdated color + check: (timeline, event) -> + return event.obj == 'epic' && + event.type == 'change' && + timeline.hasIn(['data', 'value_diff']) && + timeline.getIn(['data', 'value_diff', 'key']) == 'color' + key: 'TIMELINE.EPIC_UPDATED_WITH_NEW_COLOR', + translate_params: ['username', 'field_name', 'obj_name', 'new_value'] + }, { # EpicUpdated general check: (timeline, event) -> return event.obj == 'epic' && 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 43604994..a85bcf0b 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 @@ -1,22 +1,22 @@ -div.activity-item +.activity-item span.activity-date {{::timeline.get('created') | momentFromNow}} - div.activity-info(tg-user-timeline-title="timeline") + .activity-info(tg-user-timeline-title="timeline") - div.activity-info + .activity-info // profile image with url - div.profile-contact-picture(ng-if="timeline.getIn(['data', 'user', 'is_profile_visible'])") + .profile-contact-picture(ng-if="timeline.getIn(['data', 'user', 'is_profile_visible'])") a(tg-nav="user-profile:username=timeline.getIn(['data', 'user', 'username'])", title="{{::timeline.getIn(['data', 'user', 'name']) }}") img( tg-avatar="timeline.getIn(['data', 'user'])" alt="{{::timeline.getIn(['data', 'user', 'name'])}}" ) // profile image without url - div.profile-contact-picture(ng-if="!timeline.getIn(['data', 'user', 'is_profile_visible'])") - img( - tg-avatar="timeline.getIn(['data', 'user'])" - alt="{{::timeline.getIn(['data', 'user', 'name'])}}" - ) + .profile-contact-picture(ng-if="!timeline.getIn(['data', 'user', 'is_profile_visible'])") + img( + tg-avatar="timeline.getIn(['data', 'user'])" + alt="{{::timeline.getIn(['data', 'user', 'name'])}}" + ) p(tg-compile-html="timeline.get('title_html')") diff --git a/app/modules/user-timeline/user-timeline/user-timeline.jade b/app/modules/user-timeline/user-timeline/user-timeline.jade index b227d860..8c8bdcc1 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.jade +++ b/app/modules/user-timeline/user-timeline/user-timeline.jade @@ -1,7 +1,16 @@ section.profile-timeline div(ng-if="!vm.timelineList.size") div.spin - img(src="/#{v}/svg/spinner-circle.svg", alt="Loading...") + img( + src="/#{v}/svg/spinner-circle.svg" + alt="Loading..." + ) - div(infinite-scroll="vm.loadTimeline()", infinite-scroll-disabled="vm.scrollDisabled") - div(tg-repeat="timeline in vm.timelineList", tg-user-timeline-item="timeline") + div( + infinite-scroll="vm.loadTimeline()" + infinite-scroll-disabled="vm.scrollDisabled" + ) + div( + tg-repeat="timeline in vm.timelineList" + tg-user-timeline-item="timeline" + ) diff --git a/app/modules/user-timeline/user-timeline/user-timeline.scss b/app/modules/user-timeline/user-timeline/user-timeline.scss index 54d03b71..bcb89710 100644 --- a/app/modules/user-timeline/user-timeline/user-timeline.scss +++ b/app/modules/user-timeline/user-timeline/user-timeline.scss @@ -56,6 +56,15 @@ width: 100%; } } + .new-color { + border-radius: 50%; + display: inline-block; + height: 1rem; + margin-left: .2rem; + position: relative; + top: .1rem; + width: 1rem; + } } .activity-member-view { display: flex; From d16ece1e8079d29c43ee6bccb51335fd750091e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 14 Sep 2016 15:19:07 +0200 Subject: [PATCH 235/315] Add class for toggl --- app/modules/epics/dashboard/epics-dashboard.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 1fa89a3e..b8343a50 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -1,6 +1,6 @@ .wrapper(ng-init="vm.loadInitialData()") tg-project-menu - section.main(role="main") + section.main.epics(role="main") header.header-with-actions h1( tg-main-title From 9bae924dce8b1c8c6a0ba15e2e2396af14febace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 15 Sep 2016 09:41:21 +0200 Subject: [PATCH 236/315] Fixes color selector layout for empty and full color list --- .../tags/components/add-tag-input.jade | 1 + .../includes/modules/admin/project-tags.jade | 23 +++++-------------- app/styles/layout/admin-project-tags.scss | 5 +++- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/app/modules/components/tags/components/add-tag-input.jade b/app/modules/components/tags/components/add-tag-input.jade index 5e8de537..62b8cf1c 100644 --- a/app/modules/components/tags/components/add-tag-input.jade +++ b/app/modules/components/tags/components/add-tag-input.jade @@ -22,6 +22,7 @@ tg-color-selector( ng-if="!vm.disableColorSelection" on-select-color="vm.selectColor(color)" + is-color-required="false" ) tg-svg.save( diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index 2692089e..61538b00 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -6,16 +6,11 @@ section .admin-tags-section-wrapper form.add-tag-container.new-value.hidden - tg-color-selection.color-column( - tg-allow-empty="true" + tg-color-selector.color-column( + is-color-required="false" ng-model="newValue" + on-select-color="newValue.color = color" ) - .current-color( - ng-style="{background: newValue.color}" - ng-if="newValue.color" - ) - .current-color.empty-color(ng-if="!newValue.color") - include ../../components/select-color .tag-name input( @@ -115,17 +110,11 @@ section ) .row.tag-row.table-main.edition.hidden - .color-column( - tg-color-selection - tg-allow-empty="true" + tg-color-selector.color-column( + is-color-required="false" ng-model="tag" + on-select-color="tag.color = color" ) - .current-color( - ng-style="{background: tag.color}" - ng-if="tag.color" - ) - .current-color.empty-color(ng-if="!tag.color") - include ../../components/select-color .status-name input( diff --git a/app/styles/layout/admin-project-tags.scss b/app/styles/layout/admin-project-tags.scss index 731eb430..d9d7fedf 100644 --- a/app/styles/layout/admin-project-tags.scss +++ b/app/styles/layout/admin-project-tags.scss @@ -27,7 +27,10 @@ background: $white; } .icon { - opacity: 1; + &.icon-close, + &.icon-save { + opacity: 1; + } } } From d9905b47d9b18ed904c8ebec177000ec26637f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 15 Sep 2016 09:52:08 +0200 Subject: [PATCH 237/315] Remove only from tests --- .../color-selector/color-selector.controller.spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/components/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee index f12b85cd..a8c76c80 100644 --- a/app/modules/components/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -36,7 +36,7 @@ describe "ColorSelector", -> inject ($controller) -> controller = $controller - it.only "require Color on Selector", () -> + it "require Color on Selector", () -> colorSelectorCtrl = controller "ColorSelectorCtrl" colorSelectorCtrl.colorList = ["#000000", "#123123"] colorSelectorCtrl.isColorRequired = false From 278ca823c1a2d6c25f2f05d327aa7591067b44d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 16 Sep 2016 14:56:51 +0200 Subject: [PATCH 238/315] Add epics illustration --- .../epics/dashboard/epics-dashboard.jade | 4 ++-- .../epics/dashboard/epics-dashboard.scss | 18 ------------------ 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index b8343a50..50b88b81 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -18,9 +18,9 @@ ng-if="vm.epics.size" ) - section.empty-epics(ng-if="!vm.epics.size") + section.empty-epics.empty-large(ng-if="!vm.epics.size") img( - src="/#{v}/images/epics-empty.png" + src="/#{v}/images/empty/empty_des.png" ng-title="EPICS.EMPTY.HELP | translate" ) h1.title(translate="EPICS.EMPTY.TITLE") diff --git a/app/modules/epics/dashboard/epics-dashboard.scss b/app/modules/epics/dashboard/epics-dashboard.scss index b52027dc..159d1671 100644 --- a/app/modules/epics/dashboard/epics-dashboard.scss +++ b/app/modules/epics/dashboard/epics-dashboard.scss @@ -1,23 +1,5 @@ .empty-epics { - margin: 0 auto; - padding: 5vh; text-align: center; - width: 650px; - .title { - @include font-type(normal); - @include font-size(larger); - color: $gray-light; - margin-bottom: .5rem; - text-transform: none; - } - img { - margin: 2rem auto; - text-align: center; - width: 6rem; - } - p { - color: $gray-light; - } a { color: $primary; display: block; From c7c929363c9adbad98364ba43650b8d2efbb2be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Mon, 19 Sep 2016 16:04:30 +0200 Subject: [PATCH 239/315] Fix meta in some pages --- .../epics-dashboard.controller.coffee | 18 +++++++++++++----- .../projects/project/project.controller.coffee | 12 +++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.coffee index f5e5de26..040a1d47 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.coffee @@ -41,12 +41,20 @@ class EpicsDashboardController taiga.defineImmutableProperty @, 'project', () => return @projectService.project taiga.defineImmutableProperty @, 'epics', () => return @epicsService.epics - title = @translate.instant("EPICS.PAGE_TITLE", {projectName: @.project.get('name')}) - description = @translate.instant("EPICS.PAGE_DESCRIPTION", { - projectName: @.project.get("name"), + @appMetaService.setfn @._setMeta.bind(this) + + _setMeta: () -> + return null if !@.project + + ctx = { + projectName: @.project.get("name") projectDescription: @.project.get("description") - }) - @appMetaService.setAll(title, description) + } + + return { + title: @translate.instant("EPICS.PAGE_TITLE", ctx) + description: @translate.instant("EPICS.PAGE_DESCRIPTION", ctx) + } loadInitialData: () -> @epicsService.clear() diff --git a/app/modules/projects/project/project.controller.coffee b/app/modules/projects/project/project.controller.coffee index 418afda7..af9d2f60 100644 --- a/app/modules/projects/project/project.controller.coffee +++ b/app/modules/projects/project/project.controller.coffee @@ -35,16 +35,14 @@ class ProjectController @appMetaService.setfn @._setMeta.bind(this) - _setMeta: (project)-> + _setMeta: ()-> return null if !@.project - metas = {} - ctx = {projectName: @.project.get("name")} - metas.title = @translate.instant("PROJECT.PAGE_TITLE", ctx) - metas.description = @.project.get("description") - - return metas + return { + title: @translate.instant("PROJECT.PAGE_TITLE", ctx) + description: @.project.get("description") + } angular.module("taigaProjects").controller("Project", ProjectController) From ab21b3f92ebf34ca165a90cab8754baf0ab7d465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 20 Sep 2016 12:05:10 +0200 Subject: [PATCH 240/315] Redirect to blocked page in epics and project-home pages if the project is blocked --- app/coffee/app.coffee | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 513ee377..9a552542 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -537,8 +537,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven responseError: httpResponseError } - $provide.factory("authHttpIntercept", ["$q", "$location", "$tgNavUrls", "lightboxService", "tgErrorHandlingService", - authHttpIntercept]) + $provide.factory("authHttpIntercept", ["$q", "$location", "$tgNavUrls", "lightboxService", + "tgErrorHandlingService", authHttpIntercept]) $httpProvider.interceptors.push("authHttpIntercept") @@ -592,14 +592,13 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $httpProvider.interceptors.push("versionCheckHttpIntercept") - blockingIntercept = ($q, $routeParams, $location, $navUrls, errorHandlingService) -> + blockingIntercept = ($q, errorHandlingService) -> # API calls can return blocked elements and in that situation the user will be redirected # to the blocked project page # This can happens in two scenarios # - An ok response containing a blocked_code in the data # - An error reponse when updating/creating/deleting including a 451 error code redirectToBlockedPage = -> - pslug = $routeParams.pslug errorHandlingService.block() responseOk = (response) -> @@ -619,7 +618,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven responseError: responseError } - $provide.factory("blockingIntercept", ["$q", "$routeParams", "$location", "$tgNavUrls", "tgErrorHandlingService", blockingIntercept]) + $provide.factory("blockingIntercept", ["$q", "tgErrorHandlingService", blockingIntercept]) $httpProvider.interceptors.push("blockingIntercept") @@ -690,7 +689,8 @@ i18nInit = (lang, $translate) -> checksley.updateMessages('default', messages) -init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, loaderService, navigationBarService, errorHandlingService) -> +init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, + loaderService, navigationBarService, errorHandlingService) -> $log.debug("Initialize application") $rootscope.$on '$translatePartialLoaderStructureChanged', () -> @@ -733,6 +733,10 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na # Analytics $analytics.initialize() + # Initialize error handling service when location change start + $rootscope.$on '$locationChangeStart', (event) -> + errorHandlingService.init() + # On the first page load the loader is painted in `$routeChangeSuccess` # because we need to hide the tg-navigation-bar. # In the other cases the loader is in `$routeChangeSuccess` @@ -743,9 +747,7 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na un() - $rootscope.$on '$routeChangeSuccess', (event, next) -> - errorHandlingService.init() - + $rootscope.$on '$routeChangeSuccess', (event, next) -> if next.loader loaderService.start(true) @@ -760,7 +762,7 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na if next.mobileViewport appMetaService.addMobileViewport() - else + else appMetaService.removeMobileViewport() if next.disableHeader @@ -768,10 +770,13 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na else navigationBarService.enableHeader() -pluginsWithModule = _.filter(@.taigaContribPlugins, (plugin) -> plugin.module) - +# Config for infinite scroll angular.module('infinite-scroll').value('THROTTLE_MILLISECONDS', 500) +# Load modules +pluginsWithModule = _.filter(@.taigaContribPlugins, (plugin) -> plugin.module) +pluginsModules = _.map(pluginsWithModule, (plugin) -> plugin.module) + modules = [ # Main Global Modules "taigaBase", @@ -825,7 +830,7 @@ modules = [ "pascalprecht.translate", "infinite-scroll", "tgRepeat" -].concat(_.map(pluginsWithModule, (plugin) -> plugin.module)) +].concat(pluginsModules) # Main module definition module = angular.module("taiga", modules) From 17ef589550db0087898dbe16ec3c6aeb1a56f771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 20 Sep 2016 12:28:32 +0200 Subject: [PATCH 241/315] Fix tests --- .../epics-dashboard.controller.spec.coffee | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee index 419396b5..66f1f11a 100644 --- a/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee +++ b/app/modules/epics/dashboard/epics-dashboard.controller.spec.coffee @@ -78,15 +78,13 @@ describe "EpicsDashboard", -> _mockTgAppMetaService = () -> mocks.tgAppMetaService = { - setAll: sinon.stub() + setfn: sinon.stub() } provide.value "tgAppMetaService", mocks.tgAppMetaService _mockTranslate = () -> - mocks.translate = { - instant: sinon.stub() - } + mocks.translate = sinon.stub() provide.value "$translate", mocks.translate @@ -113,18 +111,9 @@ describe "EpicsDashboard", -> inject ($controller) -> controller = $controller - it "metada is set", (done) -> - mocks.translate.instant.withArgs("EPICS.PAGE_TITLE", { - projectName: "testing name" - }).returns("TITLE") - mocks.translate.instant.withArgs("EPICS.PAGE_DESCRIPTION", { - projectName: "testing name" - projectDescription: "testing description" - }).returns("DESCRIPTION") - + it "metada is set", () -> ctrl = controller("EpicsDashboardCtrl") - expect(mocks.tgAppMetaService.setAll).have.been.calledWith("TITLE", "DESCRIPTION") - done() + expect(mocks.tgAppMetaService.setfn).have.been.called it "load data because epics panel is enabled and user has permissions", (done) -> ctrl = controller("EpicsDashboardCtrl") From 1d1543d09ace620d752887dfb50bba9c824ee6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 20 Sep 2016 12:42:08 +0200 Subject: [PATCH 242/315] [i18n] Update locales --- app/locales/taiga/locale-ca.json | 181 ++++++++++++------ app/locales/taiga/locale-de.json | 237 ++++++++++++++++-------- app/locales/taiga/locale-es.json | 171 ++++++++++++----- app/locales/taiga/locale-fi.json | 171 ++++++++++++----- app/locales/taiga/locale-fr.json | 253 +++++++++++++++++--------- app/locales/taiga/locale-it.json | 171 ++++++++++++----- app/locales/taiga/locale-nl.json | 171 ++++++++++++----- app/locales/taiga/locale-pl.json | 175 +++++++++++++----- app/locales/taiga/locale-pt-br.json | 197 ++++++++++++++------ app/locales/taiga/locale-ru.json | 185 +++++++++++++------ app/locales/taiga/locale-sv.json | 171 ++++++++++++----- app/locales/taiga/locale-tr.json | 171 ++++++++++++----- app/locales/taiga/locale-zh-hant.json | 169 ++++++++++++----- 13 files changed, 1712 insertions(+), 711 deletions(-) diff --git a/app/locales/taiga/locale-ca.json b/app/locales/taiga/locale-ca.json index 977c8ac1..c12f8de2 100644 --- a/app/locales/taiga/locale-ca.json +++ b/app/locales/taiga/locale-ca.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "In item per línia", "NEW_BULK": "Nova inserció en grup", "RELATED_TASKS": "Tasques relacionades", + "PREVIOUS": "Previous", + "NEXT": "Següent", "LOGOUT": "Surt", "EXTERNAL_USER": "un usuari extern", "GENERIC_ERROR": "Un Oompa Loompas diu {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Aquest valor pareix invàlid.", "TYPE_EMAIL": "Deu ser un correu vàlid.", @@ -115,6 +122,7 @@ "USER_STORY": "Història d'usuari", "TASK": "Tasca", "ISSUE": "incidència", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Afegir tag", "DELETE": "Elimina l'etiqueta", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "filtres", + "TITLE": "Filtres", "INPUT_PLACEHOLDER": "Descripció o referència", "TITLE_ACTION_FILTER_BUTTON": "cerca", - "BREADCRUMB_TITLE": "tornar a categories", - "BREADCRUMB_FILTERS": "Filtres", - "BREADCRUMB_STATUS": "estats" + "INPUT_SEARCH_PLACEHOLDER": "Descripció o ref", + "TITLE_ACTION_SEARCH": "Cerca", + "ACTION_SAVE_CUSTOM_FILTER": "Guarda com a filtre", + "PLACEHOLDER_FILTER_NAME": "Escriu el filtre i pressiona Intro", + "CATEGORIES": { + "TYPE": "Tipus", + "STATUS": "Estats", + "SEVERITY": "Severitat", + "PRIORITIES": "Prioritats", + "TAGS": "Etiquetes", + "ASSIGNED_TO": "Assignat a", + "CREATED_BY": "Creat per", + "CUSTOM_FILTERS": "Filtres personalitzats" + }, + "CONFIRM_DELETE": { + "TITLE": "Esborrar filtre", + "MESSAGE": "el filtre '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Capçcalera de primer nivel", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Ajuda de Markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Vore sprints", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Observant", "DASHBOARD": "Panell principal" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Sense assignar" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Vots", + "NAME": "Nom", + "PROJECT": "Projecte", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Estats", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Bloquejat", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Els meus projectes - Taiga", "PAGE_DESCRIPTION": "Una llista de tots els teus projects, que pots reordenar o crear nous.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Editar valor", - "TITLE_ACTION_DELETE_VALUE": "Borrar valor" + "TITLE_ACTION_DELETE_VALUE": "Borrar valor", + "TITLE_ACTION_DELETE_TAG": "Elimina l'etiqueta" }, "HELP": "Necessites ajuda? Mira la nosta pàgina de suport!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Mòdules", "ENABLE": "Activa", "DISABLE": "Desactiva", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Organitza les històries d'usuari per a mantindre una vista organitzada i prioritzada del treball.", "NUMBER_SPRINTS": "Expected number of sprints", @@ -475,9 +544,9 @@ "PRIVATE_PROJECT": "Projecte privat", "PRIVATE_OR_PUBLIC": "What's the difference between public and private projects?", "DELETE": "Esborra aquest projecte", - "LOGO_HELP": "The image will be scaled to 80x80px.", + "LOGO_HELP": "S'escalarà la imatge a 80x80px.", "CHANGE_LOGO": "Change logo", - "ACTION_USE_DEFAULT_LOGO": "Use default image", + "ACTION_USE_DEFAULT_LOGO": "Utilitza la imatge per defecte", "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects allowed by your current plan", "MAX_PRIVATE_PROJECTS_MEMBERS": "The maximum number of members for private projects has been exceeded", "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Vas a canviar la URL d'accés al CSV. La URL previa no funcionarà. Estàs segur?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "informes d'històries d'usuari", "SECTION_TITLE_TASK": "infome de tasques", "SECTION_TITLE_ISSUE": "informe d'incidències", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Camps personalitzats", "SUBTITLE": "Especifiqueu els camps personalitzats del les vostres històries d'usuari, tasques i incidències", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Camps personalitzats d'històries d'usuari", "US_ADD": "Afegeix camps personalitzats en històries d'usuari", "TASK_DESCRIPTION": "Camps personalitzats de tasques", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Estat", "SUBTITLE": "Especifica els estats de les vostres històries d'usuari, tasques i incidències", - "US_TITLE": "Estats d'US", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Estats de tasques", "ISSUE_TITLE": "Estats d'incidències" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Etiquetes", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Afegeix l'etiqueta", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Rols - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "la invitació a '{{email}}'." }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Valor per defecte per a selector de punts", - "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 estats" + "LABEL_ISSUE_STATUS": "Valor per defecte per a selector de estats", + "LABEL_PRIORITY": "Valor per defecte per a selector de prioritat", + "LABEL_SEVERITY": "Valor per defecte per a selector de severitat" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Escriu un nom per a nou estat" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Mostrar tot", "FILTER_TYPE_PROJECTS": "Projectes", "FILTER_TYPE_PROJECT_TITLES": "Mostra només projectes", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Históries", "FILTER_TYPE_USER_STORIES_TITLES": "Veure només històries d'usuari", "FILTER_TYPE_TASKS": "Tasques", @@ -836,7 +917,7 @@ "CHANGE_PASSWORD": "Canvi de contrasenya", "DASHBOARD_TITLE": "Tauler", "DISCOVER_TITLE": "Discover trending projects", - "NEW_ITEM": "New", + "NEW_ITEM": "Nova", "DISCOVER": "Descobreix", "ACTION_REORDER": "Arrossega els elements per endreçar" }, @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opcional) Afegix un text personalizat a la invitació. Dis-li algo divertit als nous membres. ;-)", "PLACEHOLDER_TYPE_EMAIL": "Escriu un correu", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Història d'Usuari {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estat: {{userStoryStatus }}. Completat {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tasques tancades). Punts: {{userStoryPoints}}. Descripció: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Aquesta US ha sigut creada desde", "GO_TO_EXTERNAL_REFERENCE": "Anar a l'orige", "BLOCKED": "Aquest història d'usuari està bloquejada", - "PREVIOUS": "previa història d'usuari", - "NEXT": "Pròxima història d'usuari", "TITLE_DELETE_ACTION": "Esborra història d'usuari", "LIGHTBOX_TITLE_BLOKING_US": "Bloquejant US", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tasques completades", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "ordre d'sprint", "KANBAN_ORDER": "ordre de kanban", "TASKBOARD_ORDER": "ordre de panell de tasques", - "US_ORDER": "ordre d'US" + "US_ORDER": "ordre d'US", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtres", "REMOVE": "Esborra filtres", "HIDE": "Amaga filtres", - "SHOW": "Mostra filtres", - "FILTER_CATEGORY_STATUS": "Estats", - "FILTER_CATEGORY_TAGS": "Etiquetes" + "SHOW": "Mostra filtres" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Aquesta tasca ha sigut creada desde", "TITLE_LINK_GO_ORIGIN": "Anar a història d'usuari", "BLOCKED": "Aquesta tasca està bloquejada", - "PREVIOUS": "tasca prèvia", - "NEXT": "pròxima tasca", "TITLE_DELETE_ACTION": "Esborrar tasca", "LIGHTBOX_TITLE_BLOKING_TASK": "Bloquejant tasca", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "incidència", "ACTION_NEW_ISSUE": "+ NOVA INCIDÈNCIA", "ACTION_PROMOTE_TO_US": "Promocionar història d'usuari", - "PLACEHOLDER_FILTER_NAME": "Escriu el filtre i pressiona Intro", "PROMOTED": "Esta incidència ha sigut promcionada a US:", "EXTERNAL_REFERENCE": "Esta incidència ha sigut creada desde", "GO_TO_EXTERNAL_REFERENCE": "Anar a l'orige", "BLOCKED": "Aquesta incidència està bloquejada", - "TITLE_PREVIOUS_ISSUE": "incidència prèvia", - "TITLE_NEXT_ISSUE": "pròxima incidència", "ACTION_DELETE": "Esborrar incidència", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Bloquejant incidència", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Promociona aquesta incidència a història d'usuari", "MESSAGE": "Segur que vols crear una nova US desde aquesta incidència" }, - "FILTERS": { - "TITLE": "Filtres", - "INPUT_SEARCH_PLACEHOLDER": "Descripció o ref", - "TITLE_ACTION_SEARCH": "Busca", - "ACTION_SAVE_CUSTOM_FILTER": "Guarda com a filtre", - "BREADCRUMB": "Filtres", - "TITLE_BREADCRUMB": "Filtres", - "CATEGORIES": { - "TYPE": "Tipus", - "STATUS": "Estats", - "SEVERITY": "Severitat", - "PRIORITIES": "Prioritats", - "TAGS": "Etiquetes", - "ASSIGNED_TO": "Assignat a", - "CREATED_BY": "Creat per", - "CUSTOM_FILTERS": "Filtres personalitzats" - }, - "CONFIRM_DELETE": { - "TITLE": "Esborrar filtre", - "MESSAGE": "el filtre '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tipus", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Cerca - {{projectName}}", "PAGE_DESCRIPTION": "Busca qualsevol cosa al projecte {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Històries d'usuari", "FILTER_ISSUES": "Incidències", "FILTER_TASKS": "Tasca", @@ -1422,9 +1493,9 @@ } }, "USER_PROFILE": { - "IMAGE_HELP": "The image will be scaled to 80x80px.", + "IMAGE_HELP": "S'escalarà la imatge a 80x80px.", "ACTION_CHANGE_IMAGE": "Canviar", - "ACTION_USE_GRAVATAR": "Use default image", + "ACTION_USE_GRAVATAR": "Utilitza la imatge per defecte", "ACTION_DELETE_ACCOUNT": "Esborrar compte de Taiga", "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", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} ha creat una nova tasca {{obj_name}} a {{project_name}} provinent de US {{us_name}}", "WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} ha creat el projecte {{project_name}}", "MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}", "US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} ha actualitzat l'atribut \"{{field_name}}\" de la tasca {{obj_name}} de la història d'usuari {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} ha actualitzat l'atribut \"{{field_name}}\" de la tasca {{obj_name}} de la història d'usuari {{us_name}} amb el valor {{new_value}}", "WIKI_UPDATED": "{{username}} ha actualitzat la pàgina wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} ha comentat la història d'usuari {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} ha comentat la incidència {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} ha comentat la tasca {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} te un nou membre", "US_ADDED_MILESTONE": "{{username}} ha afegit la història d'usuari {{obj_name}} a {{sprint_name}}", "US_MOVED": "{{username}} ha mogut la història d'usuari {{obj_name}}", diff --git a/app/locales/taiga/locale-de.json b/app/locales/taiga/locale-de.json index 9de20cf3..f9bd86d1 100644 --- a/app/locales/taiga/locale-de.json +++ b/app/locales/taiga/locale-de.json @@ -5,8 +5,8 @@ "OR": "oder", "LOADING": "Wird geladen...", "LOADING_PROJECT": "Projekt wird geladen...", - "DATE": "DD MMM YYYY", - "DATETIME": "DD MMM YYYY HH:mm", + "DATE": "DD. MMM YYYY", + "DATETIME": "DD. MMM YYYY HH:mm", "SAVE": "Speichern", "CANCEL": "Abbrechen", "ACCEPT": "Akzeptieren", @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Ein Eintrag pro Zeile...", "NEW_BULK": "Neue Massenerstellung", "RELATED_TASKS": "Verbundene Aufgaben", + "PREVIOUS": "Previous", + "NEXT": "Weiter", "LOGOUT": "Ausloggen", "EXTERNAL_USER": "ein externer Benutzer", "GENERIC_ERROR": "Eins unserer Helferlein sagt {{error}}.", @@ -44,7 +46,12 @@ "OWNER": "Projekteigentümer", "CAPSLOCK_WARNING": "Achtung! Sie verwenden Großbuchstaben in einem Eingabefeld, dass Groß- und Kleinschreibung berücksichtigt.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Sind Sie sicher, dass Sie den Bearbeitungsmodus beenden möchten?", - "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Beachten Sie, dass alle Änderungen verloren gehen, wenn Sie den Bearbeitungsmodus schließen, ohne vorher zu speichern.", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Dieser Wert scheint ungültig zu sein.", "TYPE_EMAIL": "Dieser Wert sollte eine gültige E-Mail Adresse enthalten.", @@ -73,7 +80,7 @@ "PIKADAY": "Ungültiges Datumsformat. Bitte nutze DD MMM YYYY (etwa 23 März 1984)" }, "PICKERDATE": { - "FORMAT": "DD MMM YYYY", + "FORMAT": "DD. MMM YYYY", "IS_RTL": "falsch", "FIRST_DAY_OF_WEEK": "1", "PREV_MONTH": "Vorheriger Monat", @@ -115,6 +122,7 @@ "USER_STORY": "User-Story", "TASK": "Aufgabe", "ISSUE": "Ticket", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Schlagwort...", "DELETE": "Schlagwort löschen", @@ -196,9 +204,24 @@ "TITLE": "Filter", "INPUT_PLACEHOLDER": "Betreff oder Verweis", "TITLE_ACTION_FILTER_BUTTON": "suche", - "BREADCRUMB_TITLE": "zurück zu den Kategorien", - "BREADCRUMB_FILTERS": "Filter", - "BREADCRUMB_STATUS": "Status" + "INPUT_SEARCH_PLACEHOLDER": "Thema oder ref", + "TITLE_ACTION_SEARCH": "Suche", + "ACTION_SAVE_CUSTOM_FILTER": "Als Benutzerfilter speichern", + "PLACEHOLDER_FILTER_NAME": "Benennen Sie den Filter und drücken Sie die Eingabetaste", + "CATEGORIES": { + "TYPE": "Arten", + "STATUS": "Status", + "SEVERITY": "Gewichtung", + "PRIORITIES": "Prioritäten", + "TAGS": "Schlagwörter", + "ASSIGNED_TO": "Zugeordnet zu", + "CREATED_BY": "Erstellt durch", + "CUSTOM_FILTERS": "Benutzerfilter" + }, + "CONFIRM_DELETE": { + "TITLE": "Benutzerfilter löschen", + "MESSAGE": "der Benutzerfilter '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Überschrift 1", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Markdown syntax Hilfe" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Sprints ansehen", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Beobachtet", "DASHBOARD": "ProjeKte Dashboard" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ EPIC HINZUFÜGEN", + "UNASSIGNED": "Nicht zugeordnet" + }, + "EMPTY": { + "TITLE": "Es sieht so aus, als hätten Sie noch keine Epics erzeugt", + "EXPLANATION": "Erstellen Sie Epics als übergeordnete Einheit für User Storys. Epics können User Storys aus diesem oder anderen Projekten beinhalten oder aus Ihnen erstellt werden.", + "HELP": "Erfahren Sie mehr über Epics" + }, + "TABLE": { + "VOTES": "Stimmen", + "NAME": "Name", + "PROJECT": "Projekt", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Zugewiesen", + "STATUS": "Status", + "PROGRESS": "Fortschritt", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "Neues Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team-Anforderung", + "CLIENT_REQUIREMENT": "Kunden-Anforderung", + "BLOCKED": "Blockiert", + "BLOCKED_NOTE_PLACEHOLDER": "Warum ist dieses Epic geblockt?", + "CREATE_EPIC": "Epic erzeugen" + } + }, "PROJECTS": { "PAGE_TITLE": "Meine Projekte - Taiga", "PAGE_DESCRIPTION": "Eine Liste mit all Deinen Projekten. Du kannst sie ordnen oder ein Neues anlegen.", @@ -389,7 +455,7 @@ "HIDE_DEPRECATED": "- verworfene Anhänge verbergen", "COUNT_DEPRECATED": "({{ counter }} verworfen)", "MAX_UPLOAD_SIZE": "Die maximale Dateigröße beträgt {{maxFileSize}}", - "DATE": "DD MMM YYYY [um] hh:mm", + "DATE": "DD. MMM YYYY [um] hh:mm", "ERROR_UPLOAD_ATTACHMENT": "Das Hochladen war uns nicht möglich '{{fileName}}'. {{errorMessage}}", "TITLE_LIGHTBOX_DELETE_ATTACHMENT": "Anhang löschen...", "MSG_LIGHTBOX_DELETE_ATTACHMENT": "der Anhang '{{fileName}}'", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Wert bearbeiten", - "TITLE_ACTION_DELETE_VALUE": "Wert löschen" + "TITLE_ACTION_DELETE_VALUE": "Wert löschen", + "TITLE_ACTION_DELETE_TAG": "Schlagwort löschen" }, "HELP": "Wenn Sie Hilfe benötigen, besuchen Sie unsere Support-Seite!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Module", "ENABLE": "Aktivieren", "DISABLE": "Deaktivieren", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualisieren und verwalten Sie den strategischsten Teil Ihres Projektes", "BACKLOG": "Auftragsliste", "BACKLOG_DESCRIPTION": "Verwalten Sie Ihre User-Stories, um einen organisierten Überblick der anstehenden und priorisierten Aufgaben zu erhalten.", "NUMBER_SPRINTS": "Erwartete Anzahl an Sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Sie sind im Begriff, die CSV data access URL zu ändern. Die vorherige URL wird deaktiviert. Sind Sie sicher?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "User-Stories Berichte", "SECTION_TITLE_TASK": "Aufgabenberichte", "SECTION_TITLE_ISSUE": "Ticket Berichte", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Benutzerfelder", "SUBTITLE": "Spezifizieren Sie die Benutzerfelder für Ihre User-Stories, Aufgaben und Tickets.", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Benutzerdefinierte Felder der User-Story", "US_ADD": "Benutzerdefiniertes Feld bei User-Stories hinzufügen", "TASK_DESCRIPTION": "Aufgaben benutzerdefinierte Felder", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Spezifizieren Sie die Status, die Ihre User-Stories, Aufgaben und Tickets durchlaufen werden.", - "US_TITLE": "User-Story Status", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Aufgaben-Status", "ISSUE_TITLE": "Ticket-Status" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Schlagwörter", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "Es sieht so aus, als konnte zu Ihren Suchkriterien nichts passendes gefunden werden." + "EMPTY_SEARCH": "Es sieht so aus, als konnte zu Ihren Suchkriterien nichts passendes gefunden werden.", + "ACTION_ADD": "Schlagwort hinzufügen", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Rollen - {{projectName}}", @@ -621,7 +699,7 @@ "HEADERS": "Überschriften", "PAYLOAD": "Ladung", "RESPONSE": "Rückmeldung", - "DATE": "DD MMM YYYY [um] hh:mm:ss", + "DATE": "DD. MMM YYYY [um] hh:mm:ss", "ACTION_HIDE_HISTORY": "(Chronik verbergen)", "ACTION_HIDE_HISTORY_TITLE": "Chronik Details verbergen", "ACTION_SHOW_HISTORY": "(Chronik anzeigen)", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "die Einladung an {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Vorgegebener Wert für Punkteauswahl", - "LABEL_US": "Vorgegebener Wert für User-Story-Status Auswahl", "LABEL_TASK_STATUS": "Vorgegebene Auswahl für den Aufgaben-Status", - "LABEL_PRIORITY": "Vorgegebener Wert für Prioritätsauswahl", - "LABEL_SEVERITY": "Vorgegebener Wert für Gewichtungsauswahl", "LABEL_ISSUE_TYPE": "Vorgegebener Wert für Ticketartauswahl", - "LABEL_ISSUE_STATUS": "Vorgegebene Auswahl für den Ticket-Status" + "LABEL_ISSUE_STATUS": "Vorgegebene Auswahl für den Ticket-Status", + "LABEL_PRIORITY": "Vorgegebener Wert für Prioritätsauswahl", + "LABEL_SEVERITY": "Vorgegebener Wert für Gewichtungsauswahl" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Benennen Sie den neuen Status" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Alle anzeigen", "FILTER_TYPE_PROJECTS": "Projekte", "FILTER_TYPE_PROJECT_TITLES": "Nur Projekte anzeigen", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Stories", "FILTER_TYPE_USER_STORIES_TITLES": "Nur User-Stories anzeigen", "FILTER_TYPE_TASKS": "Aufgaben", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Optional) Fügen Sie einen persönlichen Text zur Einladung hinzu. Erzählen Sie Ihren neuen Mitgliedern etwas Schönes. ;-)", "PLACEHOLDER_TYPE_EMAIL": "Geben Sie eine E-Mail ein", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Leider kann dieses Projekt nicht mehr als {{maxMembers}} Mitglieder haben. Wenn Sie die derzeitige Grenze erhöhen möchten, kontaktieren Sie den Administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Leider kann dieses Projekt nicht mehr als {{maxMembers}} Mitglieder haben." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Das Projekt kann nicht ohne einen Projektleiter existieren.", @@ -985,6 +1066,25 @@ "BUTTON": "Fragen Sie dieses Projektmitglied, um Projektleiter zu werden" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - User-Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Abgeschlossen {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} von {{userStoryTotalTasks}} Aufgaben geschlossen). Punkte: {{userStoryPoints}}. Beschreibung: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Dies User-Story wurde angelegt von", "GO_TO_EXTERNAL_REFERENCE": "Zur Quelle wechseln", "BLOCKED": "Diese User-Story wird blockiert", - "PREVIOUS": "Vorherige User-Story", - "NEXT": "nächste User-Story", "TITLE_DELETE_ACTION": "User-Story löschen", "LIGHTBOX_TITLE_BLOKING_US": "Blockiert uns", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} Aufgaben fertiggestellt", @@ -1011,11 +1109,11 @@ "PUBLISH": "Als Gig in Taiga Tribe veröffentlichen", "PUBLISH_INFO": "Weitere Infos", "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", - "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", + "PUBLISHED_AS_GIG": "Story veröffentlicht als Gig in Taiga Tribe", "EDIT_LINK": "Link bearbeiten", "CLOSE": "Schließen", "SYNCHRONIZE_LINK": "mit Taiga Tribe synchronisieren", - "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TITLE": "Brauchen Sie jemanden für diese Aufgabe?", "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" }, "FIELDS": { @@ -1025,15 +1123,15 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comment deleted by {{user}}", + "DELETED_INFO": "Kommentar gelöscht von {{user}}", "TITLE": "Kommentare", - "COMMENTS_COUNT": "{{comments}} Comments", - "ORDER": "Order", - "OLDER_FIRST": "Older first", - "RECENT_FIRST": "Recent first", + "COMMENTS_COUNT": "{{comments}} Kommentare", + "ORDER": "Reihenfolge", + "OLDER_FIRST": "Ältere zuerst", + "RECENT_FIRST": "Letzte zuerst", "COMMENT": "Kommentieren", - "EDIT_COMMENT": "Edit comment", - "EDITED_COMMENT": "Edited:", + "EDIT_COMMENT": "Kommentar bearbeiten", + "EDITED_COMMENT": "Bearbeitet:", "SHOW_HISTORY": "View historic", "TYPE_NEW_COMMENT": "Geben Sie hier einen neuen Kommentar ein", "SHOW_DELETED": "Gelöschten Kommentar anzeigen", @@ -1046,22 +1144,22 @@ }, "ACTIVITY": { "SHOW_ACTIVITY": "Aktivitäten zeigen", - "DATETIME": "DD MMM YYYY HH:mm", + "DATETIME": "DD. MMM YYYY HH:mm", "SHOW_MORE": "+ Vorherige Einträge zeigen ({{showMore}} vorhanden)", "TITLE": "Aktivität", - "ACTIVITIES_COUNT": "{{activities}} Activities", + "ACTIVITIES_COUNT": "{{activities}} Aktivitäten", "REMOVED": "entfernt", "ADDED": "hinzugefügt", - "TAGS_ADDED": "tags added:", - "TAGS_REMOVED": "tags removed:", + "TAGS_ADDED": "Tags hinzugefügt:", + "TAGS_REMOVED": "Tags entfernt:", "US_POINTS": "{{role}} points", - "NEW_ATTACHMENT": "new attachment:", - "DELETED_ATTACHMENT": "deleted attachment:", + "NEW_ATTACHMENT": "neuer Anhang:", + "DELETED_ATTACHMENT": "gelöschter Anhang:", "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", "SIZE_CHANGE": "Machte {size, plural, one{eine Änderung} other{# Änderungen}}", - "BECAME_DEPRECATED": "became deprecated", + "BECAME_DEPRECATED": "ist veraltet", "BECAME_UNDEPRECATED": "became undeprecated", "TEAM_REQUIREMENT": "Team Anforderung", "CLIENT_REQUIREMENT": "Kundenanforderung", @@ -1097,13 +1195,14 @@ "TAGS": "Schlagwörter", "ATTACHMENTS": "Anhänge", "IS_DEPRECATED": "ist veraltet", - "IS_NOT_DEPRECATED": "is not deprecated", + "IS_NOT_DEPRECATED": "ist nicht verworfen", "ORDER": "Befehl", "BACKLOG_ORDER": "Backlog Befehl", "SPRINT_ORDER": "Sprint Befehl", "KANBAN_ORDER": "Kanban Befehl", "TASKBOARD_ORDER": "Taskboard Befehl", - "US_ORDER": "User-Story Befehl" + "US_ORDER": "User-Story Befehl", + "COLOR": "color" } }, "BACKLOG": { @@ -1156,7 +1255,7 @@ "IOCAINE_DOSES": "Iocaine
Dosen", "SHOW_STATISTICS_TITLE": "Statistik anzeigen", "TOGGLE_BAKLOG_GRAPH": "Zeige/Verstecke Burndowngraph", - "POINTS_PER_ROLE": "Points per role" + "POINTS_PER_ROLE": "Points pro Rolle" }, "SUMMARY": { "PROJECT_POINTS": "Projekt
Punkte", @@ -1169,13 +1268,11 @@ "TITLE": "Filter", "REMOVE": "Filter entfernen", "HIDE": "Filter verbergen", - "SHOW": "Filter anzeigen", - "FILTER_CATEGORY_STATUS": "Status", - "FILTER_CATEGORY_TAGS": "Schlagwörter" + "SHOW": "Filter anzeigen" }, "SPRINTS": { "TITLE": "SPRINTS", - "DATE": "DD MMM YYYY", + "DATE": "DD. MMM YYYY", "LINK_TASKBOARD": "Sprint Taskboard", "TITLE_LINK_TASKBOARD": "Gehe zu Taskboard von \"{{name}}\"", "NUMBER_SPRINTS": "
Sprints", @@ -1220,7 +1317,7 @@ "YAXIS_LABEL": "Punkte", "OPTIMAL": "Optimale unerledigte Punkte für Tag {{formattedDate}} sollten sein {{roundedValue}}", "REAL": "Tatsächliche Anzahl unerledigter Punkte für Tag {{formattedDate}} ist {{roundedValue}}", - "DATE": "DD MMMM YYYY" + "DATE": "DD. MMMM YYYY" } }, "TASK": { @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Diese Aufgabe wurde erstellt durch", "TITLE_LINK_GO_ORIGIN": "Zu User-Story wechseln", "BLOCKED": "Diese Aufgabe wird blockiert", - "PREVIOUS": "vorherige Aufgabe", - "NEXT": "nächste Aufgabe", "TITLE_DELETE_ACTION": "Aufgabe löschen", "LIGHTBOX_TITLE_BLOKING_TASK": "Blockierende Aufgabe", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Ticket", "ACTION_NEW_ISSUE": "+ NEUES TICKET", "ACTION_PROMOTE_TO_US": "Zur User-Story aufwerten", - "PLACEHOLDER_FILTER_NAME": "Benennen Sie den Filter und drücken Sie die Eingabetaste", "PROMOTED": "Dieses Ticket wurde aufgewertet zu User-Story:", "EXTERNAL_REFERENCE": "Dieses Ticket wurde erstellt durch", "GO_TO_EXTERNAL_REFERENCE": "Zur Quelle wechseln", "BLOCKED": "Dieses Ticket wird blockiert", - "TITLE_PREVIOUS_ISSUE": "vorheriges Ticket", - "TITLE_NEXT_ISSUE": "nächstes Ticket", "ACTION_DELETE": "Ticket löschen", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blockierendes Ticket", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Dieses Problem zur User-Story aufwerten", "MESSAGE": "Sind Sie sicher, dass Sie aus diesem Ticket eine neue User-Story erstellen möchten?" }, - "FILTERS": { - "TITLE": "Filter", - "INPUT_SEARCH_PLACEHOLDER": "Thema oder ref", - "TITLE_ACTION_SEARCH": "Suche", - "ACTION_SAVE_CUSTOM_FILTER": "Als Benutzerfilter speichern", - "BREADCRUMB": "Filter", - "TITLE_BREADCRUMB": "Filter", - "CATEGORIES": { - "TYPE": "Arten", - "STATUS": "Status", - "SEVERITY": "Gewichtung", - "PRIORITIES": "Prioritäten", - "TAGS": "Schlagwörter", - "ASSIGNED_TO": "Zugeordnet", - "CREATED_BY": "Erstellt durch", - "CUSTOM_FILTERS": "Benutzerfilter" - }, - "CONFIRM_DELETE": { - "TITLE": "Benutzerfilter löschen", - "MESSAGE": "der Benutzerfilter '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Arten", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Suche - {{projectName}}", "PAGE_DESCRIPTION": "Suchen Sie User-Stories, Tickets, Aufgaben oder Wiki Seiten im Projekt {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "User-Stories", "FILTER_ISSUES": "Tickets", "FILTER_TASKS": "Aufgaben", @@ -1464,24 +1535,24 @@ "DELETE_LIGHTBOX_TITLE": "Wiki Seite löschen", "DELETE_LINK_TITLE": "Entferne Wiki Link", "NAVIGATION": { - "HOME": "Main Page", + "HOME": "Hauptseite", "SECTION_NAME": "BOOKMARKS", - "ACTION_ADD_LINK": "Add bookmark", - "ALL_PAGES": "All wiki pages" + "ACTION_ADD_LINK": "Bookmark hinzufügen", + "ALL_PAGES": "Alle Wiki-Seiten" }, "SUMMARY": { "TIMES_EDITED": "mal
bearbeitet", "LAST_EDIT": "letzte
Bearbeitung", "LAST_MODIFICATION": "letzte Änderung" }, - "SECTION_PAGES_LIST": "All pages", + "SECTION_PAGES_LIST": "Alle Seiten", "PAGES_LIST_COLUMNS": { - "TITLE": "Title", + "TITLE": "Titel", "EDITIONS": "Editions", "CREATED": "Erstellt", - "MODIFIED": "Modified", - "CREATOR": "Creator", - "LAST_MODIFIER": "Last modifier" + "MODIFIED": "Geändert", + "CREATOR": "Ersteller", + "LAST_MODIFIER": "Letzter Bearbeiter" } }, "HINTS": { @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} erstellte die neue Aufgabe {{obj_name}} in {{project_name}}, die zur User-Story {{us_name}} gehört", "WIKI_CREATED": "{{username}} erstellte die neue Wiki Seite {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} erstellte den neuen Sprint {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} erstellte das Projekt {{project_name}}", "MILESTONE_UPDATED": "{{username}} aktualisierte den Sprint {{obj_name}}", "US_UPDATED": "{{username}} aktualisierte das Attribut \"{{field_name}}\" der User-Story {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} aktualisierte das Attribut \"{{field_name}}\" der Aufgabe {{obj_name}} von User-Story {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} aktualisierte das Attribut \"{{field_name}}\" der Aufgabe {{obj_name}} die zu der User-Story gehört {{us_name}} zu {{new_value}}", "WIKI_UPDATED": "{{username}} aktualisierte die WIKI Seite {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} schrieb einen Kommentar in der User-Story {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} schrieb einen Kommentar im Ticket {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} schrieb einen Kommentar in der Aufgabe {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} hat ein neues Mitglied", "US_ADDED_MILESTONE": "{{username}} fügte dem Sprint {{sprint_name}} die User-Story {{obj_name}} hinzu", "US_MOVED": "{{username}} wurde in die Story {{obj_name}} verschoben", diff --git a/app/locales/taiga/locale-es.json b/app/locales/taiga/locale-es.json index da2c4135..77c2972e 100644 --- a/app/locales/taiga/locale-es.json +++ b/app/locales/taiga/locale-es.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Un elemento por línea...", "NEW_BULK": "Nueva inserción en bloque", "RELATED_TASKS": "Tareas relacionadas", + "PREVIOUS": "Previous", + "NEXT": "Siguiente", "LOGOUT": "Cerrar sesión", "EXTERNAL_USER": "un usuario externo", "GENERIC_ERROR": "Uno de nuestros Oompa Loompas dice {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "¡Cuidado!. Esta usando mayusculas en un campo sensible a mayusculas", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "¿Seguro que desea cerrar el modo de edición?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Recuerde que si cierra el modo de edicion sin guardar todos los cambios se perderán", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Este valor parece inválido.", "TYPE_EMAIL": "El valor debe ser un email.", @@ -115,6 +122,7 @@ "USER_STORY": "Historia de usuario", "TASK": "Tarea", "ISSUE": "Petición", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "¿Qué soy? Etiquétame...", "DELETE": "Borrar etiqueta", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Se borrarán todos los valores de este atributo personalizado. \n¿Estás seguro de que quieres continuar?" }, "FILTERS": { - "TITLE": "filtros", + "TITLE": "Filtros", "INPUT_PLACEHOLDER": "Asunto o referencia", "TITLE_ACTION_FILTER_BUTTON": "busqueda", - "BREADCRUMB_TITLE": "Regresar a categorias", - "BREADCRUMB_FILTERS": "Filtros", - "BREADCRUMB_STATUS": "estado" + "INPUT_SEARCH_PLACEHOLDER": "Asunto o referencia", + "TITLE_ACTION_SEARCH": "Buscar", + "ACTION_SAVE_CUSTOM_FILTER": "guardar como filtro personalizado", + "PLACEHOLDER_FILTER_NAME": "Escribe un nombre para el filtro y pulsa enter", + "CATEGORIES": { + "TYPE": "Tipo", + "STATUS": "Estado", + "SEVERITY": "Gravedad", + "PRIORITIES": "Prioridades", + "TAGS": "Etiquetas", + "ASSIGNED_TO": "Asignado a", + "CREATED_BY": "Creada por", + "CUSTOM_FILTERS": "Filtros personalizados" + }, + "CONFIRM_DELETE": { + "TITLE": "Eliminar filtros personalizados", + "MESSAGE": "el filtro personalizado '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Título de primer nivel", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Ayuda de sintaxis Markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Ver sprints", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Observando", "DASHBOARD": "Dashboard de proyecto" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "No asignado" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Votos", + "NAME": "Nombre", + "PROJECT": "Proyecto", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Estado", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Bloqueada", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Mis proyectos - Taiga", "PAGE_DESCRIPTION": "Una lista con todos tus proyectos, puedes reordenarla o crear un proyecto nuevo.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Editar valor", - "TITLE_ACTION_DELETE_VALUE": "Eliminar valor" + "TITLE_ACTION_DELETE_VALUE": "Eliminar valor", + "TITLE_ACTION_DELETE_TAG": "Borrar etiqueta" }, "HELP": "¿Necesitas ayuda? ¡Revisa nuestra pagina de soporte! ", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Módulos", "ENABLE": "Activado", "DISABLE": "Desactivado", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Gestiona tus historias de usuario para mantener una vista organizada y priorizada de los próximos trabajos que deberás afrontar. ", "NUMBER_SPRINTS": "Numero esperado de sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Vas a cambiar la url de acceso a los datos en formato CSV. La url anterior se deshabilitará. ¿Estás seguro?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "informes de historias de usuario", "SECTION_TITLE_TASK": "Informes de tareas", "SECTION_TITLE_ISSUE": "informes de peticiones", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Atributos personalizados", "SUBTITLE": "Especifica los atributos personalizados para las historias de usuario, tareas y peticiones", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Atributos personalizados de historias de usuario", "US_ADD": "Añadir un atributo personalizado en las historias de usuario", "TASK_DESCRIPTION": "Atributos personalizados de tareas", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Estado", "SUBTITLE": "Especifica los estado que atravesarán tus historias de usuario, tareas y peticiones", - "US_TITLE": "Estados de historias", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Estados de Tarea", "ISSUE_TITLE": "Estados de la petición" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Etiquetas", - "SUBTITLE": "Ver y editar el color de sus historias", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Actualmente no hay etiquetas", - "EMPTY_SEARCH": "Parece que no se encontro nada con este criterio de busqueda" + "EMPTY_SEARCH": "Parece que no se encontro nada con este criterio de busqueda", + "ACTION_ADD": "Añadir etiqueta", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Roles - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "la invitación enviada a" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Valor por defecto para el selector de puntos", - "LABEL_US": "Valor por defecto para el selector de estado de historia", "LABEL_TASK_STATUS": "Valor por defecto para el selector de estado de tarea", - "LABEL_PRIORITY": "Valor por defecto para el selector de prioridad", - "LABEL_SEVERITY": "Valor por defecto para el selector de gravedad", "LABEL_ISSUE_TYPE": "Valor por defecto para el selector de tipo de la petición", - "LABEL_ISSUE_STATUS": "Valor por defecto para el selector de estado de petición" + "LABEL_ISSUE_STATUS": "Valor por defecto para el selector de estado de petición", + "LABEL_PRIORITY": "Valor por defecto para el selector de prioridad", + "LABEL_SEVERITY": "Valor por defecto para el selector de gravedad" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Escribe un nombre para el nuevo estado" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Mostrar todos", "FILTER_TYPE_PROJECTS": "Proyectos", "FILTER_TYPE_PROJECT_TITLES": "Mostrar sólo proyectos", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Historias", "FILTER_TYPE_USER_STORIES_TITLES": "Mostrar sólo historias de usuario", "FILTER_TYPE_TASKS": "Tareas", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opcional) Añade un texto personalizado a la invitación. Dile algo encantador a tus nuevos miembros ;-)", "PLACEHOLDER_TYPE_EMAIL": "Escribe un email", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Desafortunadamente, este proyecto no puede tener mas de {{maxMembers}} miembros.
Si desea aumentar este limite, por favor contacte al administrador.", - "LIMIT_USERS_WARNING_MESSAGE": "Desafortunadamente, este proyecto no puede tener mas de {{maxMembers}} miembros." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Por desgracia, este proyecto no puede ser dejado sin dueño", @@ -985,6 +1066,25 @@ "BUTTON": "Pregunte a este usuario para convertirlo en el nuero dueño del proyecto" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Historia de Usuario {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{userStoryStatus }}. Completado el {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tareas cerradas). Puntos: {{userStoryPoints}}. Descripción: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Esta historia ha sido creada desde", "GO_TO_EXTERNAL_REFERENCE": "Ir al origen", "BLOCKED": "Esta historia de usuario está bloqueada", - "PREVIOUS": "anterior historia de usuario", - "NEXT": "siguiente historia de usuario", "TITLE_DELETE_ACTION": "Borrar Historia de Usuario", "LIGHTBOX_TITLE_BLOKING_US": "Historia bloqueada", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tareas completadas", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "orden en sprint", "KANBAN_ORDER": "orden en kanban", "TASKBOARD_ORDER": "orden en panel de tareas", - "US_ORDER": "orden en historia" + "US_ORDER": "orden en historia", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtros", "REMOVE": "Borrar Filtros", "HIDE": "Ocultar filtros", - "SHOW": "Ver Filtros", - "FILTER_CATEGORY_STATUS": "Estado", - "FILTER_CATEGORY_TAGS": "Etiquetas" + "SHOW": "Ver Filtros" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Esta tarea pertenece a ", "TITLE_LINK_GO_ORIGIN": "Ir a historia de usuario", "BLOCKED": "Esta tarea está bloqueada", - "PREVIOUS": "tarea anterior", - "NEXT": "tarea siguiente", "TITLE_DELETE_ACTION": "Eliminar Tarea", "LIGHTBOX_TITLE_BLOKING_TASK": "Tarea bloqueada", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Petición", "ACTION_NEW_ISSUE": "+ NUEVA PETICIÓN", "ACTION_PROMOTE_TO_US": "Promover a Historia de Usuario", - "PLACEHOLDER_FILTER_NAME": "Escribe un nombre para el filtro y pulsa enter", "PROMOTED": "Esta petición ha sido promovida a la historia:", "EXTERNAL_REFERENCE": "Esta petición ha sido creada a partir de ", "GO_TO_EXTERNAL_REFERENCE": "Ir al origen", "BLOCKED": "La petición está bloqueada", - "TITLE_PREVIOUS_ISSUE": "petición anterior", - "TITLE_NEXT_ISSUE": "petición siguiente", "ACTION_DELETE": "Borrar petición", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Petición bloqueada", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Promover esta petición a una nueva historia de usuario", "MESSAGE": "¿Está seguro de que desea crear una nueva Historia de Usuario a partir de esta Petición?" }, - "FILTERS": { - "TITLE": "Filtros", - "INPUT_SEARCH_PLACEHOLDER": "Asunto o referencia", - "TITLE_ACTION_SEARCH": "Buscar", - "ACTION_SAVE_CUSTOM_FILTER": "guardar como filtro personalizado", - "BREADCRUMB": "Filtros", - "TITLE_BREADCRUMB": "Filtros", - "CATEGORIES": { - "TYPE": "Tipo", - "STATUS": "Estado", - "SEVERITY": "Gravedad", - "PRIORITIES": "Prioridad", - "TAGS": "Etiquetas", - "ASSIGNED_TO": "Asignado a", - "CREATED_BY": "Creada por", - "CUSTOM_FILTERS": "Filtros personalizados" - }, - "CONFIRM_DELETE": { - "TITLE": "Eliminar filtros personalizados", - "MESSAGE": "el filtro personalizado '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tipo", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Buscar - {{projectName}}", "PAGE_DESCRIPTION": "Busca cualquier cosa: historias de usuario, peticiones, tareas o páginas del wiki en el proyecto {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Historias de Usuario", "FILTER_ISSUES": "Peticiones", "FILTER_TASKS": "Tareas", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} ha creado una nueva tarea {{obj_name}} en {{project_name}} que proviene de la historia {{us_name}}", "WIKI_CREATED": "{{username}} ha creado una nueva página de wiki {{obj_name}} en {{project_name}}\n", "MILESTONE_CREATED": "{{username}} ha creado un nuevo sprint {{obj_name}} en {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} creó el proyecto {{project_name}}", "MILESTONE_UPDATED": "{{username}} ha actualizado el sprint {{obj_name}}", "US_UPDATED": "{{username}} ha actualizado el atributo \"{{field_name}}\" de la historia {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} ha actualizado el atributo \"{{field_name}}\" de la tarea {{obj_name}} que proviene de la historia {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} ha actualizado el atributo \"{{field_name}}\" de la tarea {{obj_name}} que pertenece a la historia {{us_name}} a {{new_value}}", "WIKI_UPDATED": "{{username}} ha actualizado la página del wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} ha añadido un comentado en la historia {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} ha añadido un comentado en la petición {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} ha añadido un comentado en la tarea {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} tiene un nuevo miembro", "US_ADDED_MILESTONE": "{{username}} ha añadido la historia {{obj_name}} a {{sprint_name}}", "US_MOVED": "{{username}} ha movido la historia {{obj_name}}", diff --git a/app/locales/taiga/locale-fi.json b/app/locales/taiga/locale-fi.json index 2492cebc..b8873b04 100644 --- a/app/locales/taiga/locale-fi.json +++ b/app/locales/taiga/locale-fi.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Yksi riviä kohti...", "NEW_BULK": "Lisää monta", "RELATED_TASKS": "Liittyvät tehtävät", + "PREVIOUS": "Previous", + "NEXT": "Seuraava", "LOGOUT": "Kirjaudu ulos", "EXTERNAL_USER": "ulkoinen käyttäjä", "GENERIC_ERROR": "Oompa Loompas havaitsivat virheen {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Tämä arvo vaikuttaa virheelliseltä.", "TYPE_EMAIL": "Tämän pitäisi olla toimiva sähköpostiosoite.", @@ -115,6 +122,7 @@ "USER_STORY": "Käyttäjätarina", "TASK": "Task", "ISSUE": "Issue", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Anna avainsana...", "DELETE": "Poista avainsana", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "suodattimet", + "TITLE": "Suodattimet", "INPUT_PLACEHOLDER": "Aihe tai viittaus", "TITLE_ACTION_FILTER_BUTTON": "hae", - "BREADCRUMB_TITLE": "takaisin kategorioihin", - "BREADCRUMB_FILTERS": "Suodattimet", - "BREADCRUMB_STATUS": "tila" + "INPUT_SEARCH_PLACEHOLDER": "Otsikko tai viittaus", + "TITLE_ACTION_SEARCH": "Hae", + "ACTION_SAVE_CUSTOM_FILTER": "tallenna omaksi suodattimeksi", + "PLACEHOLDER_FILTER_NAME": "Anna suodattimen nimi ja paina enter", + "CATEGORIES": { + "TYPE": "Tyyppi", + "STATUS": "Tila", + "SEVERITY": "Vakavuus", + "PRIORITIES": "Kiireellisyydet", + "TAGS": "Avainsanat", + "ASSIGNED_TO": "Tekijä", + "CREATED_BY": "Luoja", + "CUSTOM_FILTERS": "Omat suodattimet" + }, + "CONFIRM_DELETE": { + "TITLE": "Poista oma suodatin", + "MESSAGE": "oma suodatin '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Päätason otsikko", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Merkintätavan ohjeet" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Kierrokset", "VIEW_SPRINTS": "Katso kierroksia", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Watching", "DASHBOARD": "Projects Dashboard" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Tekijä puuttuu" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Ääniä", + "NAME": "Nimi", + "PROJECT": "Projekti", + "SPRINT": "Kierros", + "ASSIGNED_TO": "Assigned", + "STATUS": "Tila", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Suljettu", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "My projects - Taiga", "PAGE_DESCRIPTION": "A list with all your projects, you can reorder or create a new one.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Muokkaa arvoa", - "TITLE_ACTION_DELETE_VALUE": "Poista arvo" + "TITLE_ACTION_DELETE_VALUE": "Poista arvo", + "TITLE_ACTION_DELETE_TAG": "Poista avainsana" }, "HELP": "Tarvitsetko apua? Katso tukisivuilta.", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Modulit", "ENABLE": "Aktivoi", "DISABLE": "Passivoi", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Odottavat", "BACKLOG_DESCRIPTION": "Hallinnoi käyttäjätarinoita: järjestele ja priorisoi työtä.", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Jos muutata CSV-datan URLia, edellien lakkaa toimimasta. Oletko varma?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "käyttäjätarinoiden raportit", "SECTION_TITLE_TASK": "tehtävien raportit", "SECTION_TITLE_ISSUE": "pyyntöjen raportit", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Omat kentät", "SUBTITLE": "Määritele omia kenttiä käyttäjätarinoihin, tehtäviin ja pyytöihin", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Käyttäjätarinoiden omat kentät", "US_ADD": "Lisää käyttäjätarinoihin oma kenttä", "TASK_DESCRIPTION": "Tehtävien omat kentät", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Tila", "SUBTITLE": "Määrittele tilat joiden kautta käyttäjätarinasi, tehtäväsi ja pyyntösi kulkevat", - "US_TITLE": "Kt tilat", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Tehtävien tilat", "ISSUE_TITLE": "Pyyntöjen tilat" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Avainsanat", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Lisää avainsana", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Roles - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "kutsu sähköpostiin {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Oletukset pisteiden 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": "Oletukset vakavuudeksi", "LABEL_ISSUE_TYPE": "Oletukset pyyntöjen tyypeiksi", - "LABEL_ISSUE_STATUS": "Oletukset pyyntöjen statuksiksi" + "LABEL_ISSUE_STATUS": "Oletukset pyyntöjen statuksiksi", + "LABEL_PRIORITY": "Oletus arvo tärkeyden valiintaan", + "LABEL_SEVERITY": "Oletukset vakavuudeksi" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Anna uuden tilan nimi" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Show all", "FILTER_TYPE_PROJECTS": "Projektit", "FILTER_TYPE_PROJECT_TITLES": "Show only projects", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Stories", "FILTER_TYPE_USER_STORIES_TITLES": "Show only user stories", "FILTER_TYPE_TASKS": "Tehtävät", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Vapaaehtoinen) Lisää oma kuvaus kutsuusi uusille jäsenille ;-)", "PLACEHOLDER_TYPE_EMAIL": "Anna sähköposti", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completed {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Points: {{userStoryPoints}}. Description: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Tämä Kt oon luotu täältä: ", "GO_TO_EXTERNAL_REFERENCE": "Palaa alkuun", "BLOCKED": "Tämä käyttäjätarina on suljettu", - "PREVIOUS": "edellinen käyttäjätarina", - "NEXT": "seuraava käyttäjätarina", "TITLE_DELETE_ACTION": "Poista käyttäjätarina", "LIGHTBOX_TITLE_BLOKING_US": "Meitä estää", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tehtyä tehtävää", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "kierroksen järjestys", "KANBAN_ORDER": "kanban järjestys", "TASKBOARD_ORDER": "Tehtävätaulun järjestys", - "US_ORDER": "kt järjestys" + "US_ORDER": "kt järjestys", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Suodattimet", "REMOVE": "Poista suodattimet", "HIDE": "Piilota suodattimet", - "SHOW": "Näytä suodattimet", - "FILTER_CATEGORY_STATUS": "Tila", - "FILTER_CATEGORY_TAGS": "Avainsanat" + "SHOW": "Näytä suodattimet" }, "SPRINTS": { "TITLE": "KIERROKSET", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Tämä tehtävä on luotu", "TITLE_LINK_GO_ORIGIN": "Siirry käyttäjätarinaan", "BLOCKED": "Tämä tehtävä on suljettu", - "PREVIOUS": "edellinen tehtävä", - "NEXT": "seuraava tehtävä", "TITLE_DELETE_ACTION": "Poista tehtävä", "LIGHTBOX_TITLE_BLOKING_TASK": "Estävä tehtävä", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ UUSI PYYNTÖ", "ACTION_PROMOTE_TO_US": "Liitä käyttäjätarinaan", - "PLACEHOLDER_FILTER_NAME": "Anna suodattimen nimi ja paina enter", "PROMOTED": "Tämä pyyntö on liitetty Kthen:", "EXTERNAL_REFERENCE": "Tämä pyyntö on luotu täältä:", "GO_TO_EXTERNAL_REFERENCE": "Palaa alkuun", "BLOCKED": "Tämä pyyntö on estetty", - "TITLE_PREVIOUS_ISSUE": "edellinen pyyntö", - "TITLE_NEXT_ISSUE": "seuraava pyyntö", "ACTION_DELETE": "Poista pyyntö", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Estävä pyyntö", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Liitä tämä pyyntö uuteen käyttäjätarinaan", "MESSAGE": "Haluatko varmasti lisätä uuden käyttäjätarinan tästä pyynnöstä?" }, - "FILTERS": { - "TITLE": "Suodattimet", - "INPUT_SEARCH_PLACEHOLDER": "Otsikko tai viittaus", - "TITLE_ACTION_SEARCH": "Hae", - "ACTION_SAVE_CUSTOM_FILTER": "tallenna omaksi suodattimeksi", - "BREADCRUMB": "Suodattimet", - "TITLE_BREADCRUMB": "Suodattimet", - "CATEGORIES": { - "TYPE": "Tyyppi", - "STATUS": "Tila", - "SEVERITY": "Vakavuus", - "PRIORITIES": "Tärkeydet", - "TAGS": "Avainsanat", - "ASSIGNED_TO": "Tekijä", - "CREATED_BY": "Luoja", - "CUSTOM_FILTERS": "Omat suodattimet" - }, - "CONFIRM_DELETE": { - "TITLE": "Poista oma suodatin", - "MESSAGE": "oma suodatin '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tyyppi", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Search - {{projectName}}", "PAGE_DESCRIPTION": "Search anything, user stories, issues, tasks or wiki pages, in the project {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Käyttäjätarinat", "FILTER_ISSUES": "Pyynnöt", "FILTER_TASKS": "Tehtävät", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} has created a new task {{obj_name}} in {{project_name}} which belongs to the US {{us_name}}", "WIKI_CREATED": "{{username}} has created a new wiki page {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} has created a new sprint {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} created the project {{project_name}}", "MILESTONE_UPDATED": "{{username}} has updated the sprint {{obj_name}}", "US_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the US {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the task {{obj_name}} which belongs to the US {{us_name}} to {{new_value}}", "WIKI_UPDATED": "{{username}} has updated the wiki page {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "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_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} has a new member", "US_ADDED_MILESTONE": "{{username}} has added the US {{obj_name}} to {{sprint_name}}", "US_MOVED": "{{username}} has moved the US {{obj_name}}", diff --git a/app/locales/taiga/locale-fr.json b/app/locales/taiga/locale-fr.json index 7040e1b2..dfbff49f 100644 --- a/app/locales/taiga/locale-fr.json +++ b/app/locales/taiga/locale-fr.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Un élément par ligne...", "NEW_BULK": "Nouvel ajout en bloc", "RELATED_TASKS": "Tâches associées", + "PREVIOUS": "Précédent", + "NEXT": "Suivant", "LOGOUT": "Déconnexion", "EXTERNAL_USER": "un utilisateur externe", "GENERIC_ERROR": "L'un de nos Oompa Loompas dit {{error}}.", @@ -43,8 +45,13 @@ "TEAM_REQUIREMENT": "Un besoin projet est un besoin qui est nécessaire au projet mais qui ne doit avoir aucun impact pour le client", "OWNER": "Propriétaire du Projet", "CAPSLOCK_WARNING": "Attention ! Vous utilisez des majuscules dans un champ qui est sensible à la casse.", - "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", - "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Êtes-vous sûr de vouloir fermer le mode Édition ?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Souvenez-vous que si vous fermez le mode édition sans enregistrer, toutes vos modifications seront perdues", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Affecter à", + "EDIT": "Modifier la carte" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Cette valeur semble être invalide.", "TYPE_EMAIL": "Cette valeur devrait être une adresse courriel valide.", @@ -115,6 +122,7 @@ "USER_STORY": "Récit utilisateur", "TASK": "Tâche", "ISSUE": "Ticket", + "EPIC": "Épopée", "TAGS": { "PLACEHOLDER": "Taggez moi !", "DELETE": "Supprimer le mot-clé", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Souvenez-vous que toutes les valeurs de ce champ personnalisé vont être effacées.\nEtes-vous sûr de vouloir continuer ?" }, "FILTERS": { - "TITLE": "filtres", + "TITLE": "Filtres", "INPUT_PLACEHOLDER": "Objet ou référence", "TITLE_ACTION_FILTER_BUTTON": "recherche", - "BREADCRUMB_TITLE": "retour aux catégories", - "BREADCRUMB_FILTERS": "Filtres", - "BREADCRUMB_STATUS": "état" + "INPUT_SEARCH_PLACEHOLDER": "Objet ou réf.", + "TITLE_ACTION_SEARCH": "Rechercher", + "ACTION_SAVE_CUSTOM_FILTER": "sauvegarder en tant que filtre personnalisé", + "PLACEHOLDER_FILTER_NAME": "Écrivez le nom du filtre et appuyez sur \"Entrée\"", + "CATEGORIES": { + "TYPE": "Type", + "STATUS": "Statut", + "SEVERITY": "Gravité", + "PRIORITIES": "Priorités", + "TAGS": "Mots-clés", + "ASSIGNED_TO": "Affecté à", + "CREATED_BY": "Créé par", + "CUSTOM_FILTERS": "Filtres personnalisés" + }, + "CONFIRM_DELETE": { + "TITLE": "Supprime le filtre personnalisé", + "MESSAGE": "le filtre personnalisé '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Premier niveau de titre", @@ -227,11 +250,19 @@ "CODE_BLOCK_SAMPLE_TEXT": "Votre texte ici…", "PREVIEW_BUTTON": "Aperçu", "EDIT_BUTTON": "Modifier", - "ATTACH_FILE_HELP": "Attach files by dragging & dropping on the textarea above.", - "ATTACH_FILE_HELP_SAVE_FIRST": "Save first before if you want to attach files by dragging & dropping on the textarea above.", + "ATTACH_FILE_HELP": "Joindre des fichiers en glissant et déposant ceux-ci sur la zone de texte ci-dessus.", + "ATTACH_FILE_HELP_SAVE_FIRST": "Enregistrez d'abord si vous voulez joindre des fichiers en glissant et déposant ceux-ci sur la zone de texte ci-dessus.", "MARKDOWN_HELP": "Aide sur la syntaxe Markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Voir les sprints", @@ -244,7 +275,7 @@ "VIEW_USER_STORIES": "Afficher les récits utilisateur", "ADD_USER_STORIES": "Ajouter des récits utilisateur", "MODIFY_USER_STORIES": "Modifier les récits utilisateur", - "COMMENT_USER_STORIES": "Comment user stories", + "COMMENT_USER_STORIES": "Commenter les histoires utilisateur", "DELETE_USER_STORIES": "Supprimer des récits utilisateur" }, "TASKS": { @@ -252,7 +283,7 @@ "VIEW_TASKS": "Voir les tâches", "ADD_TASKS": "Ajouter des tâches", "MODIFY_TASKS": "Modifier des tâches", - "COMMENT_TASKS": "Comment tasks", + "COMMENT_TASKS": "Commenter les tâches", "DELETE_TASKS": "Supprimer des tâches" }, "ISSUES": { @@ -260,7 +291,7 @@ "VIEW_ISSUES": "Voir les tickets", "ADD_ISSUES": "Ajouter des tickets", "MODIFY_ISSUES": "Modifier des tickets", - "COMMENT_ISSUES": "Comment issues", + "COMMENT_ISSUES": "Commenter les tickets", "DELETE_ISSUES": "Supprimer des tickets" }, "WIKI": { @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Suivi", "DASHBOARD": "Tableau de bord des projets" }, + "EPICS": { + "TITLE": "ÉPOPÉES", + "SECTION_NAME": "Epics", + "EPIC": "ÉPOPÉE", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ AJOUTER ÉPOPÉE", + "UNASSIGNED": "Non affecté" + }, + "EMPTY": { + "TITLE": "On dirait que vous n'avez pas encore créé d'épopée", + "EXPLANATION": "Créer une épopée pour avoir un niveau au dessus des histoires utilisateur. Les épopées peuvent contenir ou être composées d'histoires utilisateur dans ce projet, ou dans tout autre projet.", + "HELP": "En savoir plus sur les épopées" + }, + "TABLE": { + "VOTES": "Votes", + "NAME": "Nom", + "PROJECT": "Projet", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Affecté", + "STATUS": "Statut", + "PROGRESS": "Avancement", + "VIEW_OPTIONS": "Voir les options" + }, + "CREATE": { + "TITLE": "Nouvelle épopée", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Besoin projet", + "CLIENT_REQUIREMENT": "Besoin client", + "BLOCKED": "Bloqué", + "BLOCKED_NOTE_PLACEHOLDER": "Pourquoi cette épopée est-elle bloquée ?", + "CREATE_EPIC": "Créer une épopée" + } + }, "PROJECTS": { "PAGE_TITLE": "Mes projets - Taiga", "PAGE_DESCRIPTION": "Une liste de tous vos projets, vous pouvez la trier ou en créer une nouvelle.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Modifier la valeur", - "TITLE_ACTION_DELETE_VALUE": "Supprimer" + "TITLE_ACTION_DELETE_VALUE": "Supprimer", + "TITLE_ACTION_DELETE_TAG": "Supprimer le mot-clé" }, "HELP": "Avez-vous besoin d'aide ? Consultez notre page d'assistance !", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Modules", "ENABLE": "Activer", "DISABLE": "Désactiver", + "EPICS": "Épopées", + "EPICS_DESCRIPTION": "Visualiser et gérer les aspects les plus stratégiques de votre projet", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Gérez votre récits utilisateur pour garder une vue organisée des travaux à venir et priorisés.", "NUMBER_SPRINTS": "Nombre prévu de sprints", @@ -501,6 +570,7 @@ "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. Êtes-vous sûr ?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "rapports des récits utilisateur", "SECTION_TITLE_TASK": "rapports des tâches", "SECTION_TITLE_ISSUE": "Rapports des tickets", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Champs personnalisés", "SUBTITLE": "Spécifiez les champs personnalisés de vos récits utilisateur, tâches et tickets", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Champs personnalisés des récits utilisateur", "US_ADD": "Ajouter un champ personnalisé dans les récits utilisateur", "TASK_DESCRIPTION": "Champs personnalisés de tâches", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Statut", "SUBTITLE": "Spécifiez les statuts que vont prendre vos récits utilisateur, tâches et tickets", - "US_TITLE": "Statuts des RU", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Statuts des tâches", "ISSUE_TITLE": "Statuts des Tickets" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Mots-clés", - "SUBTITLE": "View and edit the color of your user stories", - "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "SUBTITLE": "Voir et modifier la couleur de vos mots-clés", + "EMPTY": "Il n'y a pas de mots-clés pour l'instant", + "EMPTY_SEARCH": "Il semble qu'aucun résultat ne correspond à vos critères de recherche", + "ACTION_ADD": "Ajouter un mot-clé", + "NEW_TAG": "Nouveau mot-clé", + "MIXING_HELP_TEXT": "Sélectionnez les mots-clés que vous voulez fusionner", + "MIXING_MERGE": "Fusionner des mots-clés", + "SELECTED": "Sélectionné" }, "ROLES": { "PAGE_TITLE": "Rôles - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "l'invitation à {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Valeur par défaut pour la sélection des points", - "LABEL_US": "Valeur par défaut pour la sélection du récit utilisateur", "LABEL_TASK_STATUS": "Valeur par défaut pour la sélection de l'état des tâches", - "LABEL_PRIORITY": "Valeur par défaut de la sélection des priorités", - "LABEL_SEVERITY": "Valeur par défaut pour le sélecteur de gravité", "LABEL_ISSUE_TYPE": "Valeur par défaut pour le sélecteur de type", - "LABEL_ISSUE_STATUS": "Valeur par défaut pour le sélecteur de statut de bug" + "LABEL_ISSUE_STATUS": "Valeur par défaut pour le sélecteur de statut de bug", + "LABEL_PRIORITY": "Valeur par défaut de la sélection des priorités", + "LABEL_SEVERITY": "Valeur par défaut pour le sélecteur de gravité" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Entrez le nom du nouvel état" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Voir tous", "FILTER_TYPE_PROJECTS": "Projets", "FILTER_TYPE_PROJECT_TITLES": "Voir uniquement les projets", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Récits", "FILTER_TYPE_USER_STORIES_TITLES": "Voir uniquement les user stories", "FILTER_TYPE_TASKS": "Tâches", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Optionnel) Ajoutez un texte personnalisé à l'invitation. Dites quelque chose de gentil à vos nouveaux membres ;-)", "PLACEHOLDER_TYPE_EMAIL": "Saisissez une adresse courriel", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Désolé, ce projet ne peut avoir plus de {{maxMembers}} membres.
Si vous désirez augmenter cette limite, merci de contacter l'administrateur.", - "LIMIT_USERS_WARNING_MESSAGE": "Désolé, ce projet ne peut avoir plus de {{maxMembers}} membres." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Malheureusement, ce projet ne peut pas être laissé sans propriétaire", @@ -985,6 +1066,25 @@ "BUTTON": "Demander à ce membre du projet de devenir le nouveau propriétaire" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Récit utilisateur {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "État : {{userStoryStatus }}. Achevé {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} sur {{userStoryTotalTasks}} tâches fermées). Points : {{userStoryPoints}}. Description : {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Ce récit utilisateur a été créé depuis", "GO_TO_EXTERNAL_REFERENCE": "Allez à l'origine", "BLOCKED": "Ce récit utilisateur est bloqué", - "PREVIOUS": "récit utilisateur précédent", - "NEXT": "récit utilisateur suivant", "TITLE_DELETE_ACTION": "Supprimer le récit utilisateur", "LIGHTBOX_TITLE_BLOKING_US": "Bloque le RU", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tâches complétées", @@ -1012,10 +1110,10 @@ "PUBLISH_INFO": "Plus d'informations", "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", - "EDIT_LINK": "Edit link", - "CLOSE": "Close", + "EDIT_LINK": "Modifier le lien", + "CLOSE": "Fermer", "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", - "PUBLISH_MORE_INFO_TITLE": "Do you need somebody for this task?", + "PUBLISH_MORE_INFO_TITLE": "Avez-vous besoin de quelqu'un pour cette tâche ?", "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" }, "FIELDS": { @@ -1025,16 +1123,16 @@ } }, "COMMENTS": { - "DELETED_INFO": "Comment deleted by {{user}}", + "DELETED_INFO": "Commentaire supprimé par {{user}}", "TITLE": "Commentaires", - "COMMENTS_COUNT": "{{comments}} Comments", - "ORDER": "Order", - "OLDER_FIRST": "Older first", - "RECENT_FIRST": "Recent first", + "COMMENTS_COUNT": "{{comments}} commentaires", + "ORDER": "Trier", + "OLDER_FIRST": "Plus ancien d'abord", + "RECENT_FIRST": "Plus récent d'abord", "COMMENT": "Commentaire", - "EDIT_COMMENT": "Edit comment", - "EDITED_COMMENT": "Edited:", - "SHOW_HISTORY": "View historic", + "EDIT_COMMENT": "Modifier le commentaire", + "EDITED_COMMENT": "Modifié :", + "SHOW_HISTORY": "Voir l'historique", "TYPE_NEW_COMMENT": "Entrez un nouveau commentaire ici", "SHOW_DELETED": "Afficher le commentaire supprimé", "HIDE_DELETED": "Cacher le commentaire supprimé", @@ -1049,20 +1147,20 @@ "DATETIME": "DD MMM YYYY HH:mm", "SHOW_MORE": "+ Montrer les entrées précédentes ({{showMore}} plus)", "TITLE": "Activité", - "ACTIVITIES_COUNT": "{{activities}} Activities", + "ACTIVITIES_COUNT": "{{activities}} activités", "REMOVED": "supprimé", "ADDED": "ajouté", - "TAGS_ADDED": "tags added:", - "TAGS_REMOVED": "tags removed:", + "TAGS_ADDED": "Mots-clés ajoutés :", + "TAGS_REMOVED": "Mots-clés supprimés", "US_POINTS": "{{role}} points", - "NEW_ATTACHMENT": "new attachment:", - "DELETED_ATTACHMENT": "deleted attachment:", - "UPDATED_ATTACHMENT": "updated attachment ({{filename}}):", - "CREATED_CUSTOM_ATTRIBUTE": "created custom attribute", - "UPDATED_CUSTOM_ATTRIBUTE": "updated custom attribute", + "NEW_ATTACHMENT": "Nouvelle pièce jointe", + "DELETED_ATTACHMENT": "Pièces jointes supprimées :", + "UPDATED_ATTACHMENT": "Pièces jointes mises à jour ({{filename}}) :", + "CREATED_CUSTOM_ATTRIBUTE": "Attribut personnalisé créé", + "UPDATED_CUSTOM_ATTRIBUTE": "Attribut personnalisé mis à jour", "SIZE_CHANGE": "A fait {size, plural, one{une modification} other{# modifications}}", - "BECAME_DEPRECATED": "became deprecated", - "BECAME_UNDEPRECATED": "became undeprecated", + "BECAME_DEPRECATED": "devenu obsolète", + "BECAME_UNDEPRECATED": "n'est plus obsolète", "TEAM_REQUIREMENT": "Besoin projet", "CLIENT_REQUIREMENT": "Besoin client", "BLOCKED": "Bloqué", @@ -1097,13 +1195,14 @@ "TAGS": "mots-clés", "ATTACHMENTS": "pièces jointes", "IS_DEPRECATED": "est obsolète", - "IS_NOT_DEPRECATED": "is not deprecated", + "IS_NOT_DEPRECATED": "n'est pas obsolète", "ORDER": "classement", "BACKLOG_ORDER": "classement du backlog", "SPRINT_ORDER": "classement du sprint", "KANBAN_ORDER": "Classement du Kanban", "TASKBOARD_ORDER": "trier le tableau des tâches", - "US_ORDER": "classement des récits utilisateur" + "US_ORDER": "classement des récits utilisateur", + "COLOR": "color" } }, "BACKLOG": { @@ -1156,7 +1255,7 @@ "IOCAINE_DOSES": "doses
de iocaine", "SHOW_STATISTICS_TITLE": "Afficher les statistiques", "TOGGLE_BAKLOG_GRAPH": "Afficher/masquer le graphique d'avancement", - "POINTS_PER_ROLE": "Points per role" + "POINTS_PER_ROLE": "Points par rôle" }, "SUMMARY": { "PROJECT_POINTS": "projet
points", @@ -1169,9 +1268,7 @@ "TITLE": "Filtres", "REMOVE": "Supprimer les filtres", "HIDE": "Cacher les filtres", - "SHOW": "Afficher les filtres", - "FILTER_CATEGORY_STATUS": "Etat", - "FILTER_CATEGORY_TAGS": "Mots-clés" + "SHOW": "Afficher les filtres" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Cette tâche a été créée par", "TITLE_LINK_GO_ORIGIN": "Aller au récit utilisateur", "BLOCKED": "Cette tâche est bloquée", - "PREVIOUS": "tâche précédente", - "NEXT": "tâche suivante", "TITLE_DELETE_ACTION": "Supprimer une tâche", "LIGHTBOX_TITLE_BLOKING_TASK": "Tâche bloquante", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Ticket", "ACTION_NEW_ISSUE": "+ NOUVEAU TICKET", "ACTION_PROMOTE_TO_US": "Promouvoir en récit utilisateur", - "PLACEHOLDER_FILTER_NAME": "Écrivez le nom du filtre et appuyez sur \"Entrée\"", "PROMOTED": "Le ticket a été promu en récit utilisateur", "EXTERNAL_REFERENCE": "Ce ticket a été créé à partir de", "GO_TO_EXTERNAL_REFERENCE": "Aller à l'origine", "BLOCKED": "Ce bug est bloqué", - "TITLE_PREVIOUS_ISSUE": "ticket précédent", - "TITLE_NEXT_ISSUE": "ticket suivant", "ACTION_DELETE": "Supprimer le ticket", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Ticket bloquant", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Promouvoir ce ticket en nouveau récit utilisateur", "MESSAGE": "Êtes-vous sure de vouloir créer un nouveau récit utilisateur à partir de ce ticket ?" }, - "FILTERS": { - "TITLE": "Filtres", - "INPUT_SEARCH_PLACEHOLDER": "Objet ou réf.", - "TITLE_ACTION_SEARCH": "Rechercher", - "ACTION_SAVE_CUSTOM_FILTER": "sauvegarder en tant que filtre personnalisé", - "BREADCRUMB": "Filtres", - "TITLE_BREADCRUMB": "Filtres", - "CATEGORIES": { - "TYPE": "Type", - "STATUS": "Statut", - "SEVERITY": "Gravité", - "PRIORITIES": "Priorités", - "TAGS": "Mots-clés", - "ASSIGNED_TO": "Affecté à", - "CREATED_BY": "Créé par", - "CUSTOM_FILTERS": "Filtres personnalisés" - }, - "CONFIRM_DELETE": { - "TITLE": "Supprime le filtre personnalisé", - "MESSAGE": "le filtre personnalisé '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Type", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Chercher - {{projectName}}", "PAGE_DESCRIPTION": "Chercher tout, récits utilisateurs, tickets, tâches ou pages de wiki, dans le projet {{projectName}} : {{projectDescription}}", + "FILTER_EPICS": "Épopées", "FILTER_USER_STORIES": "Récits utilisateur", "FILTER_ISSUES": "Tickets", "FILTER_TASKS": "Tâches", @@ -1464,24 +1535,24 @@ "DELETE_LIGHTBOX_TITLE": "Supprimer la page Wiki", "DELETE_LINK_TITLE": "Supprimer un lien Wiki", "NAVIGATION": { - "HOME": "Main Page", - "SECTION_NAME": "BOOKMARKS", - "ACTION_ADD_LINK": "Add bookmark", - "ALL_PAGES": "All wiki pages" + "HOME": "Page principale", + "SECTION_NAME": "SIGNETS", + "ACTION_ADD_LINK": "Ajouter un signet", + "ALL_PAGES": "Toutes les pages wiki" }, "SUMMARY": { "TIMES_EDITED": "modifications", "LAST_EDIT": "dernière
modification", "LAST_MODIFICATION": "dernière modification" }, - "SECTION_PAGES_LIST": "All pages", + "SECTION_PAGES_LIST": "Toutes les pages", "PAGES_LIST_COLUMNS": { - "TITLE": "Title", - "EDITIONS": "Editions", + "TITLE": "Titre", + "EDITIONS": "Modifications", "CREATED": "Créé le", - "MODIFIED": "Modified", - "CREATOR": "Creator", - "LAST_MODIFIER": "Last modifier" + "MODIFIED": "Modifié", + "CREATOR": "Créateur", + "LAST_MODIFIER": "Dernier modificateur" } }, "HINTS": { @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} a créé une nouvelle tâche {{obj_name}} dans le projet {{project_name}} pour le récit utilisateur {{us_name}}", "WIKI_CREATED": "{{username}} a créé une nouvelle page wiki {{obj_name}} dans {{project_name}}", "MILESTONE_CREATED": "{{username}} a créé un nouveau sprint {{obj_name}} dans {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} a créé le projet {{project_name}}", "MILESTONE_UPDATED": "{{username}} a mis à jour le sprint {{obj_name}}", "US_UPDATED": "{{username}} a mis à jour l'attribut «{{field_name}}» du récit utilisateur {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} a mis à jour l'attribut «{{field_name}}» de la tâche {{obj_name}} qui appartient au récit utilisateur {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} a mis l'attribut \"{{field_name}}\" à {{new_value}} pour la tâche {{obj_name}} appartenant au récit utilisateur {{us_name}}", "WIKI_UPDATED": "{{username}} a mis à jour la page wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} a commenté le récit utilisateur {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} a commenté le ticket {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} a commenté la tâche {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} a un nouveau membre", "US_ADDED_MILESTONE": "{{username}} a ajouté le récit utilisateur {{obj_name}} à {{sprint_name}}", "US_MOVED": "{{username}} a déplacé le RU {{obj_name}}", diff --git a/app/locales/taiga/locale-it.json b/app/locales/taiga/locale-it.json index cbf22204..05561ca3 100644 --- a/app/locales/taiga/locale-it.json +++ b/app/locales/taiga/locale-it.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Un elemento per riga...", "NEW_BULK": "Nuovo inserimento nel carico", "RELATED_TASKS": "Compiti correlati", + "PREVIOUS": "Previous", + "NEXT": "Successivo", "LOGOUT": "Esci", "EXTERNAL_USER": "un utente esterno", "GENERIC_ERROR": "C'é uno dei nostri Oompa Loompa che dice {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Questo valore non è valido.", "TYPE_EMAIL": "Questo valore dovrebbe corrispondere ad una mail valida", @@ -115,6 +122,7 @@ "USER_STORY": "Storia utente", "TASK": "Compito", "ISSUE": "Problema", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Eccomi! taggami", "DELETE": "Elimina tag", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Ricorda che tutti i valori in questo campo custom andranno persi.\nSei sicuro di voler continuare?" }, "FILTERS": { - "TITLE": "filtri", + "TITLE": "Filtri", "INPUT_PLACEHOLDER": "Soggetto o referenza", "TITLE_ACTION_FILTER_BUTTON": "cerca", - "BREADCRUMB_TITLE": "Indietro alle categorie", - "BREADCRUMB_FILTERS": "Filtri", - "BREADCRUMB_STATUS": "stato" + "INPUT_SEARCH_PLACEHOLDER": "Soggetto o referenza", + "TITLE_ACTION_SEARCH": "Cerca", + "ACTION_SAVE_CUSTOM_FILTER": "salva come filtro personalizzato", + "PLACEHOLDER_FILTER_NAME": "Scrivi il nome del filtro e premi invio", + "CATEGORIES": { + "TYPE": "Tipo", + "STATUS": "Stato", + "SEVERITY": "Gravità", + "PRIORITIES": "Priorità", + "TAGS": "Tag", + "ASSIGNED_TO": "Assegnato a", + "CREATED_BY": "Creato da", + "CUSTOM_FILTERS": "Filtri personalizzati" + }, + "CONFIRM_DELETE": { + "TITLE": "Elimina il filtro personalizzato", + "MESSAGE": "Il filtro personalizzato '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Intestazione di primo livello", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Aiuto per la sintassi Markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Vedi gli sprint", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Osservando", "DASHBOARD": "Dashboard Progetti" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Non assegnato" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Voti", + "NAME": "Nome", + "PROJECT": "Progetto", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Stato", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Bloccato", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "I miei progetti - Taiga", "PAGE_DESCRIPTION": "Una lista di tutti i tuoi progetti, la puoi riordinare o crearne una nuova.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Modifica valore", - "TITLE_ACTION_DELETE_VALUE": "Elimina valore" + "TITLE_ACTION_DELETE_VALUE": "Elimina valore", + "TITLE_ACTION_DELETE_TAG": "Elimina tag" }, "HELP": "Hai bisogno di aiuto? Controlla la nostra pagina di supporto!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Moduli", "ENABLE": "Abilita", "DISABLE": "Disabilita", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Amministra le storie degli utenti per mantenere una visione organizzata dei lavori in arrivo e di quelli ad alta priorità ", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Stai per modificare l'url di accesso al CSV. il precedente url verrá disabilitato. Sei sicuro?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "Report delle storie utente", "SECTION_TITLE_TASK": "Analisi dei compiti", "SECTION_TITLE_ISSUE": "Report criticitá", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Campi Personalizzati", "SUBTITLE": "Specifica i campi personalizzati per le tue Storie Utente, compiti e problemi", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Campi personalizzati delle storie utente", "US_ADD": "Aggiungi un campo personalizzato nelle storie utente", "TASK_DESCRIPTION": "Campi personalizzati dei Compiti", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Stato", "SUBTITLE": "Specifica lo stato delle storie utente, i compiti e i problemi saranno affrontati", - "US_TITLE": "Stato delle storie utente", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Stato dei compiti", "ISSUE_TITLE": "Stato dei problemi" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Tag", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Aggiungi un tag", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Ruoli - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "L'invito a {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Valore standard per punti di selezione", - "LABEL_US": "Valore predefinito per la selezione di stati delle storie utente", "LABEL_TASK_STATUS": "Valore predefinito per la selezione degli stati del compito", - "LABEL_PRIORITY": "Valore predefinito per la selezione prioritaria", - "LABEL_SEVERITY": "Valore predefinito per la selezione di rigore", "LABEL_ISSUE_TYPE": "Valore predefinito per il tipo di selezione del problema", - "LABEL_ISSUE_STATUS": "Valore predefinito per la selezione di stato del problema" + "LABEL_ISSUE_STATUS": "Valore predefinito per la selezione di stato del problema", + "LABEL_PRIORITY": "Valore predefinito per la selezione prioritaria", + "LABEL_SEVERITY": "Valore predefinito per la selezione di rigore" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Scrivi un nome per il nuovo status" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Mostra tutto", "FILTER_TYPE_PROJECTS": "Progetti", "FILTER_TYPE_PROJECT_TITLES": "Mostra solo i progetti", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Resoconti", "FILTER_TYPE_USER_STORIES_TITLES": "Mostra solo resoconti utente", "FILTER_TYPE_TASKS": "Compiti", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(facoltativo) aggiungi un testo personalizzato all'invito. Di qualcosa di simpatico ai tuoi nuovi membri ;-)", "PLACEHOLDER_TYPE_EMAIL": "Scrivi una mail", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Completata per il {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} di {{userStoryTotalTasks}} tasks closed). Punti: {{userStoryPoints}}. Descrizione: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Questo US é stato creato da", "GO_TO_EXTERNAL_REFERENCE": "Ritorna all'inizio", "BLOCKED": "Questa storia utente è bloccata", - "PREVIOUS": "Storia utente precedente ", - "NEXT": "Prossima storia utente", "TITLE_DELETE_ACTION": "Elimina la storia utente", "LIGHTBOX_TITLE_BLOKING_US": "Blocco la storia utente", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} compiti completati", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "Ordine dello sprint", "KANBAN_ORDER": "ordina kanban", "TASKBOARD_ORDER": "Ordine del pannello dei compiti", - "US_ORDER": "Ordine delle storie utente" + "US_ORDER": "Ordine delle storie utente", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtri", "REMOVE": "Rimuovi filtri", "HIDE": "Nascondi Filtri", - "SHOW": "Mostra Filtri", - "FILTER_CATEGORY_STATUS": "Stato", - "FILTER_CATEGORY_TAGS": "Tag" + "SHOW": "Mostra Filtri" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Questo compito è stato creato da", "TITLE_LINK_GO_ORIGIN": "Vai alla storia utente", "BLOCKED": "Questo compito è bloccato", - "PREVIOUS": "Compito precedente", - "NEXT": "Prossimo compito", "TITLE_DELETE_ACTION": "Rimuovi compito", "LIGHTBOX_TITLE_BLOKING_TASK": "Sto bloccando il compito", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Problema", "ACTION_NEW_ISSUE": "+ NUOVA CRITICITÁ", "ACTION_PROMOTE_TO_US": "Promuovi la storia utente", - "PLACEHOLDER_FILTER_NAME": "Scrivi il nome del filtro e premi invio", "PROMOTED": "Il problema è stato promosso a storia utente", "EXTERNAL_REFERENCE": "Questo problema è stato creato da ", "GO_TO_EXTERNAL_REFERENCE": "Ritorna all'inizio", "BLOCKED": "Questo problema è bloccato", - "TITLE_PREVIOUS_ISSUE": "problema precedente", - "TITLE_NEXT_ISSUE": "Problema successivo", "ACTION_DELETE": "Elimina problema", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Issue bloccante", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Promuovi questo problema come nuova storia utente", "MESSAGE": "Sei sicuro di voler creare una nuova storia utente da questo problema?" }, - "FILTERS": { - "TITLE": "Filtri", - "INPUT_SEARCH_PLACEHOLDER": "Soggetto o referenza", - "TITLE_ACTION_SEARCH": "Cerca", - "ACTION_SAVE_CUSTOM_FILTER": "salva come filtro personalizzato", - "BREADCRUMB": "Filtri", - "TITLE_BREADCRUMB": "Filtri", - "CATEGORIES": { - "TYPE": "Tipo", - "STATUS": "Stato", - "SEVERITY": "Gravità", - "PRIORITIES": "priorità", - "TAGS": "Tag", - "ASSIGNED_TO": "Assegna a", - "CREATED_BY": "Creato da", - "CUSTOM_FILTERS": "Filtri personalizzati" - }, - "CONFIRM_DELETE": { - "TITLE": "Elimina il filtro personalizzato", - "MESSAGE": "Il filtro personalizzato '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tipo", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Cerca - {{projectName}}", "PAGE_DESCRIPTION": "Cerca storie utenti, problemi, compiti o pagine wiki, all'interno del progetto {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Storie Utente", "FILTER_ISSUES": "problemi", "FILTER_TASKS": "Compiti", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} ha creato un nuovo compito {{obj_name}} in {{project_name}} che appartiene alla storia utente {{us_name}}", "WIKI_CREATED": "{{username}} ha creato una nuova pagina wiki {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} ha creato un nuovo sprint {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} ha creato il progetto {{project_name}}", "MILESTONE_UPDATED": "{{username}} ha aggiornato lo sprint {{obj_name}}", "US_UPDATED": "{{username}} ha aggiornato l'attributo \"{{field_name}}\" alla storia utente {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} ha aggiornato l'attributo \"{{field_name}}\" del compito {{obj_name}} che appartiene alla storia utente {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} ha aggiornato l'attributo \"{{field_name}}\" del compito {{obj_name}} che appartiene alla storia utente {{us_name}} a {{new_value}}", "WIKI_UPDATED": "{{username}} ha aggiornato la pagina wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} ha commentato nella storia utente {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} ha commentato nel problema {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} ha commentato nel compito {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} ha un nuovo membro", "US_ADDED_MILESTONE": "{{username}} ha aggiunto la storia utente {{obj_name}} a {{sprint_name}}", "US_MOVED": "{{username}} ha spostato la storia utente {{obj_name}}", diff --git a/app/locales/taiga/locale-nl.json b/app/locales/taiga/locale-nl.json index fc3bd95c..d637041e 100644 --- a/app/locales/taiga/locale-nl.json +++ b/app/locales/taiga/locale-nl.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Eén item per regel...", "NEW_BULK": "Nieuwe bulk toevoeging", "RELATED_TASKS": "Gerelateerde taken", + "PREVIOUS": "Previous", + "NEXT": "Volgende", "LOGOUT": "Afmelden", "EXTERNAL_USER": "een extern gebruiker", "GENERIC_ERROR": "Een van onze Oempa Loempa's zegt {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Deze waarde lijkt ongeldig te zijn", "TYPE_EMAIL": "Deze waarde moet een geldig emailadres bevatten", @@ -115,6 +122,7 @@ "USER_STORY": "User story", "TASK": "Taak", "ISSUE": "Issue", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Ik ben 'm! Tag me...", "DELETE": "Verwijder tag", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "filters", + "TITLE": "Filters", "INPUT_PLACEHOLDER": "Onderwerp of referentie", "TITLE_ACTION_FILTER_BUTTON": "zoek", - "BREADCRUMB_TITLE": "terug naar categorieën", - "BREADCRUMB_FILTERS": "Filters", - "BREADCRUMB_STATUS": "status" + "INPUT_SEARCH_PLACEHOLDER": "Onderwerp of ref.", + "TITLE_ACTION_SEARCH": "Zoek", + "ACTION_SAVE_CUSTOM_FILTER": "Als eigen filter opslaan", + "PLACEHOLDER_FILTER_NAME": "Geef de filternaam in en druk op enter", + "CATEGORIES": { + "TYPE": "Type", + "STATUS": "Status", + "SEVERITY": "Ernst", + "PRIORITIES": "Prioriteit", + "TAGS": "Tags", + "ASSIGNED_TO": "Toegewezen aan", + "CREATED_BY": "Aangemaakt door", + "CUSTOM_FILTERS": "Eigen filters" + }, + "CONFIRM_DELETE": { + "TITLE": "Verwijder eigen filter", + "MESSAGE": "de eigen filter '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Eerste niveau heading", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Markdown syntax help" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Sprints bekijken", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Volgers", "DASHBOARD": "Projects Dashboard" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Niet toegewezen" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Stemmen", + "NAME": "Naam", + "PROJECT": "Project", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Status", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Geblokkeerd", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Mijn projecten - Taiga", "PAGE_DESCRIPTION": "Een lijst met al jouw projecten, je kunt deze herodenen of een nieuwe aanmaken.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Bewerk waarde", - "TITLE_ACTION_DELETE_VALUE": "Verwijder waarde" + "TITLE_ACTION_DELETE_VALUE": "Verwijder waarde", + "TITLE_ACTION_DELETE_TAG": "Verwijder tag" }, "HELP": "Help je hulp nodig? Bekijk onze support pagina!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Modules", "ENABLE": "Inschakelen", "DISABLE": "Uitschakelen", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Organiseer je user stories om een duidelijk overzicht van aankomend en geprioritiseerd werk te behouden.", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Je staat op het punt de CSV data toegang url te veranderen. De vorige url zal worden uitgeschakeld. Ben je zeker dat je ermee door wil gaan?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "user stories rapporten", "SECTION_TITLE_TASK": "taak rapporten", "SECTION_TITLE_ISSUE": "Issues rapport", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Eigen velden", "SUBTITLE": "Specifieer de aangepaste velden voor je user stories, taken en issues", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Eigen velden user stories", "US_ADD": "Voeg eigen velden toe in user stories", "TASK_DESCRIPTION": "Eigen velden taken", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Specifieer de statussen waar je user stories, taken en issues door zullen gaan", - "US_TITLE": "US statussen", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Taak statussen", "ISSUE_TITLE": "Issue statussen" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Tags", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Tag tovoegen", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Rollen - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "de uitnodiging naar {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Standaard waarde voor punten selectie", - "LABEL_US": "Standaard waarde voor US status selectie", "LABEL_TASK_STATUS": "Standaard waarde voor taak status selectie", - "LABEL_PRIORITY": "Standaard waarde voor prioriteit selectie", - "LABEL_SEVERITY": "Standaard waarde voor ernst selectie", "LABEL_ISSUE_TYPE": "Standaard waarde voor issue type selectie", - "LABEL_ISSUE_STATUS": "Standaard waarde voor issue status selectie" + "LABEL_ISSUE_STATUS": "Standaard waarde voor issue status selectie", + "LABEL_PRIORITY": "Standaard waarde voor prioriteit selectie", + "LABEL_SEVERITY": "Standaard waarde voor ernst selectie" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Geef een naam voor de nieuwe status" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Alles weergeven", "FILTER_TYPE_PROJECTS": "Projecten", "FILTER_TYPE_PROJECT_TITLES": "Enkel projecten weergeven", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Verhalen", "FILTER_TYPE_USER_STORIES_TITLES": "Enkel verhalen van gebruikers weergeven", "FILTER_TYPE_TASKS": "Taken", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Optioneel) Voeg een gepersonaliseerd bericht toe aan je uitnodiging. Vertel iets leuks aan je nieuwe leden ;-)", "PLACEHOLDER_TYPE_EMAIL": "Type en E-mail", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - User Story {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Voltooid {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} van {{userStoryTotalTasks}} taken gesloten). Punten: {{userStoryPoints}}. Omschrijving: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Deze US is aangemaakt vanaf", "GO_TO_EXTERNAL_REFERENCE": "Ga naar bron", "BLOCKED": "Deze user story is geblokkeerd", - "PREVIOUS": "Vorige user story", - "NEXT": "volgende user story", "TITLE_DELETE_ACTION": "Verwijder user story", "LIGHTBOX_TITLE_BLOKING_US": "User story blokkeren", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} taken afgewerkt", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "sprint volgorde", "KANBAN_ORDER": "kanban volgorde", "TASKBOARD_ORDER": "taakbord volgorde", - "US_ORDER": "us volgorde" + "US_ORDER": "us volgorde", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filters", "REMOVE": "Filters verwijderd", "HIDE": "Filters verbergen", - "SHOW": "Toon filters", - "FILTER_CATEGORY_STATUS": "Status", - "FILTER_CATEGORY_TAGS": "Tags" + "SHOW": "Toon filters" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Deze taak werd aangemaakt vanaf", "TITLE_LINK_GO_ORIGIN": "Ga naar user story", "BLOCKED": "Deze taak is geblokkeerd", - "PREVIOUS": "vorige taak", - "NEXT": "volgende taak", "TITLE_DELETE_ACTION": "Verwijder taak", "LIGHTBOX_TITLE_BLOKING_TASK": "Blokkerende taak", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Issue", "ACTION_NEW_ISSUE": "+ nieuw probleem", "ACTION_PROMOTE_TO_US": "Promoveer tot User Story", - "PLACEHOLDER_FILTER_NAME": "Geef de filternaam in en druk op enter", "PROMOTED": "Dit issue is gepromoveerd tot US:", "EXTERNAL_REFERENCE": "Dit issue is aangemaakt vanaf", "GO_TO_EXTERNAL_REFERENCE": "Ga naar bron", "BLOCKED": "Dit issue is geblokkeerd", - "TITLE_PREVIOUS_ISSUE": "vorig issue", - "TITLE_NEXT_ISSUE": "volgend issue", "ACTION_DELETE": "Verwijderd issue", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blokkerend issue", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Bevorder dit issue tot een nieuwe user story", "MESSAGE": "Weet je zeker dat je een nieuw US van dit issue wilt maken?" }, - "FILTERS": { - "TITLE": "Filters", - "INPUT_SEARCH_PLACEHOLDER": "Onderwerp of ref.", - "TITLE_ACTION_SEARCH": "Zoek", - "ACTION_SAVE_CUSTOM_FILTER": "Als eigen filter opslaan", - "BREADCRUMB": "Filters", - "TITLE_BREADCRUMB": "Filters", - "CATEGORIES": { - "TYPE": "Type", - "STATUS": "Status", - "SEVERITY": "Ernst", - "PRIORITIES": "Prioriteiten", - "TAGS": "Tags", - "ASSIGNED_TO": "Toegewezen aan", - "CREATED_BY": "Aangemaakt door", - "CUSTOM_FILTERS": "Eigen filters" - }, - "CONFIRM_DELETE": { - "TITLE": "Verwijder eigen filter", - "MESSAGE": "de eigen filter '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Type", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Zoek - {{projectName}}", "PAGE_DESCRIPTION": "Zoek op alles, user stories, issues, taken, wiki pagina's, in het project {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "User Stories", "FILTER_ISSUES": "Issues", "FILTER_TASKS": "Taken", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} heeft de nieuwe taak {{obj_name}} aangemakt in {{project_name}} die hoort bij de US {{us_name}}", "WIKI_CREATED": "{{username}} heeft een nieuwe Wiki-pagina aangemaakt {{obj_name}} in {{project_name}}", "MILESTONE_CREATED": "{{username}} heeft een nieuwe sprint aangemaakt {{obj_name}} in {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} heeft een nieuw project aangemaakt {{project_name}}", "MILESTONE_UPDATED": "{{username}} heeft de sprint {{obj_name}} bijgewerkt", "US_UPDATED": "{{username}} heeft de eigenschap \"{{field_name}}\" van de US {{obj_name}} bijgewerkt", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} heeft de eigenschap \"{{field_name}}\" van de taak {{obj_name}} die behoort tot de US {{us_name}} bijgewerkt", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} heeft de eigenschap \"{{field_name}}\" van de taak {{obj_name}} die behoort tot de US {{us_name}} gewijzigd naar {{new_value}}", "WIKI_UPDATED": "{{username}} heeft de wiki pagina {{obj_name}} bijgewerkt", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} heeft gereageerd op de US {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} heeft gereageerd op het issue {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} heeft gereageerd op de taak {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} heeft een nieuw lid", "US_ADDED_MILESTONE": "{{username}} heeft de US {{obj_name}} toegevoegd aan {{sprint_name}}", "US_MOVED": "{{username}} heeft de user story {{obj_name}} verplaatst", diff --git a/app/locales/taiga/locale-pl.json b/app/locales/taiga/locale-pl.json index 385461c5..fceae681 100644 --- a/app/locales/taiga/locale-pl.json +++ b/app/locales/taiga/locale-pl.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Jedna pozycja na wiersz...", "NEW_BULK": "Nowe zbiorcze dodawanie", "RELATED_TASKS": "Zadania pokrewne", + "PREVIOUS": "Previous", + "NEXT": "Następny", "LOGOUT": "Wyloguj", "EXTERNAL_USER": "zewnętrzny użytkownik", "GENERIC_ERROR": "Umpa Lumpa mówi {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Nieprawidłowa wartość", "TYPE_EMAIL": "Podaj prawidłowy adres email.", @@ -115,6 +122,7 @@ "USER_STORY": "Historyjka użytkownika", "TASK": "Zadania", "ISSUE": "Zgłoszenie", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Otaguj mnie!...", "DELETE": "Usuń tag", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "filtry", + "TITLE": "Filtry", "INPUT_PLACEHOLDER": "Temat lub odniesienie", "TITLE_ACTION_FILTER_BUTTON": "szukaj", - "BREADCRUMB_TITLE": "wróć do kategorii", - "BREADCRUMB_FILTERS": "Filtry", - "BREADCRUMB_STATUS": "status" + "INPUT_SEARCH_PLACEHOLDER": "Temat lub referencja", + "TITLE_ACTION_SEARCH": "Szukaj", + "ACTION_SAVE_CUSTOM_FILTER": "zapisz jako filtr niestandardowy", + "PLACEHOLDER_FILTER_NAME": "Wpisz nazwę filtru i kliknij enter", + "CATEGORIES": { + "TYPE": "Typ", + "STATUS": "Statusy", + "SEVERITY": "Ważność", + "PRIORITIES": "Priorytety", + "TAGS": "Tagi", + "ASSIGNED_TO": "Przypisane do", + "CREATED_BY": "Stworzona przez", + "CUSTOM_FILTERS": "Filtry niestandardowe" + }, + "CONFIRM_DELETE": { + "TITLE": "Usuń filtr niestandardowy", + "MESSAGE": "filtr niestandardowy '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Nagłówek pierwszego poziomu", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Składnia Markdown pomoc" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprinty", "VIEW_SPRINTS": "Przeglądaj Sprinty", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Obserwujesz", "DASHBOARD": "Projects Dashboard" }, + "EPICS": { + "TITLE": "EPIKI", + "SECTION_NAME": "Epics", + "EPIC": "EPIK", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+DODAJ EPIK", + "UNASSIGNED": "Nieprzypisane" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Głosy", + "NAME": "Nazwa", + "PROJECT": "Projekt", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Statusy", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "Nowy epik", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Wymaganie zespołu", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Zablokowane", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Moje projekty - Taiga", "PAGE_DESCRIPTION": "Lista wszystkich Twoich projektów, możesz zmieniać ich kolejność lub tworzyć nowe.", @@ -378,8 +444,8 @@ "ATTACHMENT": { "SECTION_NAME": "załączniki", "TITLE": "{{ plik }} załadowany dnia {{ data }}", - "LIST_VIEW_MODE": "List view mode", - "GALLERY_VIEW_MODE": "Gallery view mode", + "LIST_VIEW_MODE": "Tryb listy", + "GALLERY_VIEW_MODE": "Tryb galerii", "DESCRIPTION": "Wpisz krótki opis", "DEPRECATED": "(przestarzały)", "DEPRECATED_FILE": "Przestarzałe?", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Edytuj wartość", - "TITLE_ACTION_DELETE_VALUE": "Usuń wartość" + "TITLE_ACTION_DELETE_VALUE": "Usuń wartość", + "TITLE_ACTION_DELETE_TAG": "Usuń tag" }, "HELP": "Potrzebujesz pomocy? Sprawdź naszą stronę wsparcia!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Moduły", "ENABLE": "Włącz", "DISABLE": "Wyłącz", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Dziennik", "BACKLOG_DESCRIPTION": "Zarządzaj swoimi historyjkami użytkownika aby utrzymać zorganizowany widok i priorytety zadań", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Zamierzasz zmienić link dostępu do danych CSV. Poprzedni link będzie niedostępny. Czy jesteś pewien?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "raporty historii użytkownika", "SECTION_TITLE_TASK": "Raporty zadań", "SECTION_TITLE_ISSUE": "raporty zgłoszeń", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Własne Pola", "SUBTITLE": "Zdefiniuj własne dodatkowe pola dla historyjek użytkownika, zadań i zgłoszeń.", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Własne pola dla historyjek użytkownika", "US_ADD": "Dodaj własne pole dla historyjek użytkownika", "TASK_DESCRIPTION": "Własne pola dla zadań", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Zdefiniuj statusy dla historyjek użytkownika, zadań i zgłoszeń.", - "US_TITLE": "Statusy", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Statusy zadań", "ISSUE_TITLE": "Statusy zgłoszeń" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Tagi", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Dodaj tag", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Role - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "zaproszenie do {{e-mail}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Domyślna wartość dla selektora punktów", - "LABEL_US": "Domyślna wartość dla selektora statusu historyjek użytkownika", "LABEL_TASK_STATUS": "Domyśla wartość dla selektora statusu zadań", - "LABEL_PRIORITY": "Domyślna wartość dla selektora priorytetu", - "LABEL_SEVERITY": "Domyślna wartość dla selektora ważności", "LABEL_ISSUE_TYPE": "Domyślna wartość dla selektora typu zgłoszenia", - "LABEL_ISSUE_STATUS": "Domyślna wartość dla selektora statusu zgłoszenia" + "LABEL_ISSUE_STATUS": "Domyślna wartość dla selektora statusu zgłoszenia", + "LABEL_PRIORITY": "Domyślna wartość dla selektora priorytetu", + "LABEL_SEVERITY": "Domyślna wartość dla selektora ważności" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Wpisz nazwę nowego statusu" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Show all", "FILTER_TYPE_PROJECTS": "Projekty", "FILTER_TYPE_PROJECT_TITLES": "Show only projects", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Stories", "FILTER_TYPE_USER_STORIES_TITLES": "Show only user stories", "FILTER_TYPE_TASKS": "Zadania", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opcjonalne) Dodaj spersonalizowany tekst do zaproszenia. Napisz coś słodziachnego do nowego członka zespołu :)", "PLACEHOLDER_TYPE_EMAIL": "Wpisz Email", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Historyjka użytkownika {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. Zakończono {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} z {{userStoryTotalTasks}} zadań). Punktów: {{userStoryPoints}}. Opis: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Ta historyjka została utworzona z", "GO_TO_EXTERNAL_REFERENCE": "Idź do źródła", "BLOCKED": "Ta historia użytkownika jest zablokowana", - "PREVIOUS": "poprzednia historia użytkownika", - "NEXT": "następna historia użytkownika", "TITLE_DELETE_ACTION": "Usuń historyjkę użytkownika", "LIGHTBOX_TITLE_BLOKING_US": "Blokuje nas", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} zadanie zakończone", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "kolejność sprintów", "KANBAN_ORDER": "kolejność kanban", "TASKBOARD_ORDER": "kolejność tablicy zadań", - "US_ORDER": "Kolejność HU" + "US_ORDER": "Kolejność HU", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtry", "REMOVE": "Usuń filtry", "HIDE": "Ukryj filtry", - "SHOW": "Pokaż filtry", - "FILTER_CATEGORY_STATUS": "Status", - "FILTER_CATEGORY_TAGS": "Tagi" + "SHOW": "Pokaż filtry" }, "SPRINTS": { "TITLE": "SPRINTY", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Źródło tego zadania to", "TITLE_LINK_GO_ORIGIN": "Idź do historyjki użytkownika", "BLOCKED": "To zadanie jest zablokowane", - "PREVIOUS": "poprzednie zadanie", - "NEXT": "następne zadanie", "TITLE_DELETE_ACTION": "Usuń zadanie", "LIGHTBOX_TITLE_BLOKING_TASK": "Blokowanie zadania", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Zgłoszenie", "ACTION_NEW_ISSUE": "+ NOWE ZGŁOSZENIE", "ACTION_PROMOTE_TO_US": "Awansuj na historyjkę użytkownika", - "PLACEHOLDER_FILTER_NAME": "Wpisz nazwę filtru i kliknij enter", "PROMOTED": "To zgłoszenie zostało wypromowane na HU:", "EXTERNAL_REFERENCE": "Źródło zgłoszenia", "GO_TO_EXTERNAL_REFERENCE": "Idź do źródła", "BLOCKED": "To zgłoszenie jest zablokowane", - "TITLE_PREVIOUS_ISSUE": "poprzednie zgłoszenie", - "TITLE_NEXT_ISSUE": "następne zgłoszenie", "ACTION_DELETE": "Usuń zgłoszenie", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blokowanie zgłoszenia", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Awansuj to zgłoszenie na historyjkę użytkownika", "MESSAGE": "Jesteś pewny, że chcesz wypromować to zgłoszenie na historyjkę użytkownika?" }, - "FILTERS": { - "TITLE": "Filtry", - "INPUT_SEARCH_PLACEHOLDER": "Temat lub referencja", - "TITLE_ACTION_SEARCH": "Szukaj", - "ACTION_SAVE_CUSTOM_FILTER": "zapisz jako filtr niestandardowy", - "BREADCRUMB": "Filtry", - "TITLE_BREADCRUMB": "Filtry", - "CATEGORIES": { - "TYPE": "Typy", - "STATUS": "Statusy", - "SEVERITY": "Ważność", - "PRIORITIES": "Priorytety", - "TAGS": "Tagi", - "ASSIGNED_TO": "Przypisane do", - "CREATED_BY": "Stworzona przez", - "CUSTOM_FILTERS": "Filtry niestandardowe" - }, - "CONFIRM_DELETE": { - "TITLE": "Usuń filtr niestandardowy", - "MESSAGE": "filtr niestandardowy '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Typ", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Szukaj - {{projectName}}", "PAGE_DESCRIPTION": "Możesz przeszukiwać wszystko, historyjki użytkownika, zgłoszenia, zadania oraz strony Wiki w projekcie {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Historyjki użytkownika", "FILTER_ISSUES": "Zgłoszenia", "FILTER_TASKS": "Zadania", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "Użytkownik {{username}} utworzył nowe zadanie {{obj_name}} w projekcie {{project_name}} należące do HU {{us_name}}", "WIKI_CREATED": "Użytkownik {{username}} utworzył nową stronę Wiki {{obj_name}} w projekcie {{project_name}}", "MILESTONE_CREATED": "Użytkownik {{username}} utworzył nowy sprint {{obj_name}} w projekcie {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "Użytkownik {{username}} utworzył projekt {{project_name}}", "MILESTONE_UPDATED": "Użytkownik {{username}} zaktualizował sprint {{obj_name}}", "US_UPDATED": "Użytkownik {{username}} zaktualizował atrybut {{field_name}} historyjki użytkownika {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "Użytkownik {{username}} zaktualizował atrybut {{field_name}} zadania {{obj_name}} należącego do HU {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "Użytkownik {{username}} zaktualizował atrybut {{field_name}} zadania {{obj_name}} należącego do HU {{us_name}} na {{new_value}}", "WIKI_UPDATED": "Użytkownik {{username}} zaktualizował stronę Wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "Użytkownik {{username}} skomentował historyjkę użytkownika {{obj_name}}", "NEW_COMMENT_ISSUE": "Użytkownik {{username}} skomentował zgłoszenie {{obj_name}}", "NEW_COMMENT_TASK": "Użytkownik {{username}} skomentował zadanie {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "Projekt {{project_name}} ma nowego członka", "US_ADDED_MILESTONE": "Użytkownik{{username}} dodał HU {{obj_name}} do {{sprint_name}}", "US_MOVED": "{{username}} przeniósł historyjkę użytkownika {{obj_name}}", diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index 97becb3d..7c2d54b4 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Um item por linha...", "NEW_BULK": "Nova inserção em lote", "RELATED_TASKS": "Tarefas relacionadas", + "PREVIOUS": "Anterior", + "NEXT": "Próximo", "LOGOUT": "Sair", "EXTERNAL_USER": "um usuário externo", "GENERIC_ERROR": "Um Oompa Loompas disse {{error}}.", @@ -43,8 +45,13 @@ "TEAM_REQUIREMENT": "Requisito de time é um requisito que deve existir no projeto, mas que não deve ter nenhum custo para o cliente.", "OWNER": "Dono do Projeto", "CAPSLOCK_WARNING": "Seja cuidadoso! Você está escrevendo em letras maiúsculas e esse campo é case sensitive, ou seja, trata com distinção as letras maiúsculas das minúsculas.", - "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", - "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Você tem certeza que quer fechar o modo de edição?", + "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Lembre-se que se você fechar o modo de edição sem salvar, todas as mudanças serão perdidas", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Este valor parece ser inválido.", "TYPE_EMAIL": "Este valor deve ser um e-mail válido.", @@ -69,7 +76,7 @@ "MAX_CHECK": "Você deve selecionar %s escolhas ou menos.", "RANGE_CHECK": "Você deve selecionar entre %s e %s escolhas.", "EQUAL_TO": "Esse valor deveria ser o mesmo.", - "LINEWIDTH": "One or more lines is perhaps too long. Try to keep under %s characters.", + "LINEWIDTH": "Talvez uma ou mais linhas estejam muito grandes. Tente usar menos de %s caracteres.", "PIKADAY": "Formato de data inválido, por favor, use DD MMM YYYY (exemplo: 23 Mar 1984)" }, "PICKERDATE": { @@ -115,6 +122,7 @@ "USER_STORY": "História de usuário", "TASK": "Tarefa", "ISSUE": "Problema", + "EPIC": "Épico", "TAGS": { "PLACEHOLDER": "Adicionar tags...", "DELETE": "Apagar tag", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "filtros", + "TITLE": "Filtros", "INPUT_PLACEHOLDER": "Assunto ou referência", "TITLE_ACTION_FILTER_BUTTON": "procurar", - "BREADCRUMB_TITLE": "voltar para categorias", - "BREADCRUMB_FILTERS": "Filtros", - "BREADCRUMB_STATUS": "status" + "INPUT_SEARCH_PLACEHOLDER": "Assunto ou ref", + "TITLE_ACTION_SEARCH": "Procurar", + "ACTION_SAVE_CUSTOM_FILTER": "salve como filtro personalizado", + "PLACEHOLDER_FILTER_NAME": "Digite o nome do filtro e pressione Enter", + "CATEGORIES": { + "TYPE": "Tipo", + "STATUS": "Status", + "SEVERITY": "Gravidade", + "PRIORITIES": "Prioridades", + "TAGS": "Tags", + "ASSIGNED_TO": "Atribuído a", + "CREATED_BY": "Criado por", + "CUSTOM_FILTERS": "Filtros personalizados" + }, + "CONFIRM_DELETE": { + "TITLE": "Apagar filtro personalizado", + "MESSAGE": "O filtro personalizado '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Primeira caixa de cabeçalho", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Ajuda de sintaxe markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprints", "VIEW_SPRINTS": "Ver sprints", @@ -244,7 +275,7 @@ "VIEW_USER_STORIES": "Ver histórias de usuários", "ADD_USER_STORIES": "Adicionar histórias de usuários", "MODIFY_USER_STORIES": "Modificar histórias de usuários", - "COMMENT_USER_STORIES": "Comment user stories", + "COMMENT_USER_STORIES": "Comentar histórias de usuário", "DELETE_USER_STORIES": "Apagar histórias de usuários" }, "TASKS": { @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Observando", "DASHBOARD": "Painel de Projetos" }, + "EPICS": { + "TITLE": "ÉPICOS", + "SECTION_NAME": "Epics", + "EPIC": "ÉPICO", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADICIONAR ÉPICO", + "UNASSIGNED": "Não-atribuído" + }, + "EMPTY": { + "TITLE": "Parece que você ainda não criou nenhum épico ", + "EXPLANATION": "Crie um épico para ter um nível superior de Histórias de Usuário. Épicos podem conter ou serem compostos por Histórias de Usuário deste ou de qualquer outro projeto", + "HELP": "Saiba mais sobre épicos" + }, + "TABLE": { + "VOTES": "Votos", + "NAME": "Nome", + "PROJECT": "Projeto", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Status", + "PROGRESS": "Progresso", + "VIEW_OPTIONS": "Ver opções" + }, + "CREATE": { + "TITLE": "Novo Épico", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Bloqueado", + "BLOCKED_NOTE_PLACEHOLDER": "Por que esse épico está bloqueado?", + "CREATE_EPIC": "Criar épico" + } + }, "PROJECTS": { "PAGE_TITLE": "Meus projetos - Taiga", "PAGE_DESCRIPTION": "Uma lista com todos os seus projetos, você pode reorganizá-los ou criar um novo.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Editar valor", - "TITLE_ACTION_DELETE_VALUE": "Apagar valor" + "TITLE_ACTION_DELETE_VALUE": "Apagar valor", + "TITLE_ACTION_DELETE_TAG": "Apagar tag" }, "HELP": "Você precisa de ajuda? Verifique nossa pagina de suporte!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Modulos", "ENABLE": "Habilitar", "DISABLE": "Desabilitar", + "EPICS": "Épicos", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Gerencie suas histórias de usuários para manter uma visualização organizada de trabalhos futuros e priorizados.", "NUMBER_SPRINTS": "Número de sprints esperadas", @@ -480,7 +549,7 @@ "ACTION_USE_DEFAULT_LOGO": "Usar imagem padrão", "MAX_PRIVATE_PROJECTS": "Você atingiu o número máximo de projetos privados permitidos para seu plano atual.", "MAX_PRIVATE_PROJECTS_MEMBERS": "O número máximo de membros para projetos privados foi excedido.", - "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", + "MAX_PUBLIC_PROJECTS": "Infelizmente você atingiu o número máximo de projetos público permitidos para seu plano atual", "MAX_PUBLIC_PROJECTS_MEMBERS": "Este projeto atingiu o seu limite atual de membros para projetos públicos", "PROJECT_OWNER": "Dono do projeto", "REQUEST_OWNERSHIP": "Solicitar propriedade", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Você está prestes a alterar a url de acesso a dados do CSV. A URL anterior será desabilitada. Você está certo disso?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "Relatórios de histórias de usuários", "SECTION_TITLE_TASK": "relatórios de tarefas", "SECTION_TITLE_ISSUE": "relatórios de problemas", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Campos Personalizados", "SUBTITLE": "Especificar campos personalizados para histórias de usuários, tarefas e problemas", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Campos personalizados das histórias de usuários", "US_ADD": "Adicionar campo personalizado nas histórias de usuários", "TASK_DESCRIPTION": "Campos personalizados das Tarefas", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Especifique os status pelos quais suas histórias de usuários, tarefas e problemas passarão", - "US_TITLE": "Estados das Histórias de Usuários", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Estados da Tarefa", "ISSUE_TITLE": "Estados do problema" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Tags", - "SUBTITLE": "Ver e editar as cores das stories de seu usuário", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Atualmente não há tags", - "EMPTY_SEARCH": "Parece que nada foi encontrado com os critérios de sua pesquisa." + "EMPTY_SEARCH": "Parece que nada foi encontrado com os critérios de sua pesquisa.", + "ACTION_ADD": "Adicionar tag", + "NEW_TAG": "Nova tag", + "MIXING_HELP_TEXT": "Selecione as tags que você quer mesclar", + "MIXING_MERGE": "Mesclar Tags", + "SELECTED": "Selecionado" }, "ROLES": { "PAGE_TITLE": "Funções - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "o convite para {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Valores padrões para o seletor de pontos", - "LABEL_US": "Valor padrão para seletor de status da História de Usuário", "LABEL_TASK_STATUS": "Valor padrão para seletor de status de tarefa", - "LABEL_PRIORITY": "Valor padão para seletor de prioridade", - "LABEL_SEVERITY": "Valor padrão para seletor de gravidade", "LABEL_ISSUE_TYPE": "Valor padrão para seletor de tipo de problema ", - "LABEL_ISSUE_STATUS": "Valor padrão para seletor de status de problema" + "LABEL_ISSUE_STATUS": "Valor padrão para seletor de status de problema", + "LABEL_PRIORITY": "Valor padão para seletor de prioridade", + "LABEL_SEVERITY": "Valor padrão para seletor de gravidade" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Digite um nome para o novo status" @@ -710,7 +789,7 @@ "TITLE": "Serviços" }, "PROJECT_TRANSFER": { - "DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "Would you like to become the new project owner?", + "DO_YOU_ACCEPT_PROJECT_OWNERNSHIP": "Você gostaria de se tornar o novo dono do projeto?", "PRIVATE": "Privado", "ACCEPTED_PROJECT_OWNERNSHIP": "Parabéns! Você é o proprietário do projeto agora.", "REJECTED_PROJECT_OWNERNSHIP": "OK. Entraremos em contato com o atual dono do projeto.", @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Mostrar tudo", "FILTER_TYPE_PROJECTS": "Projetos", "FILTER_TYPE_PROJECT_TITLES": "Mostrar somente projetos", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Histórias", "FILTER_TYPE_USER_STORIES_TITLES": "Mostrar apenas histórias de usuários", "FILTER_TYPE_TASKS": "Tarefas", @@ -853,7 +934,7 @@ "ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) é muito pesado para nossos Oompa Loompas, tente algo menor que ({{maxFileSize}})", "SYNC_SUCCESS": "Seu projeto foi importado com sucesso", "PROJECT_RESTRICTIONS": { - "PROJECT_MEMBERS_DESC": "The project you are trying to import has {{members}} members, unfortunately, your current plan allows for a maximum of {{max_memberships}} members per project. If you would like to increase that limit please contact the administrator.", + "PROJECT_MEMBERS_DESC": "O projeto que você está tentando importar tem {{members}} membros e infelizmente seu plano atual tem um limite máximo de {{max_memberships}} membros por projeto. Se você deseja aumentar este limite entre em contato com o administrador.", "PRIVATE_PROJECTS_SPACE": { "TITLE": "Unfortunately, your current plan does not allow for additional private projects", "DESC": "The project you are trying to import is private. Unfortunately, your current plan does not allow for additional private projects." @@ -870,7 +951,7 @@ }, "PRIVATE_PROJECTS_SPACE_MEMBERS": { "TITLE": "Unfortunately your current plan doesn't allow additional private projects or an increase of more than {{max_memberships}} members per private project", - "DESC": "The project that you are trying to import is private and has {{members}} members." + "DESC": "O projeto que você está tentando importar é privado e tem {{members}} membros." }, "PUBLIC_PROJECTS_SPACE_MEMBERS": { "TITLE": "Unfortunately your current plan doesn't allow additional public projects or an increase of more than {{max_memberships}} members per public project", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opcional) Adicione uma mensagem de texto ao convite. Diga algo animador para os novos membros ;-)", "PLACEHOLDER_TYPE_EMAIL": "Digite um Email", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Infelizmente, este projeto não pode ter mais do que {{maxMembers}} membros.
Se você gostaria de aumentar o limite atual, por favor contate o administrador.", - "LIMIT_USERS_WARNING_MESSAGE": "Infelizmente este projeto não pode ter mais do que {{maxMembers}} membros." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Infelizmente, este projeto não pode ficar sem um dono", @@ -985,6 +1066,25 @@ "BUTTON": "Pedir a este membro do projeto para se tornar o novo dono do projeto" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - História de Usuário {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Estado: {{userStoryStatus }}. Completos {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} de {{userStoryTotalTasks}} tarefas encerradas). Pontos: {{userStoryPoints}}. Descrição: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Esta História de Usuário foi criada de", "GO_TO_EXTERNAL_REFERENCE": "Ir para a origem", "BLOCKED": "Esta história de usuário está bloqueada", - "PREVIOUS": "história de usuário anterior", - "NEXT": "proxima história de usuário", "TITLE_DELETE_ACTION": "Apagar história de usuário", "LIGHTBOX_TITLE_BLOKING_US": "História de usuário bloqueadora", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tarefas completas", @@ -1010,11 +1108,11 @@ "TRIBE": { "PUBLISH": "Publicar como Gig no Taiga Tribe", "PUBLISH_INFO": "Mais informações", - "PUBLISH_TITLE": "More info on publishing in Taiga Tribe", + "PUBLISH_TITLE": "Mais informações sobre como publicar na Tribo Taiga", "PUBLISHED_AS_GIG": "Story published as Gig in Taiga Tribe", "EDIT_LINK": "Editar link", "CLOSE": "Fechar", - "SYNCHRONIZE_LINK": "synchronize with Taiga Tribe", + "SYNCHRONIZE_LINK": "sincronizar com a Tribo Taiga", "PUBLISH_MORE_INFO_TITLE": "Você precisa de alguém para esta tarefa?", "PUBLISH_MORE_INFO_TEXT": "

If you need help with a particular piece of work you can easily create gigs on Taiga Tribe and receive help from all over the world. You will be able to control and manage the gig enjoying a great community eager to contribute.

TaigaTribe was born as a Taiga sibling. Both platforms can live separately but we believe that there is much power in using them combined so we are making sure the integration works like a charm.

" }, @@ -1062,7 +1160,7 @@ "UPDATED_CUSTOM_ATTRIBUTE": "atributo personalizado atualizado", "SIZE_CHANGE": "Feito {size, plural, one{one change} other{# changes}}", "BECAME_DEPRECATED": "foi depreciado", - "BECAME_UNDEPRECATED": "became undeprecated", + "BECAME_UNDEPRECATED": "foi depreciado", "TEAM_REQUIREMENT": "Requisitos da Equipe", "CLIENT_REQUIREMENT": "Requisitos do Cliente", "BLOCKED": "Bloqueado", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "ordem de sprint ", "KANBAN_ORDER": "pedido kanban", "TASKBOARD_ORDER": "Ordem de quadro de tarefa", - "US_ORDER": "ordem da história de usuário" + "US_ORDER": "ordem da história de usuário", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtros", "REMOVE": "Remover filtros", "HIDE": "Esconder Filtros", - "SHOW": "Mostrar Filtros", - "FILTER_CATEGORY_STATUS": "Status", - "FILTER_CATEGORY_TAGS": "Tags" + "SHOW": "Mostrar Filtros" }, "SPRINTS": { "TITLE": "SPRINTS", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Essa tarefa foi criada a partir de", "TITLE_LINK_GO_ORIGIN": "Ir para história de usuário", "BLOCKED": "Esta tarefa está bloqueada", - "PREVIOUS": "tarefa anterior", - "NEXT": "nova tarefa", "TITLE_DELETE_ACTION": "Apagar Tarefa", "LIGHTBOX_TITLE_BLOKING_TASK": "Tarefa bloqueadora", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Problema", "ACTION_NEW_ISSUE": "+ NOVO PROBLEMA", "ACTION_PROMOTE_TO_US": "Promover para História de Usuário", - "PLACEHOLDER_FILTER_NAME": "Digite o nome do filtro e pressione Enter", "PROMOTED": "Esse problema foi promovido para história de usuário", "EXTERNAL_REFERENCE": "Esse problema foi criado a partir de", "GO_TO_EXTERNAL_REFERENCE": "Ir para a origem", "BLOCKED": "Esse apontamento está bloqueado", - "TITLE_PREVIOUS_ISSUE": "problema anterior", - "TITLE_NEXT_ISSUE": "próximo problema", "ACTION_DELETE": "Problema apagado", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Problema que está bloqueando", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Promover esse problema para nova história de usuário", "MESSAGE": "Você tem certeza que deseja criar uma nova História de Usuário a partir desse problema?" }, - "FILTERS": { - "TITLE": "Filtros", - "INPUT_SEARCH_PLACEHOLDER": "Assunto ou ref", - "TITLE_ACTION_SEARCH": "Procurar", - "ACTION_SAVE_CUSTOM_FILTER": "salve como filtro personalizado", - "BREADCRUMB": "Filtros", - "TITLE_BREADCRUMB": "Filtros", - "CATEGORIES": { - "TYPE": "Tipo", - "STATUS": "Status", - "SEVERITY": "Gravidade", - "PRIORITIES": "Prioridades", - "TAGS": "Tags", - "ASSIGNED_TO": "Atribuído a", - "CREATED_BY": "Criado por", - "CUSTOM_FILTERS": "Filtros personalizados" - }, - "CONFIRM_DELETE": { - "TITLE": "Apagar filtro personalizado", - "MESSAGE": "O filtro personalizado '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tipo", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Buscar - {{projectName}}", "PAGE_DESCRIPTION": "Busque qualquer coisa, histórias de usuários, problemas, tarefas, ou páginas da wiki, no projeto {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Épicos", "FILTER_USER_STORIES": "Histórias de Usuários", "FILTER_ISSUES": "Problemas", "FILTER_TASKS": "Tarefas", @@ -1467,7 +1538,7 @@ "HOME": "Página principal", "SECTION_NAME": "BOOKMARKS", "ACTION_ADD_LINK": "Add bookmark", - "ALL_PAGES": "All wiki pages" + "ALL_PAGES": "Todas as páginas wiki" }, "SUMMARY": { "TIMES_EDITED": "vezes
editadas", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} criou nova tarefa {{obj_name}} em {{project_name}} que pertence a História de Usuário {{us_name}}", "WIKI_CREATED": "{{username}} criou uma página wiki {{obj_name}} em {{project_name}}", "MILESTONE_CREATED": "{{username}} criou uma nova sprint {{obj_name}} em {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} criou o projeto {{project_name}}", "MILESTONE_UPDATED": "{{username}} atualizou a sprint {{obj_name}}", "US_UPDATED": "{{username}} atualizou o atributo \"{{field_name}}\" da História de Usuário {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} que pertence à História de Usuário {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} atualizou o atributo \"{{field_name}}\" da tarefa {{obj_name}} que pertence à História de Usuário {{us_name}} para {{new_value}}", "WIKI_UPDATED": "{{username}} atualizou a página wiki {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} comentou na História de Usuário {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} comentou no problema {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} comentou na tarefa {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} tem um membro novo", "US_ADDED_MILESTONE": "{{username}} adicionou a História de Usuário {{obj_name}} a {{sprint_name}}", "US_MOVED": "{{username}} moveu a História de Usuário {{obj_name}}", @@ -1613,7 +1690,7 @@ "VIEW_MORE": "Visualizar mais", "RECRUITING": "Este projeto esta procurando colaboradores", "FEATURED": "Featured Projects", - "EMPTY": "There are no projects to show with this search criteria.
Try again!", + "EMPTY": "Não há projetos para exibir sob esse critério de pesquisa.
Tente novamente!", "FILTERS": { "ALL": "Tudo", "KANBAN": "Kanban", diff --git a/app/locales/taiga/locale-ru.json b/app/locales/taiga/locale-ru.json index 496c408e..5d5c6c53 100644 --- a/app/locales/taiga/locale-ru.json +++ b/app/locales/taiga/locale-ru.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Один объект на строку...", "NEW_BULK": "Добавить пакетно", "RELATED_TASKS": "Связанные задачи", + "PREVIOUS": "Предыдущий", + "NEXT": "Следующий", "LOGOUT": "Выйти", "EXTERNAL_USER": "внешний пользователь", "GENERIC_ERROR": "Один из Умпа-Лумп говорит {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Кажется, это значение некорректно.", "TYPE_EMAIL": "Значение должно быть корректной электронной почтой.", @@ -115,6 +122,7 @@ "USER_STORY": "Пользовательская история", "TASK": "Задача", "ISSUE": "Запрос", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Назначьте тэг", "DELETE": "Удалить тэг", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "фильтры", + "TITLE": "Фильтры", "INPUT_PLACEHOLDER": "Название ссылки", "TITLE_ACTION_FILTER_BUTTON": "поиск", - "BREADCRUMB_TITLE": "назад к категориям", - "BREADCRUMB_FILTERS": "Фильтры", - "BREADCRUMB_STATUS": "cтатус" + "INPUT_SEARCH_PLACEHOLDER": "Название ссылки", + "TITLE_ACTION_SEARCH": "Поиск", + "ACTION_SAVE_CUSTOM_FILTER": "сохранить как специальный фильтр", + "PLACEHOLDER_FILTER_NAME": "Введите название фильтра и нажмите \"ввод\"", + "CATEGORIES": { + "TYPE": "Тип", + "STATUS": "Статус", + "SEVERITY": "Важность", + "PRIORITIES": "Приоритеты", + "TAGS": "Тэги", + "ASSIGNED_TO": "Назначено", + "CREATED_BY": "Создано", + "CUSTOM_FILTERS": "Собственные фильтры" + }, + "CONFIRM_DELETE": { + "TITLE": "Удалить фильтр", + "MESSAGE": "специальный фильтр '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Заголовок первого уровня", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Помощь по синтаксису Markdown" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Спринты", "VIEW_SPRINTS": "Посмотреть спринты", @@ -363,13 +394,48 @@ "HOME": { "PAGE_TITLE": "Домашняя страница - Taiga", "PAGE_DESCRIPTION": "Главная страница Taiga с вашими основными проектами, назначенными и отслеживаемыми ПИ, задачами и запросами", - "EMPTY_WORKING_ON": "It feels empty, doesn't it? Start working with Taiga and you'll see here the stories, tasks and issues you are working on.", + "EMPTY_WORKING_ON": "Тут кажется пусто, не правда ли? Начинайте использовать Taiga и вы увидите здесь истории, задачи и запросы над которыми вы сейчас работаете.", "EMPTY_WATCHING": "Следите за пользовательскими историями, задачами, запросами в ваших проектах и будьте уведомлены об изменениях :)", "EMPTY_PROJECT_LIST": "У Вас пока нет проектов", "WORKING_ON_SECTION": "Работает над", "WATCHING_SECTION": "Отслеживаемые", "DASHBOARD": "Рабочий стол с проектами" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Не назначено" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Голоса", + "NAME": "Имя", + "PROJECT": "Проект", + "SPRINT": "Спринт", + "ASSIGNED_TO": "Assigned", + "STATUS": "Статус", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Заблокирован", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Мои проекты", "PAGE_DESCRIPTION": "Список Ваших проектов, отсортируйте их или создайте новый.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Изменить значение", - "TITLE_ACTION_DELETE_VALUE": "Удалить значение" + "TITLE_ACTION_DELETE_VALUE": "Удалить значение", + "TITLE_ACTION_DELETE_TAG": "Удалить тэг" }, "HELP": "Вам нужна помощь? Проверьте нашу страницу техподдержки!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Модули", "ENABLE": "Включить", "DISABLE": "Выключить", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Список задач", "BACKLOG_DESCRIPTION": "Управляйте пользовательскими историями, чтобы поддерживать организованное видение важных и приоритетных задач.", "NUMBER_SPRINTS": "Ожидаемое количество спринтов", @@ -478,8 +547,8 @@ "LOGO_HELP": "Изображение будет отмасштабировано до 80x80px.", "CHANGE_LOGO": "Изменить лого", "ACTION_USE_DEFAULT_LOGO": "Использовать картинку по умолчанию", - "MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects allowed by your current plan", - "MAX_PRIVATE_PROJECTS_MEMBERS": "The maximum number of members for private projects has been exceeded", + "MAX_PRIVATE_PROJECTS": "Вы достигли максимального числа приватных проектов которое разрешено вашим планом.", + "MAX_PRIVATE_PROJECTS_MEMBERS": "Максимальное количество участников в приватном проекте достигло лимита", "MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects allowed by your current plan", "MAX_PUBLIC_PROJECTS_MEMBERS": "The project exceeds your maximum number of members for public projects", "PROJECT_OWNER": "Владелец проекта", @@ -489,7 +558,7 @@ "REQUEST_OWNERSHIP_BUTTON": "Запрос", "REQUEST_OWNERSHIP_SUCCESS": "Мы уведомим владельца проекта", "CHANGE_OWNER": "Сменить владельца", - "CHANGE_OWNER_SUCCESS_TITLE": "Ok, your request has been sent!", + "CHANGE_OWNER_SUCCESS_TITLE": "ОК, ваш запрос был отправлен!", "CHANGE_OWNER_SUCCESS_DESC": "We will notify you by email if the project ownership request is accepted or declined" }, "REPORTS": { @@ -501,18 +570,21 @@ "REGENERATE_SUBTITLE": "Вы собираетесь изменить ссылку доступа к данным CSV. Прежний вариант ссылки перестанет работать. Вы уверены?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "Отчёты по пользовательским историям", "SECTION_TITLE_TASK": "отчёты о задачах", "SECTION_TITLE_ISSUE": "отчёты о запросах", "DOWNLOAD": "Скачать CSV", "URL_FIELD_PLACEHOLDER": "Упс, забыли пароль?", - "TITLE_REGENERATE_URL": " Сделать CSV ссылку ещё раз", + "TITLE_REGENERATE_URL": "Сделать CSV ссылку ещё раз", "ACTION_GENERATE_URL": "Сгенерировать ссылку", "ACTION_REGENERATE": "Создать заново" }, "CUSTOM_FIELDS": { "TITLE": "Пользовательские поля", "SUBTITLE": "Укажите специальные поля для ваших пользовательских историй, задач и запросов", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Специальные поля для пользовательских историй", "US_ADD": "Добавить специальное поле для пользовательских историй", "TASK_DESCRIPTION": "Специальные поля задач", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Статус", "SUBTITLE": "Укажите, какие статусы будут принимать ваши пользовательские истории, задачи и запросы", - "US_TITLE": "Статусы ПИ", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Статус задач", "ISSUE_TITLE": "Статусы запроса" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Тэги", - "SUBTITLE": "View and edit the color of your user stories", - "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "SUBTITLE": "Просмотреть и изменить цвет ваших тэгов", + "EMPTY": "В данный момент тэги отсутствуют", + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Добавить тэг", + "NEW_TAG": "Новый тэг", + "MIXING_HELP_TEXT": "Выберите тэги которые вы хотели бы объединить", + "MIXING_MERGE": "Объединить Тэги", + "SELECTED": "Выбранные" }, "ROLES": { "PAGE_TITLE": "Роли - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "приглашение на {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Значения по умолчанию для выбора очков", - "LABEL_US": "Значение по умолчанию для статуса ПИ", "LABEL_TASK_STATUS": "Значение по умолчанию для статуса задачи", - "LABEL_PRIORITY": "Значение по умолчанию для выбора приоритета", - "LABEL_SEVERITY": "Значение важности по умолчанию", "LABEL_ISSUE_TYPE": "Значение по умолчанию для типа запроса", - "LABEL_ISSUE_STATUS": "Значение по умолчанию для статуса запроса" + "LABEL_ISSUE_STATUS": "Значение по умолчанию для статуса запроса", + "LABEL_PRIORITY": "Значение по умолчанию для выбора приоритета", + "LABEL_SEVERITY": "Значение важности по умолчанию" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Укажите название для нового статуса" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Показать все", "FILTER_TYPE_PROJECTS": "Проекты", "FILTER_TYPE_PROJECT_TITLES": "Показать только проекты", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Истории", "FILTER_TYPE_USER_STORIES_TITLES": "Показывать только пользовательские истории", "FILTER_TYPE_TASKS": "Задачи", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Необязательно) Добавьте персональный текст в приглашение. Скажите что-нибудь приятное вашим новым участникам ;-)", "PLACEHOLDER_TYPE_EMAIL": "Введите электронную почту", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Предложить участнику проекта стать его новым владельцем" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Пользовательская История {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Статус: {{userStoryStatus }}. Выполнено {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} из {{userStoryTotalTasks}} задач). Очки: {{userStoryPoints}}. Описание: {{userStoryDescription}}", @@ -998,9 +1098,7 @@ "TITLE_LINK_GO_TO_ISSUE": "Перейти к запросу", "EXTERNAL_REFERENCE": "Эта ПИ была создана из:", "GO_TO_EXTERNAL_REFERENCE": "Перейти в начало", - "BLOCKED": "Эта пользовательская история заблокирована ", - "PREVIOUS": "предыдущая пользовательская история", - "NEXT": "следующая пользовательская история", + "BLOCKED": "Эта пользовательская история заблокирована", "TITLE_DELETE_ACTION": "Удалить пользовательскую историю", "LIGHTBOX_TITLE_BLOKING_US": "Блокирующая ПИ", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} задач выполнено", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "порядок спринтов", "KANBAN_ORDER": "порядок kanban", "TASKBOARD_ORDER": "порядок панели задач", - "US_ORDER": "порядок ПИ" + "US_ORDER": "порядок ПИ", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Фильтры", "REMOVE": "Сбросить фильтры", "HIDE": "Спрятать фильтры", - "SHOW": "Показать фильтры", - "FILTER_CATEGORY_STATUS": "Статус", - "FILTER_CATEGORY_TAGS": "Тэги" + "SHOW": "Показать фильтры" }, "SPRINTS": { "TITLE": "СПРИНТЫ", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Эта задача была создана из", "TITLE_LINK_GO_ORIGIN": "Перейти к пользовательской истории", "BLOCKED": "Эта задача заблокирована", - "PREVIOUS": "предыдущая задача", - "NEXT": "следующая задача", "TITLE_DELETE_ACTION": "Удалить задачу", "LIGHTBOX_TITLE_BLOKING_TASK": "Блокирующее задание", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Запрос", "ACTION_NEW_ISSUE": "+НОВЫЙ ЗАПРОС", "ACTION_PROMOTE_TO_US": "Повысить до пользовательской истории", - "PLACEHOLDER_FILTER_NAME": "Введите название фильтра и нажмите \"ввод\"", "PROMOTED": "Этот запрос был переделан в ПИ:", "EXTERNAL_REFERENCE": "Этот запрос был создан из", "GO_TO_EXTERNAL_REFERENCE": "Перейти в начало", "BLOCKED": "Этот запрос заблокирована", - "TITLE_PREVIOUS_ISSUE": "предыдущий запрос", - "TITLE_NEXT_ISSUE": "следующий запрос", "ACTION_DELETE": "Удалить запрос", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Блокирующий запрос", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Превратить этот запрос в новую пользовательскую историю", "MESSAGE": "Вы уверены, что хотите создать новую ПИ из этого запроса?" }, - "FILTERS": { - "TITLE": "Фильтры", - "INPUT_SEARCH_PLACEHOLDER": "Название ссылки", - "TITLE_ACTION_SEARCH": "Поиск", - "ACTION_SAVE_CUSTOM_FILTER": "сохранить как специальный фильтр", - "BREADCRUMB": "Фильтры", - "TITLE_BREADCRUMB": "Фильтры", - "CATEGORIES": { - "TYPE": "Тип", - "STATUS": "Статус", - "SEVERITY": "Важность", - "PRIORITIES": "Приоритет", - "TAGS": "Тэги", - "ASSIGNED_TO": "Назначено", - "CREATED_BY": "Создано", - "CUSTOM_FILTERS": "Собственные фильтры" - }, - "CONFIRM_DELETE": { - "TITLE": "Удалить фильтр", - "MESSAGE": "специальный фильтр '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Тип", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Поиск - {{projectName}}", "PAGE_DESCRIPTION": "Ищите что угодно, пользовательские истории, задачи, запросы и вики-страницы, в проекте {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Пользовательские Истории", "FILTER_ISSUES": "Запросы", "FILTER_TASKS": "Задачи", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} создал новую задачу {{obj_name}} в {{project_name}}, которая принадлежит ПИ {{us_name}}", "WIKI_CREATED": "{{username}} создал новую вики-страницу {{obj_name}} в {{project_name}}", "MILESTONE_CREATED": "{{username}} создал новый спринт {{obj_name}} в {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} создал проект {{project_name}}", "MILESTONE_UPDATED": "{{username}} обновил спринт {{obj_name}}", "US_UPDATED": "{{username}} обновил атрибут \"{{field_name}}\" ПИ {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} изменил атрибут \"{{field_name}}\" задачи {{obj_name}}, которая принадлежит ПИ {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} установил атрибут \"{{field_name}}\" задачи {{obj_name}}, которая принадлежит ПИ {{us_name}}, на {{new_value}}", "WIKI_UPDATED": "{{username}} обновил вики-страницу {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} прокомментировал ПИ {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} прокомментировал запрос {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} прокомментировал задачу {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "У {{project_name}} появился новый участник", "US_ADDED_MILESTONE": "{{username}} добавил ПИ {{obj_name}} для {{sprint_name}}", "US_MOVED": "{{username}} переместил ПИ {{obj_name}}", diff --git a/app/locales/taiga/locale-sv.json b/app/locales/taiga/locale-sv.json index 9f8a4118..61bcc031 100644 --- a/app/locales/taiga/locale-sv.json +++ b/app/locales/taiga/locale-sv.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "En post per rad ...", "NEW_BULK": "Lägg till flera nya", "RELATED_TASKS": "Besläktade uppgifter", + "PREVIOUS": "Previous", + "NEXT": "Nästa", "LOGOUT": "Logga ut", "EXTERNAL_USER": "en extern användare", "GENERIC_ERROR": "En av våra Oompa Loompier säger {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Det här värdet är felaktigt. ", "TYPE_EMAIL": "Värdet måste vara en giltig e-postadress", @@ -115,6 +122,7 @@ "USER_STORY": "Användarhistorie", "TASK": "Uppgift", "ISSUE": "ärende", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Det är jag! Tagga mig ...", "DELETE": "Ta bort etikett", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?" }, "FILTERS": { - "TITLE": "filter", + "TITLE": "Filter", "INPUT_PLACEHOLDER": "Titel eller referens", "TITLE_ACTION_FILTER_BUTTON": "sök", - "BREADCRUMB_TITLE": "tillbaka till kategorierna", - "BREADCRUMB_FILTERS": "Filter", - "BREADCRUMB_STATUS": "status" + "INPUT_SEARCH_PLACEHOLDER": "Titel eller referens", + "TITLE_ACTION_SEARCH": "Sök", + "ACTION_SAVE_CUSTOM_FILTER": "spara som anpassad filter", + "PLACEHOLDER_FILTER_NAME": "Skriv filternamnet och tryck på ", + "CATEGORIES": { + "TYPE": "Typ", + "STATUS": "Status", + "SEVERITY": "Allvarsgrad", + "PRIORITIES": "Prioritet", + "TAGS": "Etiketter", + "ASSIGNED_TO": "Tilldelad till", + "CREATED_BY": "Skapad av", + "CUSTOM_FILTERS": "Anpassad filter" + }, + "CONFIRM_DELETE": { + "TITLE": "Ta bort anpassad filter.", + "MESSAGE": "anpassad filter '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "Första nivån snart klar", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Hjälp för markeringssyntax" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Sprintar", "VIEW_SPRINTS": "Visa sprintar", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "Bevakar", "DASHBOARD": "Projects Dashboard" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Ej tilldelad" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Röster", + "NAME": "Namn", + "PROJECT": "Projekt", + "SPRINT": "Sprint", + "ASSIGNED_TO": "Assigned", + "STATUS": "Status", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Blockerad", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Mina projekt - Taiga", "PAGE_DESCRIPTION": "En lista med alla dina projekt som du kan organisera eller skapa ett nytt. ", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Redigera", - "TITLE_ACTION_DELETE_VALUE": "Ta bort" + "TITLE_ACTION_DELETE_VALUE": "Ta bort", + "TITLE_ACTION_DELETE_TAG": "Ta bort etikett" }, "HELP": "Behöver du hjälp? Besök hjälpsidorna!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Moduler", "ENABLE": "Aktivera", "DISABLE": "Avvaktivera", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Inkorg", "BACKLOG_DESCRIPTION": "Hantera dina användarhistorier för att organisera visningar av kommande och prioriterade jobb. ", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "Du kan ändra CSV för datalänken. Den tidigare länken tas bort. Är du säker på det? " }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "rapporter för användarhistorier", "SECTION_TITLE_TASK": "Rapport för uppgifter", "SECTION_TITLE_ISSUE": "Rapporter för ärenden", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Anpassade fält", "SUBTITLE": "Specificera anpassade fält för användarhistorier, uppgifter och ärenden. ", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Användarhistorier för anpassade fält", "US_ADD": "Lägg till ett anpassad fält i användarhistorien", "TASK_DESCRIPTION": "Anpassade fält för uppgifter", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Status", "SUBTITLE": "Specificera status för dina användarhistorier, uppgifter och ärenden ska ha i olika faser. ", - "US_TITLE": "US statuser", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Status för uppgifter", "ISSUE_TITLE": "Status för ärenden" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Etiketter", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Lägg till etikett", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Roller - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "den här invitationen till {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Standardvärde för poängväljaren", - "LABEL_US": "Standardvärde för US-statusväljare", "LABEL_TASK_STATUS": "Standardvärdet för val av uppgiftsstatus", - "LABEL_PRIORITY": "Standardvärde för val av prioritet", - "LABEL_SEVERITY": "Standardvärde för val av allvarlighet", "LABEL_ISSUE_TYPE": "Standardvärde för ärendetyp-väljare", - "LABEL_ISSUE_STATUS": "Standardvärde för väljare för ärendestatus" + "LABEL_ISSUE_STATUS": "Standardvärde för väljare för ärendestatus", + "LABEL_PRIORITY": "Standardvärde för val av prioritet", + "LABEL_SEVERITY": "Standardvärde för val av allvarlighet" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Skriv ett namn för den nya statusen" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Visa alla", "FILTER_TYPE_PROJECTS": "Projekt", "FILTER_TYPE_PROJECT_TITLES": "Visa bara projekt", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Berättelser", "FILTER_TYPE_USER_STORIES_TITLES": "Visa endast användarhistorier", "FILTER_TYPE_TASKS": "Uppgift", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Valfritt) Lägg till en personlig hälsning till invitationen. Berätta något trevligt till din nya projektmedlem ;-)", "PLACEHOLDER_TYPE_EMAIL": "Skriv in en e-postadress", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Användarhistorier {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{userStoryStatus }}. avslutad{{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} av {{userStoryTotalTasks}} tasks closed). Poäng: {{userStoryPoints}}. Beskrivning: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Denna användarhistorien är skapat från", "GO_TO_EXTERNAL_REFERENCE": "Gå till början", "BLOCKED": "Användarhistorien är blockerad", - "PREVIOUS": "tidigare användarhistorie", - "NEXT": "nästa användarhistorie", "TITLE_DELETE_ACTION": "Ta bort användarhistorien", "LIGHTBOX_TITLE_BLOKING_US": "Blockera oss", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} uppgifter kompletta", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "sortera sprintar", "KANBAN_ORDER": "kanban-sortering", "TASKBOARD_ORDER": "Sortera uppgiftstavlan", - "US_ORDER": "sortera US" + "US_ORDER": "sortera US", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filter", "REMOVE": "Ta bort filter", "HIDE": "Dölj filter", - "SHOW": "Visa filter", - "FILTER_CATEGORY_STATUS": "Status", - "FILTER_CATEGORY_TAGS": "Etiketter" + "SHOW": "Visa filter" }, "SPRINTS": { "TITLE": "SPRINTAR", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Den här uppgiften är skapad från", "TITLE_LINK_GO_ORIGIN": "Gå till användarhistorie", "BLOCKED": "Uppgiften är blockerad", - "PREVIOUS": "tidigare uppgift", - "NEXT": "ny uppgift", "TITLE_DELETE_ACTION": "Ta bort uppgift", "LIGHTBOX_TITLE_BLOKING_TASK": "Blockerad uppgift", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "ärende", "ACTION_NEW_ISSUE": "+ NYTT ÄRENDE", "ACTION_PROMOTE_TO_US": "Flytta till användarhistorie", - "PLACEHOLDER_FILTER_NAME": "Skriv filternamnet och tryck på ", "PROMOTED": "Ärendet har flyttats till US:", "EXTERNAL_REFERENCE": "Den här uppgiften är skapat från", "GO_TO_EXTERNAL_REFERENCE": "Gå till början", "BLOCKED": "Det här ärendet är blockerad", - "TITLE_PREVIOUS_ISSUE": "tidigare ärende", - "TITLE_NEXT_ISSUE": "nästa ärende", "ACTION_DELETE": "Ta bort ärende", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Blockerad ärende", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Flytta det här ärendet till en ny användarhistorie", "MESSAGE": "Är du säker på att du vill skapa en ny US från det här ärendet?" }, - "FILTERS": { - "TITLE": "Filter", - "INPUT_SEARCH_PLACEHOLDER": "Titel eller referens", - "TITLE_ACTION_SEARCH": "Sök", - "ACTION_SAVE_CUSTOM_FILTER": "spara som anpassad filter", - "BREADCRUMB": "Filter", - "TITLE_BREADCRUMB": "Filter", - "CATEGORIES": { - "TYPE": "Typ", - "STATUS": "Status", - "SEVERITY": "Allvarsgrad", - "PRIORITIES": "Prioritet", - "TAGS": "Etiketter", - "ASSIGNED_TO": "Tilldelad till", - "CREATED_BY": "Skapad av", - "CUSTOM_FILTERS": "Anpassad filter" - }, - "CONFIRM_DELETE": { - "TITLE": "Ta bort anpassad filter.", - "MESSAGE": "anpassad filter '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Typ", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Sök - {{projectName}}", "PAGE_DESCRIPTION": "Sök på vad som helst, användarhistorier, uppgifter, ärenden och wiki-innehåll i projektet {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Användarhistorier", "FILTER_ISSUES": "Ärenden", "FILTER_TASKS": "Uppgift", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} har skapat en ny uppgift {{obj_name}} i {{project_name}} som hör till US {{us_name}}", "WIKI_CREATED": "{{username}} skapade en ny wiki-sida {{obj_name}} i {{project_name}}", "MILESTONE_CREATED": "{{username}} har skapad en ny sprint {{obj_name}} i {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} skapade projektet {{project_name}}", "MILESTONE_UPDATED": "{{username}} har uppdaterad sprinten {{obj_name}}", "US_UPDATED": "{{username}} har uppdaterad egenskapen \"{{field_name}}\" i US {{obj_name}}", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} har uppdaterad egenskapen \"{{field_name}}\" för uppgiften {{obj_name}} som tillhör US {{us_name}}", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} har uppdaterad egenskapen \"{{field_name}}\" för uppgiften {{obj_name}} som tillhör US {{us_name}} till {{new_value}}", "WIKI_UPDATED": "{{username}} har uppdaterad wiki-sidan {{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} har kommenterad i {{obj_name}}", "NEW_COMMENT_ISSUE": "{{username}} har kommenterad i ärendet {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} har kommenterad uppgiften {{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} har en ny medlem", "US_ADDED_MILESTONE": "{{username}} har lagt till US {{obj_name}} till {{sprint_name}}", "US_MOVED": "{{username}} har flyttat US {{obj_name}}", diff --git a/app/locales/taiga/locale-tr.json b/app/locales/taiga/locale-tr.json index 296a85f0..4e8ca811 100644 --- a/app/locales/taiga/locale-tr.json +++ b/app/locales/taiga/locale-tr.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "Her satıra bir kalem...", "NEW_BULK": "Yeni toplu ekleme", "RELATED_TASKS": "İlişkili görevler", + "PREVIOUS": "Previous", + "NEXT": "İleri", "LOGOUT": "Çıkış", "EXTERNAL_USER": "bir dış kullanıcı", "GENERIC_ERROR": "Honki ponkilerimizden biri derki; {{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "Bu değer geçersiz gözüküyor", "TYPE_EMAIL": "Bu değer geçerli bir e-posta adresi olmalı.", @@ -115,6 +122,7 @@ "USER_STORY": "Kullanıcı hikayesi", "TASK": "Görev", "ISSUE": "Sorun", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "Ben O'yum! Etiketle beni...", "DELETE": "Etiket sil", @@ -193,12 +201,27 @@ "CONFIRM_DELETE": "Bu özel alandaki tüm bilgiler silinecek.\nDevam etmek istediğinize emin misiniz?" }, "FILTERS": { - "TITLE": "filtreler", + "TITLE": "Filtreler", "INPUT_PLACEHOLDER": "Konu yada referans", "TITLE_ACTION_FILTER_BUTTON": "ara", - "BREADCRUMB_TITLE": "kategorilere dön", - "BREADCRUMB_FILTERS": "Filtreler", - "BREADCRUMB_STATUS": "durum" + "INPUT_SEARCH_PLACEHOLDER": "Konu ya da ref", + "TITLE_ACTION_SEARCH": "Ara", + "ACTION_SAVE_CUSTOM_FILTER": "özel filtre olarak kaydet", + "PLACEHOLDER_FILTER_NAME": "Filtre adı yazın ve enter a basın", + "CATEGORIES": { + "TYPE": "Tip", + "STATUS": "Durum ", + "SEVERITY": "Önem Derecesi", + "PRIORITIES": "Öncelikler", + "TAGS": "Etiketler ", + "ASSIGNED_TO": "Atanmış", + "CREATED_BY": "Oluşturan", + "CUSTOM_FILTERS": "Özel filtreler" + }, + "CONFIRM_DELETE": { + "TITLE": "Özel filtre sil", + "MESSAGE": "'{{customFilterName}}' özel filtresi" + } }, "WYSIWYG": { "H1_BUTTON": "İlk Düzey Başlık", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Markdown yazım kılavuzu" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "Koşular", "VIEW_SPRINTS": "Koşuları gör", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "İzleniyor", "DASHBOARD": "Proje Panosu" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "Atama Yok" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "Oylar", + "NAME": "İsim", + "PROJECT": "Proje", + "SPRINT": "Koşu", + "ASSIGNED_TO": "Assigned", + "STATUS": "Durum ", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "Engelli", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "Projelerim - Taiga", "PAGE_DESCRIPTION": "Tüm projelerinizi içeren bir liste, yenide düzenle ya da yeni bir tane yarat.", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "Değeri düzenle", - "TITLE_ACTION_DELETE_VALUE": "Değer sil" + "TITLE_ACTION_DELETE_VALUE": "Değer sil", + "TITLE_ACTION_DELETE_TAG": "Etiket sil" }, "HELP": "Yardıma mı ihtiyacın var? Destek sayfamızı kontrol edin!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "Modüller", "ENABLE": "Etkinleştir", "DISABLE": "Pasifleştir", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Havuz", "BACKLOG_DESCRIPTION": "Yeni gelen ve önceliklendirilmiş işler için düzenli bir görünüm elde etmek için kullanıcı hikayelerinizi yönetin.", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "CSV veri erişim linkini değiştireceksiniz. Önceki link kapatılacak. Emin misiniz?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "kullanıcı hikayeleri raporları", "SECTION_TITLE_TASK": "görevlere ait raporlar", "SECTION_TITLE_ISSUE": "sorun raporları", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "Özel Alanlar", "SUBTITLE": "Hikayeleriniz, işleriniz ve sorunlarınız için özel alanları tanımlayın", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "Kullanıcı hikayeleri özel alanları", "US_ADD": "Kullanıcı hikayelerine özel bir alan ekleyin", "TASK_DESCRIPTION": "Görevlere ait özel alanlar", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "Durum", "SUBTITLE": "Hikayeleriniz, işleriniz ve sorunlarınızın alabileceği durumları tanımlayın", - "US_TITLE": "KH Durumları", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "Görev Durumları", "ISSUE_TITLE": "Sorun Durumları" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "Etiketler ", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "Etiket ekle", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "Roller - {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "davetiye {{email}} " }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "Puan seçici için varsayılan değer", - "LABEL_US": "KH durum seçici için varsayılan değer", "LABEL_TASK_STATUS": "Görev durum seçici için varsayılan değer", - "LABEL_PRIORITY": "Önceli seçicisi için varsayılan değer", - "LABEL_SEVERITY": "Önem derecesi seçicisi için varsayılan değer", "LABEL_ISSUE_TYPE": "Sorun tipi seçici için varsayılan değer", - "LABEL_ISSUE_STATUS": "Sorun durumu seçici için varsayılan değer" + "LABEL_ISSUE_STATUS": "Sorun durumu seçici için varsayılan değer", + "LABEL_PRIORITY": "Önceli seçicisi için varsayılan değer", + "LABEL_SEVERITY": "Önem derecesi seçicisi için varsayılan değer" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "Yeni durum için bir isim yaz" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "Hepsini göster", "FILTER_TYPE_PROJECTS": "Projeler", "FILTER_TYPE_PROJECT_TITLES": "Sadece projeleri görüntüle", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Hikayeler", "FILTER_TYPE_USER_STORIES_TITLES": "Sadece kullanıcı hikayelerini göster", "FILTER_TYPE_TASKS": "Görevler", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(Opsiyonel) Davetinize kişiselleştirilmiş bir metin ekleyin. Yeni üyelerinize tatlı bir şeyler söyleyin ;-)", "PLACEHOLDER_TYPE_EMAIL": "Bir e-posta girin", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - Kullanıcı Hikayesi {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Durum: {{userStoryStatus }}. Tamamlanan {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). Puanlar: {{userStoryPoints}}. Tanım: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "Bu KH 'ni oluşturulduğu", "GO_TO_EXTERNAL_REFERENCE": "Kökenine git ", "BLOCKED": "Bu kullanıcı hikayesi engelli", - "PREVIOUS": "önceki kullanıcı hikayesi", - "NEXT": "sonraki kullanıcı hikayesi", "TITLE_DELETE_ACTION": "Kullanıcı Hikayesi Sil", "LIGHTBOX_TITLE_BLOKING_US": "Bizi engelleyen", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} tamamlanan görevler", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "koşu sırası", "KANBAN_ORDER": "kanban sırası", "TASKBOARD_ORDER": "Görev panosu sırası", - "US_ORDER": "kh sırası" + "US_ORDER": "kh sırası", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "Filtreler", "REMOVE": "Filtreleri Sil", "HIDE": "Filtreleri Gizle", - "SHOW": "Filtreleri Göster", - "FILTER_CATEGORY_STATUS": "Durum", - "FILTER_CATEGORY_TAGS": "Etiketler " + "SHOW": "Filtreleri Göster" }, "SPRINTS": { "TITLE": "KOŞULAR", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "Bu görevin oluşturulduğu", "TITLE_LINK_GO_ORIGIN": "Kullanıcı hikayesine git", "BLOCKED": "Bu iş engelli", - "PREVIOUS": "önceki görev", - "NEXT": "sonraki görev", "TITLE_DELETE_ACTION": "Görev Sil", "LIGHTBOX_TITLE_BLOKING_TASK": "Engelleyen iş", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "Sorun", "ACTION_NEW_ISSUE": "+ YENİ SORUN", "ACTION_PROMOTE_TO_US": "Kullanıcı Hikayesine Terfi Ettir", - "PLACEHOLDER_FILTER_NAME": "Filtre adı yazın ve enter a basın", "PROMOTED": "Bu sorun, kullanıcı hikayesine yükseltildi:", "EXTERNAL_REFERENCE": "Bu talebin oluşturulduğu ", "GO_TO_EXTERNAL_REFERENCE": "Kökenine git", "BLOCKED": "Bu sorun engelli", - "TITLE_PREVIOUS_ISSUE": "önceki sorun", - "TITLE_NEXT_ISSUE": "sonraki sorun", "ACTION_DELETE": "Sorun sil", "LIGHTBOX_TITLE_BLOKING_ISSUE": "Engelleyen sorun", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "Bu talebi yeni bir kullanıcı hikayesi olacak şekilde terfi ettirin", "MESSAGE": "Bu sorundan yeni bir hikaye oluşturmak istediğinize emin misiniz?" }, - "FILTERS": { - "TITLE": "Filtreler", - "INPUT_SEARCH_PLACEHOLDER": "Konu ya da ref", - "TITLE_ACTION_SEARCH": "Ara", - "ACTION_SAVE_CUSTOM_FILTER": "özel filtre olarak kaydet", - "BREADCRUMB": "Filtreler", - "TITLE_BREADCRUMB": "Filtreler", - "CATEGORIES": { - "TYPE": "Tip", - "STATUS": "Durum", - "SEVERITY": "Önem Derecesi", - "PRIORITIES": "Öncelikler", - "TAGS": "Etiketler", - "ASSIGNED_TO": "Atanmış", - "CREATED_BY": "Oluşturan", - "CUSTOM_FILTERS": "Özel filtreler" - }, - "CONFIRM_DELETE": { - "TITLE": "Özel filtre sil", - "MESSAGE": "'{{customFilterName}}' özel filtresi" - } - }, "TABLE": { "COLUMNS": { "TYPE": "Tip", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "Ara - {{projectName}}", "PAGE_DESCRIPTION": "Projedeki hikayeleri, sorunları, işleri, viki sayfalarını ya da herhangi bir şeyi arayın {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "Kullanıcı Hikayeleri", "FILTER_ISSUES": "Sorunlar ", "FILTER_TASKS": "Görevler", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": " {{project_name}} projesinde yer alan {{us_name}} adlı KH ya ait yeni bir görev {{obj_name}}, {{username}} tarafından oluşturuldu", "WIKI_CREATED": "{{project_name}} projesindeki yeni wiki sayfası {{obj_name}}, {{username}} tarafından oluşturuldu", "MILESTONE_CREATED": "{{project_name}} projesinde {{obj_name}} koşusu, {{username}} tarafından oluşturuldu", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{project_name}} proje {{username}} tarafından oluşturuldu", "MILESTONE_UPDATED": " {{obj_name}} koşusu {{username}} tarafından güncellendi", "US_UPDATED": "{{username}} kullanıcısı, {{obj_name}} KH 'sinin \"{{field_name}}\" alanını güncelledi. ", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{us_name}} adlı KH'ye ait {{obj_name}} talebinin \"{{field_name}}\" özniteliği {{username}} tarafından güncellendi. ", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}}, {{us_name}} hikayesindeki {{obj_name}} işinin {{field_name}} özelliğini {{new_value}} olacak şekilde değiştirdi", "WIKI_UPDATED": "{{obj_name}} adlı wiki sayfası {{username}} tarafından güncellendi", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": " {{obj_name}} KH'sine {{username}} tarafından yorum yapıldı", "NEW_COMMENT_ISSUE": " {{obj_name}} talebine {{username}} tarafından yorum yapıldı", "NEW_COMMENT_TASK": " {{obj_name}} görevine {{username}} tarafından yorum yapıldı", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} projesi yeni bir üyeye sahip", "US_ADDED_MILESTONE": " {{username}}, {{sprint_name}} koşusuna {{obj_name}} hikayesini ekledi", "US_MOVED": "{{username}}, {{obj_name}} hikayesini taşıdı", diff --git a/app/locales/taiga/locale-zh-hant.json b/app/locales/taiga/locale-zh-hant.json index 1ed57f83..0dd3f5af 100644 --- a/app/locales/taiga/locale-zh-hant.json +++ b/app/locales/taiga/locale-zh-hant.json @@ -35,6 +35,8 @@ "ONE_ITEM_LINE": "一行一物 ", "NEW_BULK": "新批次插入", "RELATED_TASKS": "相關任務 ", + "PREVIOUS": "Previous", + "NEXT": "下一個", "LOGOUT": "登出", "EXTERNAL_USER": "外部使用者", "GENERIC_ERROR": "我們的系統指出{{error}}.", @@ -45,6 +47,11 @@ "CAPSLOCK_WARNING": "Be careful! You are using capital letters in an input field that is case sensitive.", "CONFIRM_CLOSE_EDIT_MODE_TITLE": "Are you sure you want to close the edit mode?", "CONFIRM_CLOSE_EDIT_MODE_MESSAGE": "Remember that if you close the edit mode without saving all the changes will be lost", + "RELATED_USERSTORIES": "Related user stories", + "CARD": { + "ASSIGN_TO": "Assign To", + "EDIT": "Edit card" + }, "FORM_ERRORS": { "DEFAULT_MESSAGE": "該數值似乎為無效", "TYPE_EMAIL": "該電子郵件應為有效地址", @@ -115,6 +122,7 @@ "USER_STORY": "使用者故事", "TASK": "任務", "ISSUE": "問題", + "EPIC": "Epic", "TAGS": { "PLACEHOLDER": "我在這裏,請標注我", "DELETE": "刪除Tag", @@ -196,9 +204,24 @@ "TITLE": "過濾器", "INPUT_PLACEHOLDER": "主旨或參考", "TITLE_ACTION_FILTER_BUTTON": "搜尋", - "BREADCRUMB_TITLE": "回到類別", - "BREADCRUMB_FILTERS": "過濾器", - "BREADCRUMB_STATUS": "狀態" + "INPUT_SEARCH_PLACEHOLDER": "主旨或參考", + "TITLE_ACTION_SEARCH": "搜尋", + "ACTION_SAVE_CUSTOM_FILTER": "儲存為客製過濾器 ", + "PLACEHOLDER_FILTER_NAME": "寫入過濾器名稱後按下enter ", + "CATEGORIES": { + "TYPE": "類型", + "STATUS": "狀態", + "SEVERITY": "急迫性", + "PRIORITIES": "優先性", + "TAGS": "標籤", + "ASSIGNED_TO": "指派給 ", + "CREATED_BY": "由創建", + "CUSTOM_FILTERS": "客製過濾器 " + }, + "CONFIRM_DELETE": { + "TITLE": "刪除客製過濾器 ", + "MESSAGE": "預設過濾器 '{{customFilterName}}'" + } }, "WYSIWYG": { "H1_BUTTON": "第一層標頭 ", @@ -232,6 +255,14 @@ "MARKDOWN_HELP": "Markdown 語法協助" }, "PERMISIONS_CATEGORIES": { + "EPICS": { + "NAME": "Epics", + "VIEW_EPICS": "View epics", + "ADD_EPICS": "Add epics", + "MODIFY_EPICS": "Modify epics", + "COMMENT_EPICS": "Comment epics", + "DELETE_EPICS": "Delete epics" + }, "SPRINTS": { "NAME": "衝刺任務", "VIEW_SPRINTS": "檢視衝刺任務 ", @@ -370,6 +401,41 @@ "WATCHING_SECTION": "觀看中", "DASHBOARD": "專案控制台" }, + "EPICS": { + "TITLE": "EPICS", + "SECTION_NAME": "Epics", + "EPIC": "EPIC", + "PAGE_TITLE": "Epics - {{projectName}}", + "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "DASHBOARD": { + "ADD": "+ ADD EPIC", + "UNASSIGNED": "未指派" + }, + "EMPTY": { + "TITLE": "It looks like you have not created any epics yet", + "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "HELP": "Learn more about epics" + }, + "TABLE": { + "VOTES": "投票數", + "NAME": "名稱 ", + "PROJECT": "專案", + "SPRINT": "衝刺任務", + "ASSIGNED_TO": "Assigned", + "STATUS": "狀態", + "PROGRESS": "Progress", + "VIEW_OPTIONS": "View options" + }, + "CREATE": { + "TITLE": "New Epic", + "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", + "TEAM_REQUIREMENT": "Team requirement", + "CLIENT_REQUIREMENT": "Client requirement", + "BLOCKED": "已封鎖", + "BLOCKED_NOTE_PLACEHOLDER": "Why is this epic blocked?", + "CREATE_EPIC": "Create epic" + } + }, "PROJECTS": { "PAGE_TITLE": "我的專案 - Taiga", "PAGE_DESCRIPTION": "你的專案列表,你可以記錄或創建新專案。", @@ -406,7 +472,8 @@ "ADMIN": { "COMMON": { "TITLE_ACTION_EDIT_VALUE": "編輯數值", - "TITLE_ACTION_DELETE_VALUE": "删除值" + "TITLE_ACTION_DELETE_VALUE": "删除值", + "TITLE_ACTION_DELETE_TAG": "刪除Tag" }, "HELP": "需要幫助嗎?看看我們的支援頁面吧!", "PROJECT_DEFAULT_VALUES": { @@ -439,6 +506,8 @@ "TITLE": "模組", "ENABLE": "啟用", "DISABLE": "停用", + "EPICS": "Epics", + "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "待辦任務優先表", "BACKLOG_DESCRIPTION": "管理你的 User Story 讓接下來的及優先的工作能被有條理地檢視 ", "NUMBER_SPRINTS": "Expected number of sprints", @@ -501,6 +570,7 @@ "REGENERATE_SUBTITLE": "你將要改變CSV資料的連結網址,之前的網址將失效。你確定要這樣做嗎?" }, "CSV": { + "SECTION_TITLE_EPIC": "epics reports", "SECTION_TITLE_US": "使用者故事報告", "SECTION_TITLE_TASK": "任務報告", "SECTION_TITLE_ISSUE": "問題報告", @@ -513,6 +583,8 @@ "CUSTOM_FIELDS": { "TITLE": "客製化欄位", "SUBTITLE": "指定使用者故事,任務與問題一些客製化欄位", + "EPIC_DESCRIPTION": "Epics custom fields", + "EPIC_ADD": "Add a custom field in epics", "US_DESCRIPTION": "使用者客製欄位", "US_ADD": "在使用者故事中加入客制欄位", "TASK_DESCRIPTION": "任務客製化欄位", @@ -550,7 +622,8 @@ "PROJECT_VALUES_STATUS": { "TITLE": "狀態", "SUBTITLE": "指明你的使用者故事狀態,任務以及經歷問題 ", - "US_TITLE": "使用者故事狀態", + "EPIC_TITLE": "Epic Statuses", + "US_TITLE": "User Story Statuses", "TASK_TITLE": "任務狀態", "ISSUE_TITLE": "問題狀態" }, @@ -562,9 +635,14 @@ }, "PROJECT_VALUES_TAGS": { "TITLE": "標籤", - "SUBTITLE": "View and edit the color of your user stories", + "SUBTITLE": "View and edit the color of your tags", "EMPTY": "Currently there are no tags", - "EMPTY_SEARCH": "It looks like nothing was found with your search criteria" + "EMPTY_SEARCH": "It looks like nothing was found with your search criteria", + "ACTION_ADD": "新增標籤", + "NEW_TAG": "New tag", + "MIXING_HELP_TEXT": "Select the tags that you want to merge", + "MIXING_MERGE": "Merge Tags", + "SELECTED": "Selected" }, "ROLES": { "PAGE_TITLE": "角色- {{projectName}}", @@ -657,13 +735,14 @@ "DEFAULT_DELETE_MESSAGE": "邀請 {{email}}" }, "DEFAULT_VALUES": { + "LABEL_EPIC_STATUS": "Default value for epic status selector", + "LABEL_US_STATUS": "Default value for user story status selector", "LABEL_POINTS": "點數選擇器預設值", - "LABEL_US": "使用者故事狀態選擇器預設值", "LABEL_TASK_STATUS": "任務狀態選擇器預設值", - "LABEL_PRIORITY": "優先選擇器預設值", - "LABEL_SEVERITY": "急迫性選擇器預設值", "LABEL_ISSUE_TYPE": "問題類型選擇器預設值", - "LABEL_ISSUE_STATUS": "問題狀態選擇器預設值" + "LABEL_ISSUE_STATUS": "問題狀態選擇器預設值", + "LABEL_PRIORITY": "優先選擇器預設值", + "LABEL_SEVERITY": "急迫性選擇器預設值" }, "STATUS": { "PLACEHOLDER_WRITE_STATUS_NAME": "為此新狀態命名" @@ -766,6 +845,8 @@ "FILTER_TYPE_ALL_TITLE": "顯示全部", "FILTER_TYPE_PROJECTS": "專案", "FILTER_TYPE_PROJECT_TITLES": "只顯示專案", + "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "故事", "FILTER_TYPE_USER_STORIES_TITLES": "只顯視使用者故事", "FILTER_TYPE_TASKS": "任務 ", @@ -965,8 +1046,8 @@ "CREATE_MEMBER": { "PLACEHOLDER_INVITATION_TEXT": "(非必要) 加上一段私人文字在邀請信,告訴你的新成員一些好事 ;-)", "PLACEHOLDER_TYPE_EMAIL": "輸入一個電郵地址", - "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "Unfortunately, this project can't have more than {{maxMembers}} members.
If you would like to increase the current limit, please contact the administrator.", - "LIMIT_USERS_WARNING_MESSAGE": "Unfortunately, this project can't have more than {{maxMembers}} members." + "LIMIT_USERS_WARNING_MESSAGE_FOR_OWNER": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members. If you would like to increase the current limit, please contact the administrator.", + "LIMIT_USERS_WARNING_MESSAGE": "You are about to reach the maximum number of members allowed for this project, {{maxMembers}} members." }, "LEAVE_PROJECT_WARNING": { "TITLE": "Unfortunately, this project can't be left without an owner", @@ -985,6 +1066,25 @@ "BUTTON": "Ask this project member to become the new project owner" } }, + "EPIC": { + "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", + "SECTION_NAME": "Epic", + "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", + "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", + "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "CREATE_RELATED_USERSTORIES": "Create a relationship with", + "NEW_USERSTORY": "New user story", + "EXISTING_USERSTORY": "Existing user story", + "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", + "SUBJECT": "Subject", + "SUBJECT_BULK_MODE": "Subject (bulk insert)", + "CHOOSE_PROJECT_FROM": "What's the project?", + "CHOOSE_USERSTORY": "What's the user story?", + "FILTER_USERSTORIES": "Filter user stories", + "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", + "ACTION_DELETE": "Delete epic" + }, "US": { "PAGE_TITLE": "{{userStorySubject}} - 使用者故事 {{userStoryRef}} - {{projectName}}", "PAGE_DESCRIPTION": "狀態: {{userStoryStatus }}.已完成 {{userStoryProgressPercentage}}% ({{userStoryClosedTasks}} of {{userStoryTotalTasks}} tasks closed). 點數: {{userStoryPoints}}. 描述: {{userStoryDescription}}", @@ -999,8 +1099,6 @@ "EXTERNAL_REFERENCE": "此使用者故事創造者是", "GO_TO_EXTERNAL_REFERENCE": "回到一開始", "BLOCKED": "這個使用者故事已被封鎖", - "PREVIOUS": "之前的使用者故事", - "NEXT": "下一個使用者故事", "TITLE_DELETE_ACTION": "刪除使用者故事", "LIGHTBOX_TITLE_BLOKING_US": "封鎖中的使用者故事", "TASK_COMPLETED": "{{totalClosedTasks}}/{{totalTasks}} 任務完成", @@ -1103,7 +1201,8 @@ "SPRINT_ORDER": "衝刺任務次序", "KANBAN_ORDER": "kanban看板次序", "TASKBOARD_ORDER": "任務板次序", - "US_ORDER": "使用者故事次序" + "US_ORDER": "使用者故事次序", + "COLOR": "color" } }, "BACKLOG": { @@ -1169,9 +1268,7 @@ "TITLE": "過濾器", "REMOVE": "移除過濾器", "HIDE": "隱藏過濾器", - "SHOW": "顯示過濾器", - "FILTER_CATEGORY_STATUS": "狀態", - "FILTER_CATEGORY_TAGS": "標籤" + "SHOW": "顯示過濾器" }, "SPRINTS": { "TITLE": "衝刺任務", @@ -1236,8 +1333,6 @@ "ORIGIN_US": "此任務創造者是", "TITLE_LINK_GO_ORIGIN": "到使用者故事", "BLOCKED": "這任務已被封鎖", - "PREVIOUS": "之前的任務 ", - "NEXT": "下一個任務 ", "TITLE_DELETE_ACTION": "刪除任務", "LIGHTBOX_TITLE_BLOKING_TASK": "封鎖中的任務 ", "FIELDS": { @@ -1278,13 +1373,10 @@ "SECTION_NAME": "問題", "ACTION_NEW_ISSUE": "+ 新問題 ", "ACTION_PROMOTE_TO_US": "提昇到使用者故事", - "PLACEHOLDER_FILTER_NAME": "寫入過濾器名稱後按下enter ", "PROMOTED": "此問題已提昇成使用者故事 ", "EXTERNAL_REFERENCE": "此問題的提供者是", "GO_TO_EXTERNAL_REFERENCE": "回到一開始", "BLOCKED": "這個議題已被封鎖", - "TITLE_PREVIOUS_ISSUE": "之前的問題 ", - "TITLE_NEXT_ISSUE": "下一個問題 ", "ACTION_DELETE": "删除議題 ", "LIGHTBOX_TITLE_BLOKING_ISSUE": "封鎖中的問題", "FIELDS": { @@ -1296,28 +1388,6 @@ "TITLE": "將此問題提到使用者故事", "MESSAGE": "你確定此問題要創建一個新的使用者故事?" }, - "FILTERS": { - "TITLE": "過濾器", - "INPUT_SEARCH_PLACEHOLDER": "主旨或參考", - "TITLE_ACTION_SEARCH": "搜尋", - "ACTION_SAVE_CUSTOM_FILTER": "儲存為客製過濾器 ", - "BREADCRUMB": "過濾器", - "TITLE_BREADCRUMB": "過濾器", - "CATEGORIES": { - "TYPE": "類型", - "STATUS": "狀態", - "SEVERITY": "急迫性", - "PRIORITIES": "優先性", - "TAGS": "標籤", - "ASSIGNED_TO": "指派給 ", - "CREATED_BY": "由創建", - "CUSTOM_FILTERS": "客製過濾器 " - }, - "CONFIRM_DELETE": { - "TITLE": "刪除客製過濾器 ", - "MESSAGE": "預設過濾器 '{{customFilterName}}'" - } - }, "TABLE": { "COLUMNS": { "TYPE": "類型", @@ -1363,6 +1433,7 @@ "SEARCH": { "PAGE_TITLE": "搜尋 - {{projectName}}", "PAGE_DESCRIPTION": "專案搜尋(使用者故事, 問題, 任務或維基頁等資訊) {{projectName}}: {{projectDescription}}", + "FILTER_EPICS": "Epics", "FILTER_USER_STORIES": "使用者故事", "FILTER_ISSUES": "問題 ", "FILTER_TASKS": "任務 ", @@ -1505,6 +1576,8 @@ "TASK_CREATED_WITH_US": "{{username}} 創建新任務 {{obj_name}} 於 {{project_name}} ,其為{{us_name}}之使用者故事", "WIKI_CREATED": "{{username}} 創建新維基頁 {{obj_name}} 於 {{project_name}}", "MILESTONE_CREATED": "{{username}} 創建新衝刺任務 {{obj_name}} 於 {{project_name}}", + "EPIC_CREATED": "{{username}} has created a new epic {{obj_name}} in {{project_name}}", + "EPIC_RELATED_USERSTORY_CREATED": "{{username}} has related the userstory {{related_us_name}} to the epic {{epic_name}} in {{project_name}}", "NEW_PROJECT": "{{username}} 創建專案 {{project_name}}", "MILESTONE_UPDATED": "{{username}}更新衝刺任務 {{obj_name}} ", "US_UPDATED": "{{username}} 已更新 {{obj_name}}使用者故事之 \"{{field_name}}\"屬性。", @@ -1517,9 +1590,13 @@ "TASK_UPDATED_WITH_US": "{{username}} 更新了 {{obj_name}} 任務之\"{{field_name}}\" 屬性,其為 {{us_name}} 之使用者故事", "TASK_UPDATED_WITH_US_NEW_VALUE": "{{username}} 已更新了 {{obj_name}} 下的 {{us_name}} 使用者故事\"{{field_name}}\"屬性到{{new_value}}", "WIKI_UPDATED": "\n{{username}} 更新了維基頁{{obj_name}}", + "EPIC_UPDATED": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}}", + "EPIC_UPDATED_WITH_NEW_VALUE": "{{username}} has updated the attribute \"{{field_name}}\" of the epic {{obj_name}} to {{new_value}}", + "EPIC_UPDATED_WITH_NEW_COLOR": "{{username}} has updated the \"{{field_name}}\" of the epic {{obj_name}} to ", "NEW_COMMENT_US": "{{username}} 評論了 {{obj_name}}使用者故事", "NEW_COMMENT_ISSUE": "{{username}}評論了此問題 {{obj_name}}", "NEW_COMMENT_TASK": "{{username}} 評論了此任務{{obj_name}}", + "NEW_COMMENT_EPIC": "{{username}} has commented in the epic {{obj_name}}", "NEW_MEMBER": "{{project_name}} 有新成員", "US_ADDED_MILESTONE": "{{username}} 增加使用者故事 {{obj_name}} 給 {{sprint_name}}", "US_MOVED": "{{username}} 搬移了使用者故事 {{obj_name}}", From c5a18c6dd1df067b6920e4da9b20bf0845d46261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 12:25:57 +0200 Subject: [PATCH 243/315] Fix typo in epic permissions section in the admin panel --- app/coffee/modules/admin/roles.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/coffee/modules/admin/roles.coffee b/app/coffee/modules/admin/roles.coffee index c282f7c6..11fb5f4c 100644 --- a/app/coffee/modules/admin/roles.coffee +++ b/app/coffee/modules/admin/roles.coffee @@ -357,7 +357,7 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm, $compile) -> { key: "view_epics", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.VIEW_EPICS"} { key: "add_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.ADD_EPICS"} { key: "modify_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.MODIFY_EPICS"} - { key: "comment_epic", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.COMMENT_EPICS"} + { key: "comment_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.COMMENT_EPICS"} { key: "delete_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.DELETE_EPICS"} ] categories.push({ From 61d8cb60757049cffc2052d6ca1e580876e78737 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 12:43:02 +0200 Subject: [PATCH 244/315] Fixing initial on color edition in tags admin --- app/partials/includes/modules/admin/project-tags.jade | 1 + 1 file changed, 1 insertion(+) diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index 61538b00..e64007c3 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -113,6 +113,7 @@ section tg-color-selector.color-column( is-color-required="false" ng-model="tag" + init-color="tag.color" on-select-color="tag.color = color" ) From 6031e19691ac09440162e05bd6ea84b47e0a2513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 21 Sep 2016 12:58:26 +0200 Subject: [PATCH 245/315] Remove assigned and load indicator --- .../epics/dashboard/epic-row/epic-row.controller.coffee | 5 ++++- .../epics/dashboard/epic-row/epic-row.directive.coffee | 2 +- app/modules/epics/dashboard/epic-row/epic-row.jade | 7 +++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee index 65a82333..76c8ca24 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.controller.coffee @@ -72,8 +72,11 @@ class EpicRowController @.loadingStatus = false updateAssignedTo: (member) -> - return @epicsService.updateEpicAssignedTo(@.epic, member?.id) + @.assignLoader = true + return @epicsService.updateEpicAssignedTo(@.epic, member?.id or null) .catch () => @confirm.notify('error') + .then () => + @.assignLoader = false angular.module("taigaEpics").controller("EpicRowCtrl", EpicRowController) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee index b1a6c85d..cf85a32b 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee +++ b/app/modules/epics/dashboard/epic-row/epic-row.directive.coffee @@ -25,7 +25,7 @@ EpicRowDirective = () -> bindToController: true, scope: { epic: '=', - column: '=', + column: '=' } } diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 829ecf7a..1952641c 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -32,11 +32,14 @@ .sprint(ng-if="vm.column.sprint") - .assigned.e2e-assigned-tio(ng-if="vm.column.assigned") + .assigned.e2e-assigned-to( + ng-if="vm.column.assigned" + tg-loading="vm.assignLoader" + ) tg-assigned-to-component( assigned-to="vm.epic.get('assigned_to_extra_info')" project="vm.project" - on-remove-assigned="vm.updateAssignedTo(null)" + on-remove-assigned="vm.updateAssignedTo()" on-assign-to="vm.updateAssignedTo(member)" tg-isolate-click ) From 60b7f4a1b9a312ec338eab270ceee18cfc540eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 21 Sep 2016 13:01:58 +0200 Subject: [PATCH 246/315] Remove drag icon if user has no permission --- app/modules/epics/dashboard/epic-row/epic-row.jade | 1 + 1 file changed, 1 insertion(+) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 1952641c..583c6238 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -4,6 +4,7 @@ ) tg-svg.icon-drag( svg-icon="icon-drag" + ng-if="vm.canEditEpics()" ) .vote( From 3f686d4c52337d3c5f08230e7cb299ae08da10de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 21 Sep 2016 13:03:54 +0200 Subject: [PATCH 247/315] Avoid removing name from epics list --- app/modules/epics/dashboard/epics-table/epics-table.jade | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 31d29bb0..7ab6a81b 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -19,7 +19,6 @@ mixin epicSwitch(name, model) ) .name( translate="EPICS.TABLE.NAME" - ng-if="vm.column.name" ) .project( translate="EPICS.TABLE.PROJECT" @@ -52,12 +51,6 @@ mixin epicSwitch(name, model) for="epicSwitch-votes" ) +epicSwitch('switch-votes', 'vm.column.votes') - .fieldset - label.epics-table-options-vote( - translate="EPICS.TABLE.NAME" - for="switch-name" - ) - +epicSwitch('switch-name', 'vm.column.name') .fieldset label.epics-table-options-vote( translate="EPICS.TABLE.PROJECT" From c6b5228d544c304da6d2f2798c022c96f6bb0592 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 13:18:08 +0200 Subject: [PATCH 248/315] Fixing color when creating tags on admin area --- .../components/color-selector/color-selector.directive.coffee | 3 ++- app/partials/includes/modules/admin/project-tags.jade | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee index fc9df40a..cf5aa8f2 100644 --- a/app/modules/components/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -44,7 +44,8 @@ ColorSelectorDirective = ($timeout) -> .mouseenter(cancel) .mouseleave(close) - bindOnce scope, 'vm.initColor', (color) -> + scope.$watch 'vm.initColor', (color) -> + # We can't just bind once because sometimes the initial color is reset from the outside ctrl.setColor(color) return { diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index e64007c3..83b6fc78 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -8,7 +8,7 @@ section form.add-tag-container.new-value.hidden tg-color-selector.color-column( is-color-required="false" - ng-model="newValue" + init-color="newValue.color" on-select-color="newValue.color = color" ) From 5e6d29b6098dda8912800cdbbd30a8ecf1dd3e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 21 Sep 2016 13:32:13 +0200 Subject: [PATCH 249/315] Change cursor depending on epic contents --- app/modules/epics/dashboard/epic-row/epic-row.jade | 3 ++- app/modules/epics/dashboard/epic-row/epic-row.scss | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/modules/epics/dashboard/epic-row/epic-row.jade b/app/modules/epics/dashboard/epic-row/epic-row.jade index 583c6238..7497be1c 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.jade +++ b/app/modules/epics/dashboard/epic-row/epic-row.jade @@ -1,5 +1,6 @@ + .epic-row.e2e-epic-row( - ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed'), 'unfold': vm.displayUserStories}" + ng-class="{'is-blocked': vm.epic.get('is_blocked'), 'is-closed': vm.epic.get('is_closed'), 'unfold': vm.displayUserStories, 'not-empty': vm.epic.getIn(['user_stories_counts', 'opened']) || vm.epic.getIn(['user_stories_counts', 'closed'])}" ng-click="vm.toggleUserStoryList()" ) tg-svg.icon-drag( diff --git a/app/modules/epics/dashboard/epic-row/epic-row.scss b/app/modules/epics/dashboard/epic-row/epic-row.scss index 7c1d7814..0f7205a0 100644 --- a/app/modules/epics/dashboard/epic-row/epic-row.scss +++ b/app/modules/epics/dashboard/epic-row/epic-row.scss @@ -6,7 +6,7 @@ align-items: center; background: $white; border-bottom: 1px solid $whitish; - cursor: pointer; + cursor: move; display: flex; transition: background .2s; &:hover { @@ -15,6 +15,9 @@ opacity: 1; } } + &.not-empty { + cursor: pointer; + } &.is-blocked { background: rgba($red-light, .5); } From 2c6db751aa37d60861aaac6a0015073c5baf9043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 13:40:25 +0200 Subject: [PATCH 250/315] Prevent event propagation when press ENTER in the color selector --- .../color-selector/color-selector.controller.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee index a52ed21b..e183adfe 100644 --- a/app/modules/components/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -29,7 +29,7 @@ class ColorSelectorController checkIsColorRequired: () -> if !@.isColorRequired - @.colorList = _.dropRight(@.colorList); + @.colorList = _.dropRight(@.colorList) setColor: (color) -> @.color = @.initColor @@ -49,9 +49,9 @@ class ColorSelectorController onKeyDown: (event) -> if event.which == 13 # ENTER - event.stopPropagation() if @.color or not @.isColorRequired @.onSelectDropdownColor(@.color) + event.preventDefault() angular.module('taigaComponents').controller("ColorSelectorCtrl", ColorSelectorController) From 0023f528a994afafe211c4e9c552d0409ba3dff3 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 13:46:25 +0200 Subject: [PATCH 251/315] Fixing epic links in rich content --- app/coffee/modules/common/wisiwyg.coffee | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/common/wisiwyg.coffee b/app/coffee/modules/common/wisiwyg.coffee index 40f53264..15c0d689 100644 --- a/app/coffee/modules/common/wisiwyg.coffee +++ b/app/coffee/modules/common/wisiwyg.coffee @@ -374,7 +374,7 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans search: (term, callback) -> term = taiga.slugify(term) - searchTypes = ['issues', 'tasks', 'userstories'] + searchTypes = ['issues', 'tasks', 'userstories', 'epics'] searchProps = ['ref', 'subject'] filter = (item) => @@ -384,8 +384,7 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans return false cancelablePromise.abort() if cancelablePromise - - cancelablePromise = $rs.search.do($scope.projectId, term) + cancelablePromise = $rs.search.do($scope.projectId || $scope.vm.projectId, term) cancelablePromise.then (res) => # ignore wikipages if they're the only results. can't exclude them in search @@ -441,15 +440,18 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans search: (term, callback) -> term = taiga.slugify(term) - $rs.search.do($scope.projectId, term).then (res) => + $rs.search.do($scope.projectId || $scope.vm.projectId, term).then (res) => if res.count < 1 + console.log 1 callback([]) if res.count < 1 or not res.wikipages or res.wikipages.length <= 0 + console.log 2 callback([]) else callback res.wikipages.filter((page) => + console.log 3 return taiga.slugify(page['slug']).indexOf(term) >= 0 ), true From dc48e3e2c6c5506bc58c19d075dde4ca025d1ac8 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 13:51:07 +0200 Subject: [PATCH 252/315] Removing unncesary console.log --- app/coffee/modules/common/wisiwyg.coffee | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/coffee/modules/common/wisiwyg.coffee b/app/coffee/modules/common/wisiwyg.coffee index 15c0d689..ee143b59 100644 --- a/app/coffee/modules/common/wisiwyg.coffee +++ b/app/coffee/modules/common/wisiwyg.coffee @@ -442,16 +442,13 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans $rs.search.do($scope.projectId || $scope.vm.projectId, term).then (res) => if res.count < 1 - console.log 1 callback([]) if res.count < 1 or not res.wikipages or res.wikipages.length <= 0 - console.log 2 callback([]) else callback res.wikipages.filter((page) => - console.log 3 return taiga.slugify(page['slug']).indexOf(term) >= 0 ), true From ebe7f479d276f9a570ce9e5e6f81c9f22bbf7b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 13:51:27 +0200 Subject: [PATCH 253/315] Fix tests --- .../color-selector/color-selector.controller.spec.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee index a8c76c80..e402b8c8 100644 --- a/app/modules/components/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -64,12 +64,12 @@ describe "ColorSelector", -> colorSelectorCtrl = controller "ColorSelectorCtrl" colorSelectorCtrl.onSelectDropdownColor = sinon.stub() - event = {which: 13, stopPropagation: sinon.stub()} + event = {which: 13, preventDefault: sinon.stub()} color = "#fabada" colorSelectorCtrl.color = color colorSelectorCtrl.onKeyDown(event) - expect(event.stopPropagation).have.been.called + expect(event.preventDefault).have.been.called expect(colorSelectorCtrl.onSelectDropdownColor).have.been.called expect(colorSelectorCtrl.onSelectDropdownColor).have.been.calledWith(color) From d15cc477e8fd7b0505b8a3b97cf129aa5f4379ee Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 14:04:08 +0200 Subject: [PATCH 254/315] Removing required on new related us form --- .../related-userstories-create.jade | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade index 9f549da1..84e3898c 100644 --- a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade @@ -49,7 +49,6 @@ a.add-button.e2e-add-userstory-button( ng-model="selectedProject" ng-change="selectProject(selectedProject)" data-required="true" - required ng-options="p.id as p.name for p in vm.projects | toMutable" id="project-selector-dropdown" ) @@ -88,20 +87,20 @@ a.add-button.e2e-add-userstory-button( ) label.e2e-bulk-creation-label(for="bulk-new-user-stories") tg-svg(svg-icon="icon-bulk") - + form.new-user-story-form .single-creation(ng-show="creationMode=='single-new-user-story'") input.e2e-new-userstory-input-text( type="text" ng-model="relatedUserstoriesText" - required + data-required="true" ) .bulk-creation(ng-show="creationMode=='bulk-new-user-stories'") textarea.e2e-new-userstories-input-textarea( ng-model="relatedUserstoriesText" - required + data-required="true" ) button.button-green.create-user-story.e2e-create-userstory-button.ng-animate-disabled( @@ -129,7 +128,7 @@ a.add-button.e2e-add-userstory-button( select.userstory.e2e-userstories-select( size="5" ng-model="selectedUserstory" - required + data-required="true" ) - var hash = "#"; option.hidden( From e8be87e16fce87189e776d629a8a74918045b092 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 14:17:02 +0200 Subject: [PATCH 255/315] Fixing project selected on epic related userstories edition --- .../related-userstories-create/related-userstories-create.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade index 84e3898c..159e594d 100644 --- a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade @@ -1,6 +1,6 @@ a.add-button.e2e-add-userstory-button( href="" - ng-click="showLightbox(vm.project.get('id'))" + ng-click="showLightbox(selectedProject)" ) tg-svg(svg-icon="icon-add") From be35cddd6bde66df51f221e45c0c13496df97268 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 21 Sep 2016 14:22:02 +0200 Subject: [PATCH 256/315] Fixing assigned to activity visualization on detail views --- .../history/history/history-templates/history-assigned.jade | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/modules/history/history/history-templates/history-assigned.jade b/app/modules/history/history/history-templates/history-assigned.jade index ab57ac18..64fb23a6 100644 --- a/app/modules/history/history/history-templates/history-assigned.jade +++ b/app/modules/history/history/history-templates/history-assigned.jade @@ -3,7 +3,11 @@ translate="ACTIVITY.FIELDS.ASSIGNED_TO" ) span.diff(ng-if="vm.diff[0]") {{vm.diff[0]}} + span.diff(ng-if="!vm.diff[0]" translate="ACTIVITY.VALUES.UNASSIGNED") + tg-svg( svg-icon="icon-arrow-right" ) + span.diff(ng-if="vm.diff[1]") {{vm.diff[1]}} + span.diff(ng-if="!vm.diff[1]" translate="ACTIVITY.VALUES.UNASSIGNED") From d186463e5d49b8c834dce698b33af700c066472c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 14:27:28 +0200 Subject: [PATCH 257/315] Fix placeholder for empty epics dashboard --- app/locales/taiga/locale-en.json | 4 ++-- app/modules/epics/dashboard/epics-dashboard.jade | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 86bb1688..4fa858e9 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -414,8 +414,8 @@ "UNASSIGNED": "Unassigned" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { diff --git a/app/modules/epics/dashboard/epics-dashboard.jade b/app/modules/epics/dashboard/epics-dashboard.jade index 50b88b81..46f751a3 100644 --- a/app/modules/epics/dashboard/epics-dashboard.jade +++ b/app/modules/epics/dashboard/epics-dashboard.jade @@ -27,7 +27,7 @@ p(translate="EPICS.EMPTY.EXPLANATION") a( translate="EPICS.EMPTY.HELP" - href="#TODO: Link to Epics section in taiga-support" + href="https://tree.taiga.io/support/epics/what-is-an-epic/" target="_blank" ng-title="EPICS.EMPTY.HELP | translate" ) From 4c3710cf4f3102dc9a7828b00f2adf632e5beee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Wed, 21 Sep 2016 16:05:44 +0200 Subject: [PATCH 258/315] Display placeholder if projects have no US --- app/locales/taiga/locale-en.json | 1 + .../related-userstories-create.jade | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 4fa858e9..1be0721f 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1083,6 +1083,7 @@ "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" diff --git a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade index 159e594d..f5cabb13 100644 --- a/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade +++ b/app/modules/epics/related-userstories/related-userstories-create/related-userstories-create.jade @@ -110,8 +110,13 @@ a.add-button.e2e-add-userstory-button( translate="COMMON.SAVE" ng-show="relatedWithSelector=='new-user-story'" ) + + p( + ng-show="relatedWithSelector=='existing-user-story' && !vm.projectUserstories.size" + translate="EPIC.NO_USERSTORIES" + ) - fieldset.existing-user-story(ng-show="relatedWithSelector=='existing-user-story'") + fieldset.existing-user-story(ng-show="relatedWithSelector=='existing-user-story' && vm.projectUserstories.size") label( translate="EPIC.CHOOSE_USERSTORY" for="userstory-filter" From 4c89ff36697423094798f1109b2b012776f4524c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 16:13:00 +0200 Subject: [PATCH 259/315] Disable color selector if the user doesn\t have permission to edit epic --- .../color-selector/color-selector.controller.coffee | 10 +++++++++- .../color-selector/color-selector.directive.coffee | 3 ++- .../components/color-selector/color-selector.jade | 10 +++++++++- .../components/color-selector/color-selector.scss | 3 +++ app/partials/epic/epic-detail.jade | 1 + 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.controller.coffee b/app/modules/components/color-selector/color-selector.controller.coffee index e183adfe..47838f23 100644 --- a/app/modules/components/color-selector/color-selector.controller.coffee +++ b/app/modules/components/color-selector/color-selector.controller.coffee @@ -22,11 +22,19 @@ getDefaulColorList = taiga.getDefaulColorList class ColorSelectorController - constructor: () -> + @.$inject = [ + "tgProjectService", + ] + + constructor: (@projectService) -> @.colorList = getDefaulColorList() @.checkIsColorRequired() @.displayColorList = false + userCanChangeColor: () -> + return true if not @.requiredPerm + return @projectService.hasPermission(@.requiredPerm) + checkIsColorRequired: () -> if !@.isColorRequired @.colorList = _.dropRight(@.colorList) diff --git a/app/modules/components/color-selector/color-selector.directive.coffee b/app/modules/components/color-selector/color-selector.directive.coffee index cf5aa8f2..f020e408 100644 --- a/app/modules/components/color-selector/color-selector.directive.coffee +++ b/app/modules/components/color-selector/color-selector.directive.coffee @@ -56,7 +56,8 @@ ColorSelectorDirective = ($timeout) -> bindToController: { isColorRequired: "=", onSelectColor: "&", - initColor: "=" + initColor: "=", + requiredPerm: "@" }, scope: {}, } diff --git a/app/modules/components/color-selector/color-selector.jade b/app/modules/components/color-selector/color-selector.jade index 7a472f02..d4213b62 100644 --- a/app/modules/components/color-selector/color-selector.jade +++ b/app/modules/components/color-selector/color-selector.jade @@ -1,4 +1,12 @@ -.color-selector +//- Read only mode +.color-selector(ng-if="!vm.userCanChangeColor()") + .tag-color.disabled.e2e-open-color-selector( + ng-class="{'empty-color': !vm.color}" + ng-style="{'background': vm.color}" + ) + +//- Read & Edit mode +.color-selector(ng-if="vm.userCanChangeColor()") .tag-color.e2e-open-color-selector( ng-click="vm.toggleColorList()" ng-class="{'empty-color': !vm.color}" diff --git a/app/modules/components/color-selector/color-selector.scss b/app/modules/components/color-selector/color-selector.scss index 6bf35ad9..3f5a9bc0 100644 --- a/app/modules/components/color-selector/color-selector.scss +++ b/app/modules/components/color-selector/color-selector.scss @@ -18,6 +18,9 @@ border-radius: 0; margin: 0; transition: background .3s ease-out; + &.disabled { + cursor: auto; + } &.empty-color { @include empty-color(34); } diff --git a/app/partials/epic/epic-detail.jade b/app/partials/epic/epic-detail.jade index dcaa775e..52bb688c 100644 --- a/app/partials/epic/epic-detail.jade +++ b/app/partials/epic/epic-detail.jade @@ -23,6 +23,7 @@ div.wrapper( is-color-required="true" init-color="epic.color" on-select-color="ctrl.onSelectColor(color)" + required-perm="modify_epic" ) tg-detail-header( item="epic" From 2389bf4785f7a1cb9408cc47d696303711e4d44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 16:23:26 +0200 Subject: [PATCH 260/315] Fix tests --- .../color-selector/color-selector.controller.spec.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/modules/components/color-selector/color-selector.controller.spec.coffee b/app/modules/components/color-selector/color-selector.controller.spec.coffee index e402b8c8..5fe727f6 100644 --- a/app/modules/components/color-selector/color-selector.controller.spec.coffee +++ b/app/modules/components/color-selector/color-selector.controller.spec.coffee @@ -23,9 +23,17 @@ describe "ColorSelector", -> colorSelectorCtrl = null mocks = {} + _mockTgProjectService = () -> + mocks.tgProjectService = { + hasPermission: sinon.stub() + } + provide.value "tgProjectService", mocks.tgProjectService + _mocks = () -> module ($provide) -> provide = $provide + _mockTgProjectService() + return null beforeEach -> From b7cc9e9c1e4cd5197f38d7052870a87ba3061a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 21 Sep 2016 21:10:27 +0200 Subject: [PATCH 261/315] Fix related userstories view for users without edit permission --- .../related-userstories-controller.coffee | 10 +++++-- .../related-userstories.jade | 2 +- .../related-userstory-row.jade | 5 +++- .../related-userstory-row.scss | 30 ++++++++++--------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/app/modules/epics/related-userstories/related-userstories-controller.coffee b/app/modules/epics/related-userstories/related-userstories-controller.coffee index 0ab2fd1f..d2624ec6 100644 --- a/app/modules/epics/related-userstories/related-userstories-controller.coffee +++ b/app/modules/epics/related-userstories/related-userstories-controller.coffee @@ -20,12 +20,18 @@ module = angular.module("taigaEpics") class RelatedUserStoriesController - @.$inject = ["tgEpicsService"] + @.$inject = [ + "tgProjectService", + "tgEpicsService" + ] - constructor: (@epicsService) -> + constructor: (@projectService, @epicsService) -> @.sectionName = "Epics" @.showCreateRelatedUserstoriesLightbox = false + userCanSort: () -> + return @projectService.hasPermission("modify_epic") + loadRelatedUserstories: () -> @epicsService.listRelatedUserStories(@.epic) .then (userstories) => diff --git a/app/modules/epics/related-userstories/related-userstories.jade b/app/modules/epics/related-userstories/related-userstories.jade index 35a848b7..e1f8a79e 100644 --- a/app/modules/epics/related-userstories/related-userstories.jade +++ b/app/modules/epics/related-userstories/related-userstories.jade @@ -15,7 +15,7 @@ section.related-userstories ) tg-related-userstory-row.row( tg-repeat="us in vm.userstories track by us.get('id')" - ng-class="{closed: us.get('is_closed'), blocked: us.get('is_blocked')}" + ng-class="{closed: us.get('is_closed'), blocked: us.get('is_blocked'), sortable: vm.userCanSort()}" userstory="us" epic="vm.epic" project="vm.project" diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade index c7790332..a4d74a88 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade @@ -1,5 +1,6 @@ tg-svg.icon-drag( svg-icon="icon-drag" + tg-check-permission="modify_epic" ) .userstory-name @@ -33,7 +34,9 @@ tg-svg.icon-drag( ) .status - span.userstory-status(ng-style="{'color': vm.userstory.getIn(['status_extra_info', 'color'])}") {{vm.userstory.getIn(['status_extra_info', 'name'])}} + span.userstory-status( + ng-style="{'color': vm.userstory.getIn(['status_extra_info', 'color'])}" + ) {{vm.userstory.getIn(['status_extra_info', 'name'])}} .assigned-to-column figure.avatar diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss index ad3cabc3..c9fc2f32 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.scss @@ -2,26 +2,28 @@ tg-related-userstory-row { @include font-size(small); align-items: center; border-bottom: 1px solid $whitish; - cursor: move; display: flex; padding: .5rem 0 .5rem .5rem; - &:hover { - background: rgba($primary-light, .05); - .userstory-settings { - opacity: 1; - transition: all .2s ease-in; + &.sortable { + cursor: move; + &:hover { + background: rgba($primary-light, .05); + .userstory-settings { + opacity: 1; + transition: all .2s ease-in; + } + .icon-drag { + opacity: 1; + } } .icon-drag { - opacity: 1; + @include svg-size(.75rem); + cursor: move; + fill: $whitish; + opacity: 0; + transition: opacity .1s; } } - .icon-drag { - @include svg-size(.75rem); - cursor: move; - fill: $whitish; - opacity: 0; - transition: opacity .1s; - } .status { flex-shrink: 0; position: relative; From 62a20552a4aad406ccb278bbaf4239b54b9ad1e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 22 Sep 2016 11:06:09 +0200 Subject: [PATCH 262/315] Color selector fixes for transparent color --- .../components/color-selector/color-selector.jade | 11 ++++++----- .../components/color-selector/color-selector.scss | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/modules/components/color-selector/color-selector.jade b/app/modules/components/color-selector/color-selector.jade index d4213b62..dda5becc 100644 --- a/app/modules/components/color-selector/color-selector.jade +++ b/app/modules/components/color-selector/color-selector.jade @@ -28,11 +28,12 @@ .display-custom-color.empty-color( ng-if="!vm.color" ) - .display-custom-color( - ng-if="vm.color" - ng-style="{'background': vm.color}" - ng-click="vm.onSelectDropdownColor(vm.color)" - ) + .display-custom-color-wrapper + .display-custom-color( + ng-if="vm.color" + ng-style="{'background': vm.color}" + ng-click="vm.onSelectDropdownColor(vm.color)" + ) input.custom-color-input( type="text" maxlength="7" diff --git a/app/modules/components/color-selector/color-selector.scss b/app/modules/components/color-selector/color-selector.scss index 3f5a9bc0..98cf62fd 100644 --- a/app/modules/components/color-selector/color-selector.scss +++ b/app/modules/components/color-selector/color-selector.scss @@ -52,16 +52,20 @@ } .custom-color-selector { + align-items: center; display: flex; .custom-color-input { margin: 0; width: 100%; } + .display-custom-color-wrapper { + background: $mass-white; + margin-right: .5rem; + } .display-custom-color { @include color-selector-option; flex-shrink: 0; margin: 0; - margin-right: .5rem; &.empty-color { @include empty-color(34); cursor: default; From 2cc8b2e458d3e87cf1fccf867fe188339f62cd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 22 Sep 2016 11:47:25 +0200 Subject: [PATCH 263/315] Show history section and its tabs only when it's needed --- .../history-tabs/history-tabs.directive.coffee | 3 ++- app/modules/history/history-tabs/history-tabs.jade | 4 +++- app/modules/history/history.controller.coffee | 12 +++++++++++- app/modules/history/history.jade | 6 +++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/modules/history/history-tabs/history-tabs.directive.coffee b/app/modules/history/history-tabs/history-tabs.directive.coffee index fdadb250..e7e48e74 100644 --- a/app/modules/history/history-tabs/history-tabs.directive.coffee +++ b/app/modules/history/history-tabs/history-tabs.directive.coffee @@ -20,10 +20,11 @@ module = angular.module('taigaHistory') HistoryTabsDirective = () -> - return { templateUrl:"history/history-tabs/history-tabs.html", scope: { + showCommentTab: "&", + showActivityTab: "&" onActiveComments: "&", onActiveActivities: "&", onOrderComments: "&" diff --git a/app/modules/history/history-tabs/history-tabs.jade b/app/modules/history/history-tabs/history-tabs.jade index deb458de..d9e25e30 100644 --- a/app/modules/history/history-tabs/history-tabs.jade +++ b/app/modules/history/history-tabs/history-tabs.jade @@ -1,5 +1,6 @@ nav.history-tabs a.history-tab.e2e-comments-tab( + ng-if="showCommentTab()" href="" title="{{COMMENTS.COMMENT}}" ng-click="onActiveComments()" @@ -8,6 +9,7 @@ nav.history-tabs translate-values="{comments: commentsNum}" ) a.history-tab.e2e-activity-tab( + ng-if="showActivityTab()" href="" title="Activities" ng-click="onActiveActivities()" @@ -22,7 +24,7 @@ nav.history-tabs ng-class="{'new-first': top, 'old-first': !top}" ng-if="commentsNum > 1 && activeTab" ) - + span( translate="COMMENTS.OLDER_FIRST" ng-if="onReverse" diff --git a/app/modules/history/history.controller.coffee b/app/modules/history/history.controller.coffee index c419fd64..d80a5ec0 100644 --- a/app/modules/history/history.controller.coffee +++ b/app/modules/history/history.controller.coffee @@ -24,9 +24,10 @@ class HistorySectionController "$tgResources", "$tgRepo", "$tgStorage", + "tgProjectService", ] - constructor: (@rs, @repo, @storage) -> + constructor: (@rs, @repo, @storage, @projectService) -> @.editing = null @.deleting = null @.editMode = {} @@ -49,6 +50,15 @@ class HistorySectionController @.activities = _.filter(activities, (item) -> Object.keys(item.values_diff).length > 0) @.activitiesNum = @.activities.length + showHistorySection: () -> + return @.showCommentTab() or @.showActivityTab() + + showCommentTab: () -> + return @.commentsNum > 0 or @projectService.hasPermission("comment_#{@.name}") + + showActivityTab: () -> + return @.activitiesNum > 0 + toggleEditMode: (commentId) -> @.editMode[commentId] = !@.editMode[commentId] diff --git a/app/modules/history/history.jade b/app/modules/history/history.jade index 86ee999a..eaa9af8d 100644 --- a/app/modules/history/history.jade +++ b/app/modules/history/history.jade @@ -1,5 +1,9 @@ -section.history +section.history( + ng-if="vm.showHistorySection()" +) tg-history-tabs( + show-comment-tab="vm.showCommentTab()" + show-activity-tab="vm.showActivityTab()" on-active-comments="vm.onActiveHistoryTab(true)" on-active-activities="vm.onActiveHistoryTab(false)" active-tab="vm.viewComments", From dd72cc880784232c6ffe3672c7f75ff0df8ed029 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 11:49:27 +0200 Subject: [PATCH 264/315] Hidding filters when empty --- app/coffee/modules/controllerMixins.coffee | 7 ++++++- app/coffee/modules/issues/list.coffee | 8 +++++++- app/coffee/modules/taskboard/main.coffee | 7 ++++++- app/modules/components/filter/filter.jade | 8 +++++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/controllerMixins.coffee b/app/coffee/modules/controllerMixins.coffee index 08d0ac50..bf0290b7 100644 --- a/app/coffee/modules/controllerMixins.coffee +++ b/app/coffee/modules/controllerMixins.coffee @@ -221,6 +221,10 @@ class UsFiltersMixin it.id = it.name return it + + tagsWithAtLeastOneElement = _.filter tags, (tag) -> + return tag.count > 0 + assignedTo = _.map data.assigned_to, (it) -> if it.id it.id = it.id.toString() @@ -266,7 +270,8 @@ class UsFiltersMixin title: @translate.instant("COMMON.FILTERS.CATEGORIES.TAGS"), dataType: "tags", content: tags, - hideEmpty: true + hideEmpty: true, + totalTaggedElements: tagsWithAtLeastOneElement.length }, { title: @translate.instant("COMMON.FILTERS.CATEGORIES.ASSIGNED_TO"), diff --git a/app/coffee/modules/issues/list.coffee b/app/coffee/modules/issues/list.coffee index c1172408..81b577a5 100644 --- a/app/coffee/modules/issues/list.coffee +++ b/app/coffee/modules/issues/list.coffee @@ -188,6 +188,10 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi it.id = it.name return it + + tagsWithAtLeastOneElement = _.filter tags, (tag) -> + return tag.count > 0 + assignedTo = _.map data.assigned_to, (it) -> if it.id it.id = it.id.toString() @@ -259,7 +263,9 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi { title: @translate.instant("COMMON.FILTERS.CATEGORIES.TAGS"), dataType: "tags", - content: tags + content: tags, + hideEmpty: true, + totalTaggedElements: tagsWithAtLeastOneElement.length }, { title: @translate.instant("COMMON.FILTERS.CATEGORIES.ASSIGNED_TO"), diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee index 1a13a106..97252b9b 100644 --- a/app/coffee/modules/taskboard/main.coffee +++ b/app/coffee/modules/taskboard/main.coffee @@ -160,6 +160,10 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga it.id = it.name return it + + tagsWithAtLeastOneElement = _.filter tags, (tag) -> + return tag.count > 0 + assignedTo = _.map data.assigned_to, (it) -> if it.id it.id = it.id.toString() @@ -205,7 +209,8 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga title: @translate.instant("COMMON.FILTERS.CATEGORIES.TAGS"), dataType: "tags", content: tags, - hideEmpty: true + hideEmpty: true, + totalTaggedElements: tagsWithAtLeastOneElement.length }, { title: @translate.instant("COMMON.FILTERS.CATEGORIES.ASSIGNED_TO"), diff --git a/app/modules/components/filter/filter.jade b/app/modules/components/filter/filter.jade index 05760586..db7f53b1 100644 --- a/app/modules/components/filter/filter.jade +++ b/app/modules/components/filter/filter.jade @@ -49,7 +49,9 @@ form li( ng-class="{selected: vm.isOpen(filter.dataType)}" ng-repeat="filter in vm.filters track by filter.dataType" + ng-if="!(filter.hideEmpty && filter.totalTaggedElements === 0)" ) + a.filters-cat-single.e2e-category( ng-class="{selected: vm.isOpen(filter.dataType)}" ng-click="vm.toggleFilterCategory(filter.dataType)" @@ -78,7 +80,11 @@ form span.name(ng-attr-style="{{it.color ? 'border-left: 3px solid ' + it.color: ''}}") {{it.name}} span.number.e2e-filter-count(ng-if="it.count > 0") {{it.count}} - li.custom-filters.e2e-custom-filters(ng-class="{selected: vm.isOpen('custom-filter')}") + li.custom-filters.e2e-custom-filters( + ng-class="{selected: vm.isOpen('custom-filter')}" + ng-if="vm.customFilters.length > 0" + ) + a.filters-cat-single( ng-class="{selected: vm.isOpen('custom-filter')}" ng-click="vm.toggleFilterCategory('custom-filter')" From 1d9a405a768e3185b265ed8133537aa0eedeaade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 22 Sep 2016 12:03:01 +0200 Subject: [PATCH 265/315] Fix broken tests --- .../related-userstories.controller.spec.coffee | 7 +++++++ app/modules/history/history.controller.spec.coffee | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee index 30611140..6c82137e 100644 --- a/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee +++ b/app/modules/epics/related-userstories/related-userstories.controller.spec.coffee @@ -31,10 +31,17 @@ describe "RelatedUserStories", -> provide.value "tgEpicsService", mocks.tgEpicsService + _mockTgProjectService = () -> + mocks.tgProjectService = { + hasPermission: sinon.stub() + } + provide.value "tgProjectService", mocks.tgProjectService + _mocks = () -> module ($provide) -> provide = $provide _mockTgEpicsService() + _mockTgProjectService() return null beforeEach -> diff --git a/app/modules/history/history.controller.spec.coffee b/app/modules/history/history.controller.spec.coffee index 6194cc5c..2a97b2ca 100644 --- a/app/modules/history/history.controller.spec.coffee +++ b/app/modules/history/history.controller.spec.coffee @@ -48,12 +48,19 @@ describe "HistorySection", -> } provide.value "$tgStorage", mocks.tgStorage + _mockTgProjectService = () -> + mocks.tgProjectService = { + hasPermission: sinon.stub() + } + provide.value "tgProjectService", mocks.tgProjectService + _mocks = () -> module ($provide) -> provide = $provide _mockTgResources() _mockTgRepo() _mocktgStorage() + _mockTgProjectService() return null beforeEach -> From a0e95d36cae1ad69d61e00a8a8fd9d271817e74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 22 Sep 2016 12:14:24 +0200 Subject: [PATCH 266/315] Show related user stories section only if it's needed --- .../related-userstories/related-userstories-controller.coffee | 3 +++ .../epics/related-userstories/related-userstories.jade | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/modules/epics/related-userstories/related-userstories-controller.coffee b/app/modules/epics/related-userstories/related-userstories-controller.coffee index d2624ec6..07319495 100644 --- a/app/modules/epics/related-userstories/related-userstories-controller.coffee +++ b/app/modules/epics/related-userstories/related-userstories-controller.coffee @@ -29,6 +29,9 @@ class RelatedUserStoriesController @.sectionName = "Epics" @.showCreateRelatedUserstoriesLightbox = false + showRelatedUserStoriesSection: () -> + return @projectService.hasPermission("modify_epic") or @.userstories?.legth > 0 + userCanSort: () -> return @projectService.hasPermission("modify_epic") diff --git a/app/modules/epics/related-userstories/related-userstories.jade b/app/modules/epics/related-userstories/related-userstories.jade index e1f8a79e..1cbbe21b 100644 --- a/app/modules/epics/related-userstories/related-userstories.jade +++ b/app/modules/epics/related-userstories/related-userstories.jade @@ -1,4 +1,6 @@ -section.related-userstories +section.related-userstories( + ng-if="vm.showRelatedUserStoriesSection()" +) .related-userstories-header span.related-userstories-title(translate="COMMON.RELATED_USERSTORIES") tg-related-userstories-create( From 2950020beab7b9c9d501852daee67da893a9e1e0 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 12:33:57 +0200 Subject: [PATCH 267/315] Filtering by epic in backlog and kanban --- app/coffee/modules/controllerMixins.coffee | 21 +++++++++++++++++++-- app/locales/taiga/locale-en.json | 3 ++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/coffee/modules/controllerMixins.coffee b/app/coffee/modules/controllerMixins.coffee index bf0290b7..a6c501cd 100644 --- a/app/coffee/modules/controllerMixins.coffee +++ b/app/coffee/modules/controllerMixins.coffee @@ -204,6 +204,7 @@ class UsFiltersMixin loadFilters.status = urlfilters.status loadFilters.assigned_to = urlfilters.assigned_to loadFilters.owner = urlfilters.owner + loadFilters.epic = urlfilters.epic loadFilters.q = urlfilters.q return @q.all([ @@ -221,10 +222,8 @@ class UsFiltersMixin it.id = it.name return it - tagsWithAtLeastOneElement = _.filter tags, (tag) -> return tag.count > 0 - assignedTo = _.map data.assigned_to, (it) -> if it.id it.id = it.id.toString() @@ -239,6 +238,15 @@ class UsFiltersMixin it.name = it.full_name return it + epic = _.map data.epics, (it) -> + if it.id + it.id = it.id.toString() + it.name = "##{it.ref} #{it.subject}" + else + it.id = "null" + it.name = "Not in an epic" + + return it @.selectedFilters = [] @@ -258,6 +266,10 @@ class UsFiltersMixin selected = @.formatSelectedFilters("owner", owner, loadFilters.owner) @.selectedFilters = @.selectedFilters.concat(selected) + if loadFilters.epic + selected = @.formatSelectedFilters("epic", epic, loadFilters.epic) + @.selectedFilters = @.selectedFilters.concat(selected) + @.filterQ = loadFilters.q @.filters = [ @@ -282,6 +294,11 @@ class UsFiltersMixin title: @translate.instant("COMMON.FILTERS.CATEGORIES.CREATED_BY"), dataType: "owner", content: owner + }, + { + title: @translate.instant("COMMON.FILTERS.CATEGORIES.EPIC"), + dataType: "epic", + content: epic } ] diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 1be0721f..31e0225b 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -218,7 +218,8 @@ "TAGS": "Tags", "ASSIGNED_TO": "Assigned to", "CREATED_BY": "Created by", - "CUSTOM_FILTERS": "Custom filters" + "CUSTOM_FILTERS": "Custom filters", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Delete custom filter", From ce35fc3a92f05cd433d81c1c697e90a27c6d358f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 22 Sep 2016 13:02:09 +0200 Subject: [PATCH 268/315] Edit color background --- app/modules/components/belong-to-epics/belong-to-epics.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.scss b/app/modules/components/belong-to-epics/belong-to-epics.scss index e068c2c6..01e05872 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.scss +++ b/app/modules/components/belong-to-epics/belong-to-epics.scss @@ -9,7 +9,7 @@ } .belong-to-epic-pill { - background-color: $grayer; + background-color: $mass-white; border-radius: 50%; display: inline-block; height: .7rem; From 5bffce6f89d2b3e0defeb265eae188d4e8e3382b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 13:04:34 +0200 Subject: [PATCH 269/315] Filter by epics --- app/coffee/modules/controllerMixins.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/coffee/modules/controllerMixins.coffee b/app/coffee/modules/controllerMixins.coffee index a6c501cd..5d8cef9f 100644 --- a/app/coffee/modules/controllerMixins.coffee +++ b/app/coffee/modules/controllerMixins.coffee @@ -204,7 +204,7 @@ class UsFiltersMixin loadFilters.status = urlfilters.status loadFilters.assigned_to = urlfilters.assigned_to loadFilters.owner = urlfilters.owner - loadFilters.epic = urlfilters.epic + loadFilters.epics = urlfilters.epics loadFilters.q = urlfilters.q return @q.all([ @@ -238,7 +238,7 @@ class UsFiltersMixin it.name = it.full_name return it - epic = _.map data.epics, (it) -> + epics = _.map data.epics, (it) -> if it.id it.id = it.id.toString() it.name = "##{it.ref} #{it.subject}" @@ -266,8 +266,8 @@ class UsFiltersMixin selected = @.formatSelectedFilters("owner", owner, loadFilters.owner) @.selectedFilters = @.selectedFilters.concat(selected) - if loadFilters.epic - selected = @.formatSelectedFilters("epic", epic, loadFilters.epic) + if loadFilters.epics + selected = @.formatSelectedFilters("epics", epics, loadFilters.epics) @.selectedFilters = @.selectedFilters.concat(selected) @.filterQ = loadFilters.q @@ -297,8 +297,8 @@ class UsFiltersMixin }, { title: @translate.instant("COMMON.FILTERS.CATEGORIES.EPIC"), - dataType: "epic", - content: epic + dataType: "epics", + content: epics } ] From cf2b2b4be7dd0d5ef7bc6cb4d8112e313a458455 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 13:32:06 +0200 Subject: [PATCH 270/315] Adding darker filter --- app/coffee/modules/common/filters.coffee | 29 +++++++++++++++++++ .../belong-to-epics/belong-to-epics-pill.jade | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/app/coffee/modules/common/filters.coffee b/app/coffee/modules/common/filters.coffee index 17696c55..e91de832 100644 --- a/app/coffee/modules/common/filters.coffee +++ b/app/coffee/modules/common/filters.coffee @@ -100,3 +100,32 @@ byRefFilter = ($filterFilter)-> return $filterFilter(userstories, filter) module.filter("byRef", ["filterFilter", byRefFilter]) + + +darkerFilter = -> + return (color, luminosity) -> + # validate hex string + console.log color + color = new String(color).replace(/[^0-9a-f]/gi, '') + console.log color + if color.length < 6 + color = color[0]+ color[0]+ color[1]+ color[1]+ color[2]+ color[2]; + + luminosity = luminosity || 0 + + # convert to decimal and change luminosity + newColor = "#" + c = 0 + i = 0 + black = 0 + white = 255 + # for (i = 0; i < 3; i++) + for i in [0, 1, 2] + c = parseInt(color.substr(i*2,2), 16) + c = Math.round(Math.min(Math.max(black, c + (luminosity * white)), white)).toString(16) + newColor += ("00"+c).substr(c.length) + + return newColor + + +module.filter("darker", darkerFilter) diff --git a/app/modules/components/belong-to-epics/belong-to-epics-pill.jade b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade index 2ff75ba3..fd861905 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics-pill.jade +++ b/app/modules/components/belong-to-epics/belong-to-epics-pill.jade @@ -1,6 +1,6 @@ - var hash = "#"; span.belong-to-epic-pill-wrapper(tg-repeat="epic in epics track by epic.get('id')") .belong-to-epic-pill( - ng-style="{'background': epic.get('color')}" + ng-style="{'background': epic.get('color'), 'border-color': '{{ epic.get('color') | darker: -0.2 }}'}" title="#{hash}{{epic.get('id')}} {{epic.get('subject')}}" ) From b0395724ed6d5f945047cb05f882e00807785911 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 14:41:33 +0200 Subject: [PATCH 271/315] Removing console.log --- app/coffee/modules/common/filters.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/coffee/modules/common/filters.coffee b/app/coffee/modules/common/filters.coffee index e91de832..c232d85d 100644 --- a/app/coffee/modules/common/filters.coffee +++ b/app/coffee/modules/common/filters.coffee @@ -105,9 +105,7 @@ module.filter("byRef", ["filterFilter", byRefFilter]) darkerFilter = -> return (color, luminosity) -> # validate hex string - console.log color color = new String(color).replace(/[^0-9a-f]/gi, '') - console.log color if color.length < 6 color = color[0]+ color[0]+ color[1]+ color[1]+ color[2]+ color[2]; From fffc14089898f64dfa101d93759a9c760515919e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 22 Sep 2016 15:18:19 +0200 Subject: [PATCH 272/315] Fixing pills when drag and drop --- .../belong-to-epics/belong-to-epics.directive.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee index 39eae9fb..91ffe19e 100644 --- a/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee +++ b/app/modules/components/belong-to-epics/belong-to-epics.directive.coffee @@ -22,8 +22,9 @@ module = angular.module('taigaEpics') BelongToEpicsDirective = () -> link = (scope, el, attrs) -> - if scope.epics && !scope.epics.isIterable - scope.epics = Immutable.fromJS(scope.epics) + scope.$watch 'epics', (epics) -> + if epics && !epics.isIterable + scope.epics = Immutable.fromJS(epics) templateUrl = (el, attrs) -> if attrs.format From a3a7b868ba8e66a3b16d00914eeef6f6e268bd81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 22 Sep 2016 16:18:20 +0200 Subject: [PATCH 273/315] Add timeout to hide epics table options --- .../epics-table/epics-table.controller.coffee | 12 ++++++++++-- .../epics/dashboard/epics-table/epics-table.jade | 9 +++++++-- .../epics/dashboard/epics-table/epics-table.scss | 6 ++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee index 45e41d45..4934b4d9 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee +++ b/app/modules/epics/dashboard/epics-table/epics-table.controller.coffee @@ -23,10 +23,11 @@ taiga = @.taiga class EpicsTableController @.$inject = [ "$tgConfirm", - "tgEpicsService" + "tgEpicsService", + "$timeout" ] - constructor: (@confirm, @epicsService) -> + constructor: (@confirm, @epicsService, @timeout) -> @.displayOptions = false @.displayVotes = true @.column = { @@ -49,4 +50,11 @@ class EpicsTableController .then null, () => # on error @confirm.notify("error") + hoverEpicTableOption: () -> + if @.timer + @timeout.cancel(@.timer) + + hideEpicTableOption: () -> + return @.timer = @timeout (=> @.displayOptions = false), 400 + angular.module("taigaEpics").controller("EpicsTableCtrl", EpicsTableController) diff --git a/app/modules/epics/dashboard/epics-table/epics-table.jade b/app/modules/epics/dashboard/epics-table/epics-table.jade index 7ab6a81b..fe99fcbd 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.jade +++ b/app/modules/epics/dashboard/epics-table/epics-table.jade @@ -40,11 +40,16 @@ mixin epicSwitch(name, model) translate="EPICS.TABLE.PROGRESS" ng-if="vm.column.progress" ) - .epics-table-options-wrapper(ng-mouseleave="vm.displayOptions = false") + .epics-table-options-wrapper( + ng-mouseleave="vm.hideEpicTableOption()" + ) button.epics-table-option-button.e2e-epics-column-button(ng-click="vm.displayOptions = true") span(translate="EPICS.TABLE.VIEW_OPTIONS") tg-svg(svg-icon="icon-arrow-down") - form.epics-table-dropdown.e2e-epics-column-dropdown(ng-show="vm.displayOptions") + form.epics-table-dropdown.e2e-epics-column-dropdown( + ng-show="vm.displayOptions" + ng-mouseenter="vm.keepEpicTableOption()" + ) .fieldset label.epics-table-options-vote( translate="EPICS.TABLE.VOTES" diff --git a/app/modules/epics/dashboard/epics-table/epics-table.scss b/app/modules/epics/dashboard/epics-table/epics-table.scss index 2651dde4..8814fbc0 100644 --- a/app/modules/epics/dashboard/epics-table/epics-table.scss +++ b/app/modules/epics/dashboard/epics-table/epics-table.scss @@ -44,6 +44,12 @@ top: 1.3rem; width: 250px; z-index: 99; + &.ng-hide-remove { + animation: dropdownFade .2s; + } + &.ng-hide-add { + animation: dropdownFade .2s reverse; + } .fieldset { @include font-size(small); border-bottom: 1px solid $whitish; From d3f5cfb02f94cd831bba5f46b2e139d9067c603b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 23 Sep 2016 07:59:44 +0200 Subject: [PATCH 274/315] Add broken link icon --- .../related-userstory-row/related-userstory-row.jade | 2 +- app/svg/sprite.svg | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade index a4d74a88..c54e09af 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.jade @@ -23,7 +23,7 @@ tg-svg.icon-drag( href="" ng-click="vm.onDeleteRelatedUserstory()" ) - tg-svg(svg-icon="icon-trash") + tg-svg(svg-icon="icon-broken-link") .project( tg-nav="project:project=vm.userstory.getIn(['project_extra_info', 'slug'])" diff --git a/app/svg/sprite.svg b/app/svg/sprite.svg index f6f2022f..869e7c00 100644 --- a/app/svg/sprite.svg +++ b/app/svg/sprite.svg @@ -450,5 +450,9 @@ Epics + + Broken Link + + From ffc04dcc41053dbca126d4b68bddfe41d9c17df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 23 Sep 2016 08:05:45 +0200 Subject: [PATCH 275/315] Change DELETE for UNLINK in unlink Story lightbox --- app/locales/taiga/locale-en.json | 6 +++--- .../related-userstory-row.controller.coffee | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json index 31e0225b..99a17c12 100644 --- a/app/locales/taiga/locale-en.json +++ b/app/locales/taiga/locale-en.json @@ -1073,9 +1073,9 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", "NEW_USERSTORY": "New user story", "EXISTING_USERSTORY": "Existing user story", diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee index ef58ab9b..c8513f9e 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee @@ -40,15 +40,15 @@ class RelatedUserstoryRowController return @translate.instant("COMMON.ASSIGNED_TO.NOT_ASSIGNED") onDeleteRelatedUserstory: () -> - title = @translate.instant('EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY') - message = @translate.instant('EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY', { + title = @translate.instant('EPIC.TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY') + message = @translate.instant('EPIC.MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY', { subject: @.userstory.get('subject') }) return @confirm.askOnDelete(title, message) .then (askResponse) => onError = () => - message = @translate.instant('EPIC.ERROR_DELETE_RELATED_USERSTORY', {errorMessage: message}) + message = @translate.instant('EPIC.ERROR_UNLINK_RELATED_USERSTORY', {errorMessage: message}) @confirm.notify("error", null, message) askResponse.finish(false) From ea7098cad789cb836ca567220ee0238e1218537f Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 23 Sep 2016 09:49:32 +0200 Subject: [PATCH 276/315] Removing cursor from e2e tests --- conf.e2e.js | 82 ++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/conf.e2e.js b/conf.e2e.js index cd421c09..d7ca0a01 100644 --- a/conf.e2e.js +++ b/conf.e2e.js @@ -53,55 +53,55 @@ var config = { onPrepare: function() { // disable by default because performance problems on IE // track mouse movements - var trackMouse = function() { - angular.module('trackMouse', []).run(function($document) { + // var trackMouse = function() { + // angular.module('trackMouse', []).run(function($document) { - function addDot(ev) { - var color = 'black', - size = 6; + // function addDot(ev) { + // var color = 'black', + // size = 6; - switch (ev.type) { - case 'click': - color = 'red'; - break; - case 'dblclick': - color = 'blue'; - break; - case 'mousemove': - color = 'green'; - break; - } + // switch (ev.type) { + // case 'click': + // color = 'red'; + // break; + // case 'dblclick': + // color = 'blue'; + // break; + // case 'mousemove': + // color = 'green'; + // break; + // } - var dotEl = $('
') - .css({ - position: 'fixed', - height: size + 'px', - width: size + 'px', - 'background-color': color, - top: ev.clientY, - left: ev.clientX, + // var dotEl = $('
') + // .css({ + // position: 'fixed', + // height: size + 'px', + // width: size + 'px', + // 'background-color': color, + // top: ev.clientY, + // left: ev.clientX, - 'z-index': 9999, + // 'z-index': 9999, - // make sure this dot won't interfere with the mouse events of other elements - 'pointer-events': 'none' - }) - .appendTo('body'); + // // make sure this dot won't interfere with the mouse events of other elements + // 'pointer-events': 'none' + // }) + // .appendTo('body'); - setTimeout(function() { - dotEl.remove(); - }, 1000); - } + // setTimeout(function() { + // dotEl.remove(); + // }, 1000); + // } - $document.on({ - click: addDot, - dblclick: addDot, - mousemove: addDot - }); + // $document.on({ + // click: addDot, + // dblclick: addDot, + // mousemove: addDot + // }); - }); - }; - browser.addMockModule('trackMouse', trackMouse); + // }); + // }; + // browser.addMockModule('trackMouse', trackMouse); browser.params.glob.back = argv.back; From 4cdd1653e9846953fd76c17073526f506a9ed3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 23 Sep 2016 11:05:01 +0200 Subject: [PATCH 277/315] Minor style errors --- app/modules/components/filter/filter.scss | 2 +- app/modules/components/vote-button/vote-button.jade | 10 ++++------ app/styles/modules/backlog/sprints.scss | 5 +++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/modules/components/filter/filter.scss b/app/modules/components/filter/filter.scss index 4283e97b..1d78ca71 100644 --- a/app/modules/components/filter/filter.scss +++ b/app/modules/components/filter/filter.scss @@ -1,5 +1,5 @@ tg-filter { - background-color: $whitish; + background-color: $mass-white; display: block; left: 0; min-height: 100%; diff --git a/app/modules/components/vote-button/vote-button.jade b/app/modules/components/vote-button/vote-button.jade index 3ae180ac..1259c10d 100644 --- a/app/modules/components/vote-button/vote-button.jade +++ b/app/modules/components/vote-button/vote-button.jade @@ -8,17 +8,15 @@ a.vote-inner( ng-mouseover="vm.showTextWhenMouseIsOver()" ng-mouseleave="vm.showTextWhenMouseIsLeave()" ) - span.track-icon - tg-svg(svg-icon="icon-upvote") - span.track-button-counter( + tg-svg(svg-icon="icon-upvote") + span( title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.total_voters||0}:'messageformat' }}", tg-loading="vm.loading" ) {{ vm.item.total_voters }} //- Anonymous user button span.vote-inner(ng-if="::!vm.user") - span.track-icon - tg-svg(svg-icon="icon-watch") - span.track-button-counter( + tg-svg(svg-icon="icon-upvote") + span( title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.total_voters||0}:'messageformat' }}" ) {{ ::vm.item.total_voters }} diff --git a/app/styles/modules/backlog/sprints.scss b/app/styles/modules/backlog/sprints.scss index 82deb18a..040cadba 100644 --- a/app/styles/modules/backlog/sprints.scss +++ b/app/styles/modules/backlog/sprints.scss @@ -78,10 +78,11 @@ } } .number { - @include font-size(small); + @include font-size(xsmall); + margin-right: .2rem; } .description { - @include font-size(x-small); + @include font-size(xsmall); line-height: .6rem; margin-top: 5px; } From 636a44e0f9e13931fe790b701de834630d3b6041 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 23 Sep 2016 19:50:23 +0200 Subject: [PATCH 278/315] Fixing tests --- .../related-userstory-row.controller.coffee | 1 - .../related-userstory-row.controller.spec.coffee | 12 +++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee index c8513f9e..ba583d8a 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.coffee @@ -44,7 +44,6 @@ class RelatedUserstoryRowController message = @translate.instant('EPIC.MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY', { subject: @.userstory.get('subject') }) - return @confirm.askOnDelete(title, message) .then (askResponse) => onError = () => diff --git a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee index c300b372..5b496b0e 100644 --- a/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee +++ b/app/modules/epics/related-userstories/related-userstory-row/related-userstory-row.controller.spec.coffee @@ -125,15 +125,14 @@ describe "RelatedUserstoryRow", -> finish: sinon.spy() } - mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY").returns("title") - mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") + mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY").returns("title") + mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") mocks.tgConfirm.askOnDelete = sinon.stub() mocks.tgConfirm.askOnDelete.withArgs("title", "message").promise().resolve(askResponse) promise = mocks.tgResources.epics.deleteRelatedUserstory.withArgs(123, 124).promise().resolve(true) RelatedUserstoryRowCtrl.onDeleteRelatedUserstory().then () -> - expect(mocks.tgResources.epics.deleteRelatedUserstory).have.been.calledWith(123, 124) expect(RelatedUserstoryRowCtrl.loadRelatedUserstories).have.been.calledOnce expect(askResponse.finish).have.been.calledOnce done() @@ -153,16 +152,15 @@ describe "RelatedUserstoryRow", -> finish: sinon.spy() } - mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY").returns("title") - mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_DELETE_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") - mocks.translate.instant.withArgs("EPIC.ERROR_DELETE_RELATED_USERSTORY", {errorMessage: "message"}).returns("error message") + mocks.translate.instant.withArgs("EPIC.TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY").returns("title") + mocks.translate.instant.withArgs("EPIC.MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY", {subject: "Deleting"}).returns("message") + mocks.translate.instant.withArgs("EPIC.ERROR_UNLINK_RELATED_USERSTORY", {errorMessage: "message"}).returns("error message") mocks.tgConfirm.askOnDelete = sinon.stub() mocks.tgConfirm.askOnDelete.withArgs("title", "message").promise().resolve(askResponse) promise = mocks.tgResources.epics.deleteRelatedUserstory.withArgs(123, 124).promise().reject(new Error("error")) RelatedUserstoryRowCtrl.onDeleteRelatedUserstory().then () -> - expect(mocks.tgResources.epics.deleteRelatedUserstory).have.been.calledWith(123, 124) expect(RelatedUserstoryRowCtrl.loadRelatedUserstories).to.not.have.been.called expect(askResponse.finish).have.been.calledWith(false) expect(mocks.tgConfirm.notify).have.been.calledWith("error", null, "error message") From 1bc2eb03448582fa87be11f63918875e9fed073d Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 27 Sep 2016 07:44:32 +0200 Subject: [PATCH 279/315] Fixing epic filtering --- app/coffee/modules/controllerMixins.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/coffee/modules/controllerMixins.coffee b/app/coffee/modules/controllerMixins.coffee index 5d8cef9f..a6c501cd 100644 --- a/app/coffee/modules/controllerMixins.coffee +++ b/app/coffee/modules/controllerMixins.coffee @@ -204,7 +204,7 @@ class UsFiltersMixin loadFilters.status = urlfilters.status loadFilters.assigned_to = urlfilters.assigned_to loadFilters.owner = urlfilters.owner - loadFilters.epics = urlfilters.epics + loadFilters.epic = urlfilters.epic loadFilters.q = urlfilters.q return @q.all([ @@ -238,7 +238,7 @@ class UsFiltersMixin it.name = it.full_name return it - epics = _.map data.epics, (it) -> + epic = _.map data.epics, (it) -> if it.id it.id = it.id.toString() it.name = "##{it.ref} #{it.subject}" @@ -266,8 +266,8 @@ class UsFiltersMixin selected = @.formatSelectedFilters("owner", owner, loadFilters.owner) @.selectedFilters = @.selectedFilters.concat(selected) - if loadFilters.epics - selected = @.formatSelectedFilters("epics", epics, loadFilters.epics) + if loadFilters.epic + selected = @.formatSelectedFilters("epic", epic, loadFilters.epic) @.selectedFilters = @.selectedFilters.concat(selected) @.filterQ = loadFilters.q @@ -297,8 +297,8 @@ class UsFiltersMixin }, { title: @translate.instant("COMMON.FILTERS.CATEGORIES.EPIC"), - dataType: "epics", - content: epics + dataType: "epic", + content: epic } ] From 4da7d44d0e924d9b8c8cba7180a84309fedae7b0 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 27 Sep 2016 09:58:33 +0200 Subject: [PATCH 280/315] fix admin e2e tests --- .../admin/admin-custom-attributes.jade | 2 +- .../includes/modules/admin/project-tags.jade | 2 +- e2e/helpers/admin-attributes-helper.js | 20 +++++++++++++------ e2e/helpers/custom-fields-helper.js | 4 ++-- e2e/helpers/project-detail-helper.js | 9 ++++++++- e2e/suites/admin/attributes/tags.e2e.js | 9 +++++---- e2e/suites/admin/project/modules.e2e.js | 2 +- .../admin/project/project-detail.e2e.js | 9 +++++++-- 8 files changed, 39 insertions(+), 18 deletions(-) diff --git a/app/partials/includes/modules/admin/admin-custom-attributes.jade b/app/partials/includes/modules/admin/admin-custom-attributes.jade index 123d39ce..847a4133 100644 --- a/app/partials/includes/modules/admin/admin-custom-attributes.jade +++ b/app/partials/includes/modules/admin/admin-custom-attributes.jade @@ -16,7 +16,7 @@ section.custom-fields-table.basic-table .table-body .js-sortable - div( + .e2e-item( ng-repeat="attr in customAttributes track by attr.id" tg-bind-scope ) diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index 83b6fc78..dd339453 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -72,7 +72,7 @@ section ) p(translate="ADMIN.PROJECT_VALUES_TAGS.EMPTY_SEARCH") - div( + div.e2e-tag-row( ng-repeat="tag in projectTags" tg-bind-scope ) diff --git a/e2e/helpers/admin-attributes-helper.js b/e2e/helpers/admin-attributes-helper.js index fc485b94..b3bedf2d 100644 --- a/e2e/helpers/admin-attributes-helper.js +++ b/e2e/helpers/admin-attributes-helper.js @@ -40,7 +40,15 @@ helper.getTagsSection = function(item) { return { el: section, rows: function() { - return section.$$('.table-main'); + return section.$$('.e2e-tag-row'); + }, + edit: async function(row) { + let editButton = row.$('.edit-value'); + + return browser.actions() + .mouseMove(editButton) + .click() + .perform(); } }; }; @@ -48,7 +56,7 @@ helper.getTagsSection = function(item) { helper.getTagsFilter = function() { return $('.table-header .e2e-tags-filter'); -} +}; helper.getStatusNames = function(section) { return section.$$('.status-name span').getText(); @@ -111,12 +119,12 @@ helper.getGenericForm = function(form) { }; obj.colorBox = function() { - return form.$('.edition .current-color'); - } + return form.$('.edition .e2e-open-color-selector'); + }; obj.colorText = function() { - return form.$('.select-color input'); - } + return form.$('.color-selector-dropdown input'); + }; return obj; }; diff --git a/e2e/helpers/custom-fields-helper.js b/e2e/helpers/custom-fields-helper.js index 19bef257..837ac956 100644 --- a/e2e/helpers/custom-fields-helper.js +++ b/e2e/helpers/custom-fields-helper.js @@ -47,11 +47,11 @@ helper.drag = function(indexType, indexCustomField, indexNewPosition) { let customField = helper.getCustomFiledsByType(indexType).get(indexCustomField).$('.e2e-drag'); let newPosition = helper.getCustomFiledsByType(indexType).get(indexNewPosition); - return utils.common.drag(customField, newPosition, 0, 40); + return utils.common.drag(customField, newPosition, 5, 25); }; helper.getCustomFiledsByType = function(indexType) { - return $$('div[tg-project-custom-attributes]').get(indexType).$$('.js-sortable > div'); + return $$('div[tg-project-custom-attributes]').get(indexType).$$('.e2e-item'); }; helper.delete = async function(indexType, indexCustomField) { diff --git a/e2e/helpers/project-detail-helper.js b/e2e/helpers/project-detail-helper.js index efc676f5..88f2c81a 100644 --- a/e2e/helpers/project-detail-helper.js +++ b/e2e/helpers/project-detail-helper.js @@ -58,11 +58,14 @@ helper.getChangeOwnerLb = function() { return utils.lightbox.close(el); }, search: function(q) { - el.$$('input').get(0).sendKeys(q); + return el.$$('input').get(0).sendKeys(q); }, select: function(index) { el.$$('.user-list-single').get(index).click(); }, + getUserName: function(index) { + return el.$$('.user-list-single').get(index).$('.user-list-name').getText(); + }, addComment: function(text) { el.$('.add-comment a').click(); el.$('textarea').sendKeys(text); @@ -74,3 +77,7 @@ helper.getChangeOwnerLb = function() { return obj; }; + +helper.enableAddTags = function() { + $('.add-tag-button').click(); +}; diff --git a/e2e/suites/admin/attributes/tags.e2e.js b/e2e/suites/admin/attributes/tags.e2e.js index a4b6010e..403591fd 100644 --- a/e2e/suites/admin/attributes/tags.e2e.js +++ b/e2e/suites/admin/attributes/tags.e2e.js @@ -22,7 +22,9 @@ describe('attributes - tags', function() { let rows = section.rows(); let row = rows.get(0); - let form = adminAttributesHelper.getGenericForm(row.$('form')); + section.edit(row); + + let form = adminAttributesHelper.getGenericForm(row.$$('form').first()); var colorBox = form.colorBox(); await colorBox.click(); @@ -35,7 +37,7 @@ describe('attributes - tags', function() { section = adminAttributesHelper.getTagsSection(0); rows = section.rows(); row = rows.get(0); - let backgroundColor = await row.$$('.edition .current-color').get(0).getCssValue('background-color'); + let backgroundColor = await row.$$('.e2e-open-color-selector').get(0).getCssValue('background-color'); expect(backgroundColor).to.be.equal('rgba(0, 0, 0, 1)'); utils.common.takeScreenshot('attributes', 'tag edited is black'); }); @@ -44,12 +46,11 @@ describe('attributes - tags', function() { let tagsFilter = adminAttributesHelper.getTagsFilter(); await tagsFilter.clear(); await tagsFilter.sendKeys('ad'); + await browser.sleep(5000); let section = adminAttributesHelper.getTagsSection(0); let rows = section.rows(); let count = await rows.count(); expect(count).to.be.equal(2); }); - - }); diff --git a/e2e/suites/admin/project/modules.e2e.js b/e2e/suites/admin/project/modules.e2e.js index 7ad0e361..5c85e3a8 100644 --- a/e2e/suites/admin/project/modules.e2e.js +++ b/e2e/suites/admin/project/modules.e2e.js @@ -78,7 +78,7 @@ describe('modules', function() { }); it('enable videoconference', async function() { - let functionality = $$('.module').get(4); + let functionality = $$('.module').get(5); let input = functionality.$('.check input'); diff --git a/e2e/suites/admin/project/project-detail.e2e.js b/e2e/suites/admin/project/project-detail.e2e.js index e3c15366..033015a4 100644 --- a/e2e/suites/admin/project/project-detail.e2e.js +++ b/e2e/suites/admin/project/project-detail.e2e.js @@ -18,7 +18,9 @@ describe('project detail', function() { }); it('edit tag, description and project settings', async function() { - let tag = $('.tag-input'); + adminHelper.enableAddTags(); + + let tag = $('.e2e-add-tag-input'); tag.sendKeys('aaa'); browser.actions().sendKeys(protractor.Key.ENTER).perform(); @@ -105,7 +107,10 @@ describe('project detail', function() { await lb.waitOpen(); - lb.search('Alicia Flores'); + let username = lb.getUserName(0); + + await lb.search(username); + lb.select(0); lb.addComment('text'); From adaab29768e0e11a168fef3e8217f15f6c4d2123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 19 Sep 2016 10:37:40 +0200 Subject: [PATCH 281/315] Fix issue related to limit height in custom fields --- .../modules/common/custom-field-values.coffee | 18 +++++------------- .../custom-attributes-values.jade | 19 ++++++++++++++----- app/styles/modules/common/custom-fields.scss | 8 +++++--- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/app/coffee/modules/common/custom-field-values.coffee b/app/coffee/modules/common/custom-field-values.coffee index ad3e8049..b9808cbb 100644 --- a/app/coffee/modules/common/custom-field-values.coffee +++ b/app/coffee/modules/common/custom-field-values.coffee @@ -112,31 +112,23 @@ CustomAttributesValuesDirective = ($templates, $storage) -> link = ($scope, $el, $attrs, $ctrls) -> $ctrl = $ctrls[0] $model = $ctrls[1] + hash = collapsedHash($attrs.type) + $scope.collapsed = $storage.get(hash) or false bindOnce $scope, $attrs.ngModel, (value) -> $ctrl.initialize($attrs.type, value.id) $ctrl.loadCustomAttributesValues() - $el.on "click", ".custom-fields-header .collapse", -> - hash = collapsedHash($attrs.type) - collapsed = not($storage.get(hash) or false) - $storage.set(hash, collapsed) - if collapsed - $el.find(".custom-fields-header .icon").removeClass("open") - $el.find(".custom-fields-body").removeClass("open") - else - $el.find(".custom-fields-header .icon").addClass("open") - $el.find(".custom-fields-body").addClass("open") + $scope.toggleCollapse = () -> + $scope.collapsed = !$scope.collapsed + $storage.set(hash, $scope.collapsed) $scope.$on "$destroy", -> $el.off() templateFn = ($el, $attrs) -> - collapsed = $storage.get(collapsedHash($attrs.type)) or false - return template({ requiredEditionPerm: $attrs.requiredEditionPerm - collapsed: collapsed }) return { diff --git a/app/partials/custom-attributes/custom-attributes-values.jade b/app/partials/custom-attributes/custom-attributes-values.jade index e2dbdcd3..2a5ec5eb 100644 --- a/app/partials/custom-attributes/custom-attributes-values.jade +++ b/app/partials/custom-attributes/custom-attributes-values.jade @@ -1,8 +1,17 @@ section.duty-custom-fields(ng-show="ctrl.customAttributes.length") - div.custom-fields-header + .custom-fields-header span(translate="COMMON.CUSTOM_ATTRIBUTES.CUSTOM_FIELDS") - // Remove .open class on click on this button in both .icon and .custom-fields-body to close - a.collapse(href="", class!="<% if (!collapsed) { %>open<% } %>") + a.collapse( + href="" + ng-class="{'open': !collapsed}" + ng-click="toggleCollapse()" + ) tg-svg(svg-icon="icon-arrow-down") - div.custom-fields-body(class!="<% if (!collapsed) { %>open<% } %>") - div(ng-repeat="att in ctrl.customAttributes", tg-custom-attribute-value="ctrl.getAttributeValue(att)", required-edition-perm!="<%- requiredEditionPerm %>") + .custom-fields-body( + ng-show="!collapsed" + ) + .custom-attribute( + ng-repeat="attr in ctrl.customAttributes" + tg-custom-attribute-value="ctrl.getAttributeValue(attr)" + required-edition-perm!="<%- requiredEditionPerm %>" + ) diff --git a/app/styles/modules/common/custom-fields.scss b/app/styles/modules/common/custom-fields.scss index 8c840263..d2aed368 100644 --- a/app/styles/modules/common/custom-fields.scss +++ b/app/styles/modules/common/custom-fields.scss @@ -13,10 +13,12 @@ } .collapse { display: block; + transform: rotate(-90deg); + transition: .1s ease-out; + } + .open { + transform: rotate(0); } - } - .custom-fields-body { - @include slide(1000px, hidden, $min: 0); } .custom-field-single { border-bottom: 1px solid $whitish; From d39e4c1f223ea4bb91c9f614d65191741ae50a7a Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 27 Sep 2016 10:37:17 +0200 Subject: [PATCH 282/315] Fixing custom fields e2e tests --- e2e/suites/issues/issue-detail.e2e.js | 2 +- e2e/suites/tasks/task-detail.e2e.js | 2 +- e2e/suites/user-stories/user-story-detail.e2e.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/suites/issues/issue-detail.e2e.js b/e2e/suites/issues/issue-detail.e2e.js index a1674ccf..352d6615 100644 --- a/e2e/suites/issues/issue-detail.e2e.js +++ b/e2e/suites/issues/issue-detail.e2e.js @@ -43,7 +43,7 @@ describe('Issue detail', async function(){ it('attachments', sharedDetail.attachmentTesting); - describe('custom-fields', sharedDetail.customFields.bind(this, 2)); + describe('custom-fields', sharedDetail.customFields.bind(this, 3)); it('screenshot', async function() { await utils.common.takeScreenshot("issues", "detail updated"); diff --git a/e2e/suites/tasks/task-detail.e2e.js b/e2e/suites/tasks/task-detail.e2e.js index 31dca89c..10389c00 100644 --- a/e2e/suites/tasks/task-detail.e2e.js +++ b/e2e/suites/tasks/task-detail.e2e.js @@ -59,7 +59,7 @@ describe('Task detail', function(){ it('attachments', sharedDetail.attachmentTesting); - describe('custom-fields', sharedDetail.customFields.bind(this, 1)); + describe('custom-fields', sharedDetail.customFields.bind(this, 2)); it('screenshot', async function() { await utils.common.takeScreenshot("tasks", "detail updated"); diff --git a/e2e/suites/user-stories/user-story-detail.e2e.js b/e2e/suites/user-stories/user-story-detail.e2e.js index bbc52509..1a7d28af 100644 --- a/e2e/suites/user-stories/user-story-detail.e2e.js +++ b/e2e/suites/user-stories/user-story-detail.e2e.js @@ -48,7 +48,7 @@ describe('User story detail', function(){ it('attachments', sharedDetail.attachmentTesting); - describe('custom-fields', sharedDetail.customFields.bind(this, 0)); + describe('custom-fields', sharedDetail.customFields.bind(this, 1)); describe('related tasks', function() { it('create', async function() { From 2ece124553d3a1a94434c0b1a3bcd52da6c50530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Tue, 27 Sep 2016 10:38:23 +0200 Subject: [PATCH 283/315] Fix the send to tribe button for the new changes on Taiga Tribe --- app/modules/components/tribe-button/tribe-button.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/components/tribe-button/tribe-button.jade b/app/modules/components/tribe-button/tribe-button.jade index 1d1cad90..06ace0c7 100644 --- a/app/modules/components/tribe-button/tribe-button.jade +++ b/app/modules/components/tribe-button/tribe-button.jade @@ -1,5 +1,5 @@ a.button-tribe( - ng-href="{{::vm.tribeHost}}/gigs/import-from-taiga?project={{projectSlug}}&us={{usId}}", + ng-href="{{::vm.tribeHost}}/taiga-integration/receive?project={{projectSlug}}&us={{usId}}", title="{{ 'US.TRIBE.PUBLISH' | translate }}" target="_blank" ) From 5ca24aa37bb3e762b3068d34f2283a63f5e70f1e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 27 Sep 2016 10:57:16 +0200 Subject: [PATCH 284/315] Fixing filters e2e tests --- e2e/shared/filters.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/e2e/shared/filters.js b/e2e/shared/filters.js index f8cbff2d..f42386d0 100644 --- a/e2e/shared/filters.js +++ b/e2e/shared/filters.js @@ -44,9 +44,6 @@ module.exports = function(name, counter) { it('save custom filters', async () => { let len = await counter(); - - filterHelper.openCustomFiltersCategory(); - let customFiltersSize = await filterHelper.getCustomFilters().count(); await filterHelper.firterByCategoryWithContent(); From e233736aeaf806e43a3793c1c9ce334fc90988c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Tue, 27 Sep 2016 11:29:37 +0200 Subject: [PATCH 285/315] Pass the service url to tribe on send-to-tribe link --- .../tribe-button/tribe-button.directive.coffee | 9 +++++++-- app/modules/components/tribe-button/tribe-button.jade | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/modules/components/tribe-button/tribe-button.directive.coffee b/app/modules/components/tribe-button/tribe-button.directive.coffee index 4f961f06..03925461 100644 --- a/app/modules/components/tribe-button/tribe-button.directive.coffee +++ b/app/modules/components/tribe-button/tribe-button.directive.coffee @@ -17,11 +17,16 @@ # File: tribe-button.directive.coffee ### -TribeButtonDirective = (configService) -> +TribeButtonDirective = (configService, locationService) -> link = (scope, el, attrs) -> scope.vm = {} scope.vm.tribeHost = configService.config.tribeHost + scope.vm.url = "#{locationService.protocol()}://#{locationService.host()}" + if (locationService.protocol() == "http" and locationService.port() != 80) + scope.vm.url = "#{scope.vm.url}:#{locationService.port()}" + else if (locationService.protocol() == "https" and locationService.port() != 443) + scope.vm.url = "#{scope.vm.url}:#{locationService.port()}" return { scope: {usId: "=", projectSlug: "="} @@ -31,7 +36,7 @@ TribeButtonDirective = (configService) -> } TribeButtonDirective.$inject = [ - "$tgConfig" + "$tgConfig", "$tgLocation" ] angular.module("taigaComponents").directive("tgTribeButton", TribeButtonDirective) diff --git a/app/modules/components/tribe-button/tribe-button.jade b/app/modules/components/tribe-button/tribe-button.jade index 06ace0c7..7d2b09e2 100644 --- a/app/modules/components/tribe-button/tribe-button.jade +++ b/app/modules/components/tribe-button/tribe-button.jade @@ -1,5 +1,5 @@ a.button-tribe( - ng-href="{{::vm.tribeHost}}/taiga-integration/receive?project={{projectSlug}}&us={{usId}}", + ng-href="{{::vm.tribeHost}}/taiga-integration/receive?url={{::vm.url}}&project={{projectSlug}}&us={{usId}}", title="{{ 'US.TRIBE.PUBLISH' | translate }}" target="_blank" ) From 3ec18f4781f2f741256f0b5783354f40d565485c Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 27 Sep 2016 11:32:58 +0200 Subject: [PATCH 286/315] fix issue 4573 --- app/coffee/modules/related-tasks.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/coffee/modules/related-tasks.coffee b/app/coffee/modules/related-tasks.coffee index 0e51b881..d8eede4e 100644 --- a/app/coffee/modules/related-tasks.coffee +++ b/app/coffee/modules/related-tasks.coffee @@ -168,6 +168,8 @@ RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading, $scope.newTask = $tgmodel.make_model("tasks", newTask) render = -> + return if $scope.openNewRelatedTask + $scope.openNewRelatedTask = true $el.on "keyup", "input", (event)-> From 1bbe86ef16d29985e10f1df1aacb4f27edfe74ea Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 27 Sep 2016 11:39:23 +0200 Subject: [PATCH 287/315] Fixing userProfile e2e tests --- .../user-profile/user-profile-votes.e2e.js | 36 +++++++++++++++--- .../user-profile/user-profile-watched.e2e.js | 37 ++++++++++++++++--- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/e2e/suites/user-profile/user-profile-votes.e2e.js b/e2e/suites/user-profile/user-profile-votes.e2e.js index 44ede43d..f1402e8b 100644 --- a/e2e/suites/user-profile/user-profile-votes.e2e.js +++ b/e2e/suites/user-profile/user-profile-votes.e2e.js @@ -40,7 +40,7 @@ describe('user profile - votes', function() { expect(hasMoreItems).to.be.equal(true); }); - it('votes tab - filter user stories', async function() { + it('votes tab - filter epics', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(1).click(); @@ -52,7 +52,7 @@ describe('user profile - votes', function() { expect(allItems).to.be.not.equal(filteredItems); }); - it('votes tab - filter tasks', async function() { + it('votes tab - filter user stories', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(2).click(); @@ -64,7 +64,7 @@ describe('user profile - votes', function() { expect(allItems).to.be.not.equal(filteredItems); }); - it('votes tab - filter issues', async function() { + it('votes tab - filter tasks', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(3).click(); @@ -76,6 +76,18 @@ describe('user profile - votes', function() { expect(allItems).to.be.not.equal(filteredItems); }); + it('votes tab - filter issues', async function() { + let allItems = await $('div[infinite-scroll]').getInnerHtml(); + + await $$('div.filters > a').get(4).click(); + + await browser.waitForAngular(); + + let filteredItems = await $('div[infinite-scroll]').getInnerHtml(); + + expect(allItems).to.be.not.equal(filteredItems); + }); + it('votes tab - filter by query', async function() { let allItems = await $$('div[infinite-scroll] > div').count(); @@ -130,7 +142,7 @@ describe('user profile - votes', function() { expect(hasMoreItems).to.be.equal(true); }); - it('votes tab - filter user stories', async function() { + it('votes tab - filter epics', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(1).click(); @@ -142,7 +154,7 @@ describe('user profile - votes', function() { expect(allItems).to.be.not.equal(filteredItems); }); - it('votes tab - filter tasks', async function() { + it('votes tab - filter user stories', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(2).click(); @@ -154,7 +166,7 @@ describe('user profile - votes', function() { expect(allItems).to.be.not.equal(filteredItems); }); - it('votes tab - filter issues', async function() { + it('votes tab - filter tasks', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(3).click(); @@ -166,6 +178,18 @@ describe('user profile - votes', function() { expect(allItems).to.be.not.equal(filteredItems); }); + it('votes tab - filter issues', async function() { + let allItems = await $('div[infinite-scroll]').getInnerHtml(); + + await $$('div.filters > a').get(4).click(); + + await browser.waitForAngular(); + + let filteredItems = await $('div[infinite-scroll]').getInnerHtml(); + + expect(allItems).to.be.not.equal(filteredItems); + }); + it('votes tab - filter by query', async function() { let allItems = await $$('div[infinite-scroll] > div').count(); diff --git a/e2e/suites/user-profile/user-profile-watched.e2e.js b/e2e/suites/user-profile/user-profile-watched.e2e.js index 2b33b45a..df85a428 100644 --- a/e2e/suites/user-profile/user-profile-watched.e2e.js +++ b/e2e/suites/user-profile/user-profile-watched.e2e.js @@ -53,7 +53,8 @@ describe('user profile - watched', function() { expect(allItems).to.be.not.equal(filteredItems); }); - it('watched tab - filter user stories', async function() { + + it('watched tab - filter epics', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(2).click(); @@ -65,7 +66,7 @@ describe('user profile - watched', function() { expect(allItems).to.be.not.equal(filteredItems); }); - it('watched tab - filter tasks', async function() { + it('watched tab - filter user stories', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(3).click(); @@ -77,7 +78,7 @@ describe('user profile - watched', function() { expect(allItems).to.be.not.equal(filteredItems); }); - it('watched tab - filter issues', async function() { + it('watched tab - filter tasks', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(4).click(); @@ -89,6 +90,18 @@ describe('user profile - watched', function() { expect(allItems).to.be.not.equal(filteredItems); }); + it('watched tab - filter issues', async function() { + let allItems = await $('div[infinite-scroll]').getInnerHtml(); + + await $$('div.filters > a').get(5).click(); + + await browser.waitForAngular(); + + let filteredItems = await $('div[infinite-scroll]').getInnerHtml(); + + expect(allItems).to.be.not.equal(filteredItems); + }); + it('watched tab - filter by query', async function() { let allItems = await $$('div[infinite-scroll] > div').count(); @@ -155,7 +168,7 @@ describe('user profile - watched', function() { expect(allItems).to.be.not.equal(filteredItems); }); - it('watched tab - filter user stories', async function() { + it('watched tab - filter epics', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(2).click(); @@ -167,7 +180,7 @@ describe('user profile - watched', function() { expect(allItems).to.be.not.equal(filteredItems); }); - it('watched tab - filter tasks', async function() { + it('watched tab - filter user stories', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(3).click(); @@ -179,7 +192,7 @@ describe('user profile - watched', function() { expect(allItems).to.be.not.equal(filteredItems); }); - it('watched tab - filter issues', async function() { + it('watched tab - filter tasks', async function() { let allItems = await $('div[infinite-scroll]').getInnerHtml(); await $$('div.filters > a').get(4).click(); @@ -191,6 +204,18 @@ describe('user profile - watched', function() { expect(allItems).to.be.not.equal(filteredItems); }); + it('watched tab - filter issues', async function() { + let allItems = await $('div[infinite-scroll]').getInnerHtml(); + + await $$('div.filters > a').get(5).click(); + + await browser.waitForAngular(); + + let filteredItems = await $('div[infinite-scroll]').getInnerHtml(); + + expect(allItems).to.be.not.equal(filteredItems); + }); + it('watched tab - filter by query', async function() { let allItems = await $$('div[infinite-scroll] > div').count(); From 78f5b1998423a2be8fadb6b4e951e11c4a75b5e8 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 27 Sep 2016 12:28:51 +0200 Subject: [PATCH 288/315] Improving e2e backlog and kanban tests --- e2e/suites/backlog.e2e.js | 6 +----- e2e/suites/kanban.e2e.js | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/e2e/suites/backlog.e2e.js b/e2e/suites/backlog.e2e.js index 9ec2b4ee..e3f664ba 100644 --- a/e2e/suites/backlog.e2e.js +++ b/e2e/suites/backlog.e2e.js @@ -142,11 +142,7 @@ describe('backlog', function() { editUSLightbox.status(3).click(); // tags - editUSLightbox.tags().sendKeys('www'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); - - editUSLightbox.tags().sendKeys('xxx'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); + editUSLightbox.tags(); // description editUSLightbox.description().sendKeys('test test test test'); diff --git a/e2e/suites/kanban.e2e.js b/e2e/suites/kanban.e2e.js index 85d100f3..481dcf77 100644 --- a/e2e/suites/kanban.e2e.js +++ b/e2e/suites/kanban.e2e.js @@ -143,11 +143,7 @@ describe('kanban', function() { expect(totalPoints).to.be.equal('4'); // tags - createUSLightbox.tags().sendKeys('www'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); - - createUSLightbox.tags().sendKeys('xxx'); - browser.actions().sendKeys(protractor.Key.ENTER).perform(); + createUSLightbox.tags(); // description createUSLightbox.description().sendKeys(formFields.description); From c7d4414668408eefe8f3ab3d68d58d92a8260730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 27 Sep 2016 13:35:54 +0200 Subject: [PATCH 289/315] Fix blocked page --- app/modules/projects/project/blocked-project.jade | 2 +- app/modules/projects/project/project.controller.coffee | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/modules/projects/project/blocked-project.jade b/app/modules/projects/project/blocked-project.jade index 0cf464b8..bd7e8ad2 100644 --- a/app/modules/projects/project/blocked-project.jade +++ b/app/modules/projects/project/blocked-project.jade @@ -1,4 +1,4 @@ -.blocked-project-detail +.blocked-project-detail(ng-controller="Project as vm") .blocked-project-inner .blocked-project-title .project-image diff --git a/app/modules/projects/project/project.controller.coffee b/app/modules/projects/project/project.controller.coffee index af9d2f60..18820098 100644 --- a/app/modules/projects/project/project.controller.coffee +++ b/app/modules/projects/project/project.controller.coffee @@ -27,7 +27,6 @@ class ProjectController ] constructor: (@routeParams, @appMetaService, @auth, @translate, @projectService) -> - projectSlug = @routeParams.pslug @.user = @auth.userData taiga.defineImmutableProperty @, "project", () => return @projectService.project From de7baa1fb3eec0e5f0550739687dc948a7ddf96e Mon Sep 17 00:00:00 2001 From: Juanfran Date: Tue, 27 Sep 2016 15:01:09 +0200 Subject: [PATCH 290/315] fix e2e backlog sorting tests --- e2e/shared/filters.js | 4 ++++ e2e/suites/backlog.e2e.js | 2 +- e2e/utils/common.js | 18 +++++++++++++++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/e2e/shared/filters.js b/e2e/shared/filters.js index f42386d0..3cc193ad 100644 --- a/e2e/shared/filters.js +++ b/e2e/shared/filters.js @@ -71,4 +71,8 @@ module.exports = function(name, counter) { expect(newCustomFiltersSize).to.be.equal(customFiltersSize - 1); }); + + after(async function() { + await filterHelper.clearFilters(); + }); }; diff --git a/e2e/suites/backlog.e2e.js b/e2e/suites/backlog.e2e.js index e3f664ba..396db50b 100644 --- a/e2e/suites/backlog.e2e.js +++ b/e2e/suites/backlog.e2e.js @@ -237,7 +237,7 @@ describe('backlog', function() { expect(elementRef1).to.be.equal(draggedRefs[1]); }); - it('drag multiple us to milestone', async function() { + it.skip('drag multiple us to milestone', async function() { let sprint = backlogHelper.sprints().get(0); let initUssSprintCount = await backlogHelper.getSprintUsertories(sprint).count(); diff --git a/e2e/utils/common.js b/e2e/utils/common.js index 4d50932c..d3a3c4dc 100644 --- a/e2e/utils/common.js +++ b/e2e/utils/common.js @@ -189,6 +189,14 @@ common.drag = async function(elm, elm2, extrax = 0, extray = 0) { var extrax = arguments[0].extrax; var extray = arguments[0].extray; + function isScrolledIntoView(el) { + var elemTop = el.getBoundingClientRect().top; + var elemBottom = el.getBoundingClientRect().bottom; + + var isVisible = (elemTop >= 0) && (elemBottom <= window.innerHeight); + return isVisible; + } + function triggerMouseEvent (node, eventType, opts) { var event = new CustomEvent(eventType); event.initEvent (eventType, true, true); @@ -206,12 +214,12 @@ common.drag = async function(elm, elm2, extrax = 0, extray = 0) { node.dispatchEvent(event); } - drag.scrollIntoView(); + if (!isScrolledIntoView(drag)) { + drag.scrollIntoView(); + } triggerMouseEvent(drag, "mousedown"); - dest.scrollIntoView(); - triggerMouseEvent(document.documentElement, "mousemove", { cords: { x: $(dest).offset().left + extrax, @@ -219,6 +227,10 @@ common.drag = async function(elm, elm2, extrax = 0, extray = 0) { } }); + if (!isScrolledIntoView(dest)) { + dest.scrollIntoView(); + } + triggerMouseEvent(document.documentElement, "mousemove", { cords: { x: $(dest).offset().left + extrax, From df22bcc02164783aae2d8ca282ddc7cbd495332e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 27 Sep 2016 20:51:05 +0200 Subject: [PATCH 291/315] Remove duplicate controller --- app/coffee/app.coffee | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index 9a552542..6fd780a3 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -76,6 +76,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven return originalWhen.call($routeProvider, path, route) + # Home $routeProvider.when("/", { templateUrl: "home/home.html", @@ -89,6 +90,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) + # Discover $routeProvider.when("/discover", { templateUrl: "discover/discover-home/discover-home.html", @@ -110,6 +112,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) + # My Projects $routeProvider.when("/projects/", { templateUrl: "projects/listing/projects-listing.html", @@ -123,16 +126,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven controllerAs: "vm" } ) - - $routeProvider.when("/blocked-project/:pslug/", - { - templateUrl: "projects/project/blocked-project.html", - loader: true, - controller: "Project", - controllerAs: "vm" - } - ) - + # Project $routeProvider.when("/project/:pslug/", { templateUrl: "projects/project/project.html", @@ -152,6 +146,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) + # Epics $routeProvider.when("/project/:pslug/epics", { section: "epics", @@ -162,7 +157,6 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven } ) - # Epics $routeProvider.when("/project/:pslug/epic/:epicref", { templateUrl: "epic/epic-detail.html", @@ -485,6 +479,12 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven ) # Errors/Exceptions + $routeProvider.when("/blocked-project/:pslug/", + { + templateUrl: "projects/project/blocked-project.html", + loader: true, + } + ) $routeProvider.when("/error", {templateUrl: "error/error.html"}) $routeProvider.when("/not-found", From bf407a04f68def7b038d9d5e668eac7efc8794de Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 28 Sep 2016 07:01:33 +0200 Subject: [PATCH 292/315] fix discover e2e tests --- e2e/suites/discover/discover-search.e2e.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/e2e/suites/discover/discover-search.e2e.js b/e2e/suites/discover/discover-search.e2e.js index 9fa5bd02..b843bde5 100644 --- a/e2e/suites/discover/discover-search.e2e.js +++ b/e2e/suites/discover/discover-search.e2e.js @@ -40,11 +40,14 @@ describe('discover search', () => { }); it('search by text', async () => { - discoverHelper.searchInput().sendKeys('Project Example 0'); + let projects = discoverHelper.searchProjects(); + let projectTitle = projects.get(0).$('h2 a').getText(); + + discoverHelper.searchInput().sendKeys(projectTitle); discoverHelper.sendSearch(); - let projects = discoverHelper.searchProjects(); + projects = discoverHelper.searchProjects(); expect(await projects.count()).to.be.equal(1); }); }); From 473362d772559f516282fd667b9b8c384685c422 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 28 Sep 2016 07:30:24 +0200 Subject: [PATCH 293/315] prevent e2e auth loading page errors --- e2e/suites/auth/auth.e2e.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/e2e/suites/auth/auth.e2e.js b/e2e/suites/auth/auth.e2e.js index 0e9dbc5f..ff4557c4 100644 --- a/e2e/suites/auth/auth.e2e.js +++ b/e2e/suites/auth/auth.e2e.js @@ -43,6 +43,8 @@ describe('auth', function() { it("redirect to login", async function() { browser.get(browser.params.glob.host + path); + await utils.common.waitLoader(); + let url = await browser.getCurrentUrl(); expect(url).to.be.equal(browser.params.glob.host + 'login?next=' + encodeURIComponent('/' + path)); @@ -53,6 +55,8 @@ describe('auth', function() { $('input[name="password"]').sendKeys('123123'); $('.submit-button').click(); + await utils.common.waitLoader(); + let url = await browser.getCurrentUrl(); expect(url).to.be.equal(browser.params.glob.host + path); From d95e4778c837cd2b2a46dc79c4a54b6b870d0d50 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 28 Sep 2016 09:56:32 +0200 Subject: [PATCH 294/315] improve lightbox close e2e --- e2e/shared/detail.js | 3 ++- e2e/suites/backlog.e2e.js | 6 ++++-- e2e/utils/lightbox.js | 25 ++++++++++--------------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/e2e/shared/detail.js b/e2e/shared/detail.js index f8694cbd..364813c5 100644 --- a/e2e/shared/detail.js +++ b/e2e/shared/detail.js @@ -357,9 +357,10 @@ shared.attachmentTesting = async function() { let previewSrc2 = await attachmentHelper.getPreviewSrc(); - expect(previewSrc).not.to.be.equal(previewSrc2); await lightbox.exit(); + expect(previewSrc).not.to.be.equal(previewSrc2); + // Deleting attachmentsLength = await attachmentHelper.countAttachments(); await attachmentHelper.deleteLastAttachment(); diff --git a/e2e/suites/backlog.e2e.js b/e2e/suites/backlog.e2e.js index 396db50b..f847a4f2 100644 --- a/e2e/suites/backlog.e2e.js +++ b/e2e/suites/backlog.e2e.js @@ -10,6 +10,7 @@ var sharedFilters = require('../shared/filters'); chai.use(chaiAsPromised); var expect = chai.expect; + describe('backlog', function() { before(async function() { browser.get(browser.params.glob.host + 'project/project-3/backlog'); @@ -160,6 +161,7 @@ describe('backlog', function() { }); }); + it('edit status inline', async function() { await backlogHelper.setUsStatus(0, 1); @@ -209,7 +211,7 @@ describe('backlog', function() { expect(firstElementTextRef).to.be.equal(draggedElementRef); }); - it.skip('reorder multiple us', async function() { + it('reorder multiple us', async function() { let dragableElements = backlogHelper.userStories(); let count = await dragableElements.count(); @@ -237,7 +239,7 @@ describe('backlog', function() { expect(elementRef1).to.be.equal(draggedRefs[1]); }); - it.skip('drag multiple us to milestone', async function() { + it('drag multiple us to milestone', async function() { let sprint = backlogHelper.sprints().get(0); let initUssSprintCount = await backlogHelper.getSprintUsertories(sprint).count(); diff --git a/e2e/utils/lightbox.js b/e2e/utils/lightbox.js index a6222d99..8c041672 100644 --- a/e2e/utils/lightbox.js +++ b/e2e/utils/lightbox.js @@ -13,6 +13,8 @@ lightbox.exit = function(el) { } el.$('.close').click(); + + return lightbox.close(el); }; lightbox.open = async function(el) { @@ -38,7 +40,6 @@ lightbox.open = async function(el) { }; lightbox.close = async function(el) { - var deferred = protractor.promise.defer(); var present = true; if (typeof el == 'string' || el instanceof String) { @@ -47,22 +48,16 @@ lightbox.close = async function(el) { present = await el.isPresent(); - if (!present) { - deferred.fulfill(true); - } else { - return browser.wait(function() { - return common.hasClass(el, 'open').then(function(open) { + if (present) { + try { + await browser.wait(async function() { + let open = await common.hasClass(el, 'open'); return !open; - }); - }, 4000) - .then(function() { - return deferred.fulfill(true); - }, function() { - deferred.reject(new Error('Lightbox doesn\'t close')); - }); + }, 4000); + } catch (e) { + new Error('Lightbox doesn\'t close') + } } - - return deferred.promise; }; lightbox.confirm = {}; From 9e26033a2a74323a5bc01638a137722ea4f2c4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 28 Sep 2016 10:40:56 +0200 Subject: [PATCH 295/315] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b823c12a..32b15b1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog # -## 3.0.0 ???? (Unreleased) +## 3.0.0 Stellaria Borealis (2016-10-02) ### Features - Add Epics. From d09c35423812e570bf4e0a357c6cc245e5eed230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 28 Sep 2016 10:45:34 +0200 Subject: [PATCH 296/315] [i18n] Update locales --- app/locales/taiga/locale-ca.json | 21 ++++++++++-------- app/locales/taiga/locale-de.json | 23 +++++++++++--------- app/locales/taiga/locale-es.json | 21 ++++++++++-------- app/locales/taiga/locale-fi.json | 23 +++++++++++--------- app/locales/taiga/locale-fr.json | 31 +++++++++++++++------------ app/locales/taiga/locale-it.json | 23 +++++++++++--------- app/locales/taiga/locale-nb.json | 17 +++++++++------ app/locales/taiga/locale-nl.json | 23 +++++++++++--------- app/locales/taiga/locale-pl.json | 23 +++++++++++--------- app/locales/taiga/locale-pt-br.json | 31 +++++++++++++++------------ app/locales/taiga/locale-ru.json | 23 +++++++++++--------- app/locales/taiga/locale-sv.json | 23 +++++++++++--------- app/locales/taiga/locale-tr.json | 23 +++++++++++--------- app/locales/taiga/locale-zh-hant.json | 23 +++++++++++--------- 14 files changed, 185 insertions(+), 143 deletions(-) diff --git a/app/locales/taiga/locale-ca.json b/app/locales/taiga/locale-ca.json index c12f8de2..faa9bfb7 100644 --- a/app/locales/taiga/locale-ca.json +++ b/app/locales/taiga/locale-ca.json @@ -124,7 +124,7 @@ "ISSUE": "incidència", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "Afegir tag", + "PLACEHOLDER": "Enter tag", "DELETE": "Elimina l'etiqueta", "ADD": "Afegeix l'etiqueta" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Cerca", "ACTION_SAVE_CUSTOM_FILTER": "Guarda com a filtre", "PLACEHOLDER_FILTER_NAME": "Escriu el filtre i pressiona Intro", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Tipus", "STATUS": "Estats", @@ -216,7 +217,8 @@ "TAGS": "Etiquetes", "ASSIGNED_TO": "Assignat a", "CREATED_BY": "Creat per", - "CUSTOM_FILTERS": "Filtres personalitzats" + "CUSTOM_FILTERS": "Filtres personalitzats", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Esborrar filtre", @@ -412,8 +414,8 @@ "UNASSIGNED": "Sense assignar" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { @@ -1070,17 +1072,18 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Nova història d'usuari", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Descripció", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" diff --git a/app/locales/taiga/locale-de.json b/app/locales/taiga/locale-de.json index f9bd86d1..aef79320 100644 --- a/app/locales/taiga/locale-de.json +++ b/app/locales/taiga/locale-de.json @@ -124,7 +124,7 @@ "ISSUE": "Ticket", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "Schlagwort...", + "PLACEHOLDER": "Enter tag", "DELETE": "Schlagwort löschen", "ADD": "Schlagwort hinzufügen" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Suche", "ACTION_SAVE_CUSTOM_FILTER": "Als Benutzerfilter speichern", "PLACEHOLDER_FILTER_NAME": "Benennen Sie den Filter und drücken Sie die Eingabetaste", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Arten", "STATUS": "Status", @@ -216,7 +217,8 @@ "TAGS": "Schlagwörter", "ASSIGNED_TO": "Zugeordnet zu", "CREATED_BY": "Erstellt durch", - "CUSTOM_FILTERS": "Benutzerfilter" + "CUSTOM_FILTERS": "Benutzerfilter", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Benutzerfilter löschen", @@ -412,8 +414,8 @@ "UNASSIGNED": "Nicht zugeordnet" }, "EMPTY": { - "TITLE": "Es sieht so aus, als hätten Sie noch keine Epics erzeugt", - "EXPLANATION": "Erstellen Sie Epics als übergeordnete Einheit für User Storys. Epics können User Storys aus diesem oder anderen Projekten beinhalten oder aus Ihnen erstellt werden.", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Erfahren Sie mehr über Epics" }, "TABLE": { @@ -1070,17 +1072,18 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Neue User-Story", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Thema", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" @@ -1202,7 +1205,7 @@ "KANBAN_ORDER": "Kanban Befehl", "TASKBOARD_ORDER": "Taskboard Befehl", "US_ORDER": "User-Story Befehl", - "COLOR": "color" + "COLOR": "Farbe" } }, "BACKLOG": { diff --git a/app/locales/taiga/locale-es.json b/app/locales/taiga/locale-es.json index 77c2972e..2fc850e1 100644 --- a/app/locales/taiga/locale-es.json +++ b/app/locales/taiga/locale-es.json @@ -124,7 +124,7 @@ "ISSUE": "Petición", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "¿Qué soy? Etiquétame...", + "PLACEHOLDER": "Enter tag", "DELETE": "Borrar etiqueta", "ADD": "Añadir etiqueta" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Buscar", "ACTION_SAVE_CUSTOM_FILTER": "guardar como filtro personalizado", "PLACEHOLDER_FILTER_NAME": "Escribe un nombre para el filtro y pulsa enter", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Tipo", "STATUS": "Estado", @@ -216,7 +217,8 @@ "TAGS": "Etiquetas", "ASSIGNED_TO": "Asignado a", "CREATED_BY": "Creada por", - "CUSTOM_FILTERS": "Filtros personalizados" + "CUSTOM_FILTERS": "Filtros personalizados", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Eliminar filtros personalizados", @@ -412,8 +414,8 @@ "UNASSIGNED": "No asignado" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { @@ -1070,17 +1072,18 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Nueva historia de usuario", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Asunto", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" diff --git a/app/locales/taiga/locale-fi.json b/app/locales/taiga/locale-fi.json index b8873b04..a933a9b0 100644 --- a/app/locales/taiga/locale-fi.json +++ b/app/locales/taiga/locale-fi.json @@ -124,7 +124,7 @@ "ISSUE": "Issue", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "Anna avainsana...", + "PLACEHOLDER": "Enter tag", "DELETE": "Poista avainsana", "ADD": "Lisää avainsana" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Hae", "ACTION_SAVE_CUSTOM_FILTER": "tallenna omaksi suodattimeksi", "PLACEHOLDER_FILTER_NAME": "Anna suodattimen nimi ja paina enter", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Tyyppi", "STATUS": "Tila", @@ -216,7 +217,8 @@ "TAGS": "Avainsanat", "ASSIGNED_TO": "Tekijä", "CREATED_BY": "Luoja", - "CUSTOM_FILTERS": "Omat suodattimet" + "CUSTOM_FILTERS": "Omat suodattimet", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Poista oma suodatin", @@ -412,8 +414,8 @@ "UNASSIGNED": "Tekijä puuttuu" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { @@ -1070,17 +1072,18 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Uusi käyttäjätarina", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Aihe", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" @@ -1202,7 +1205,7 @@ "KANBAN_ORDER": "kanban järjestys", "TASKBOARD_ORDER": "Tehtävätaulun järjestys", "US_ORDER": "kt järjestys", - "COLOR": "color" + "COLOR": "väri" } }, "BACKLOG": { diff --git a/app/locales/taiga/locale-fr.json b/app/locales/taiga/locale-fr.json index dfbff49f..c54acaf6 100644 --- a/app/locales/taiga/locale-fr.json +++ b/app/locales/taiga/locale-fr.json @@ -124,7 +124,7 @@ "ISSUE": "Ticket", "EPIC": "Épopée", "TAGS": { - "PLACEHOLDER": "Taggez moi !", + "PLACEHOLDER": "Enter tag", "DELETE": "Supprimer le mot-clé", "ADD": "Ajouter un mot-clé" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Rechercher", "ACTION_SAVE_CUSTOM_FILTER": "sauvegarder en tant que filtre personnalisé", "PLACEHOLDER_FILTER_NAME": "Écrivez le nom du filtre et appuyez sur \"Entrée\"", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Type", "STATUS": "Statut", @@ -216,7 +217,8 @@ "TAGS": "Mots-clés", "ASSIGNED_TO": "Affecté à", "CREATED_BY": "Créé par", - "CUSTOM_FILTERS": "Filtres personnalisés" + "CUSTOM_FILTERS": "Filtres personnalisés", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Supprime le filtre personnalisé", @@ -256,7 +258,7 @@ }, "PERMISIONS_CATEGORIES": { "EPICS": { - "NAME": "Epics", + "NAME": "Épopées", "VIEW_EPICS": "View epics", "ADD_EPICS": "Add epics", "MODIFY_EPICS": "Modify epics", @@ -403,7 +405,7 @@ }, "EPICS": { "TITLE": "ÉPOPÉES", - "SECTION_NAME": "Epics", + "SECTION_NAME": "Épopées", "EPIC": "ÉPOPÉE", "PAGE_TITLE": "Epics - {{projectName}}", "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", @@ -412,8 +414,8 @@ "UNASSIGNED": "Non affecté" }, "EMPTY": { - "TITLE": "On dirait que vous n'avez pas encore créé d'épopée", - "EXPLANATION": "Créer une épopée pour avoir un niveau au dessus des histoires utilisateur. Les épopées peuvent contenir ou être composées d'histoires utilisateur dans ce projet, ou dans tout autre projet.", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "En savoir plus sur les épopées" }, "TABLE": { @@ -845,7 +847,7 @@ "FILTER_TYPE_ALL_TITLE": "Voir tous", "FILTER_TYPE_PROJECTS": "Projets", "FILTER_TYPE_PROJECT_TITLES": "Voir uniquement les projets", - "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPICS": "Épopées", "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Récits", "FILTER_TYPE_USER_STORIES_TITLES": "Voir uniquement les user stories", @@ -1069,18 +1071,19 @@ "EPIC": { "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", - "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "SECTION_NAME": "Épopée", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Nouveau récit utilisateur", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Objet", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" @@ -1202,7 +1205,7 @@ "KANBAN_ORDER": "Classement du Kanban", "TASKBOARD_ORDER": "trier le tableau des tâches", "US_ORDER": "classement des récits utilisateur", - "COLOR": "color" + "COLOR": "couleur" } }, "BACKLOG": { diff --git a/app/locales/taiga/locale-it.json b/app/locales/taiga/locale-it.json index 05561ca3..f8d9b1d1 100644 --- a/app/locales/taiga/locale-it.json +++ b/app/locales/taiga/locale-it.json @@ -124,7 +124,7 @@ "ISSUE": "Problema", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "Eccomi! taggami", + "PLACEHOLDER": "Enter tag", "DELETE": "Elimina tag", "ADD": "Aggiungi un tag" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Cerca", "ACTION_SAVE_CUSTOM_FILTER": "salva come filtro personalizzato", "PLACEHOLDER_FILTER_NAME": "Scrivi il nome del filtro e premi invio", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Tipo", "STATUS": "Stato", @@ -216,7 +217,8 @@ "TAGS": "Tag", "ASSIGNED_TO": "Assegnato a", "CREATED_BY": "Creato da", - "CUSTOM_FILTERS": "Filtri personalizzati" + "CUSTOM_FILTERS": "Filtri personalizzati", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Elimina il filtro personalizzato", @@ -412,8 +414,8 @@ "UNASSIGNED": "Non assegnato" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { @@ -1070,17 +1072,18 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Nuova storia utente", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Oggetto", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" @@ -1202,7 +1205,7 @@ "KANBAN_ORDER": "ordina kanban", "TASKBOARD_ORDER": "Ordine del pannello dei compiti", "US_ORDER": "Ordine delle storie utente", - "COLOR": "color" + "COLOR": "colore" } }, "BACKLOG": { diff --git a/app/locales/taiga/locale-nb.json b/app/locales/taiga/locale-nb.json index 90958db1..4e9bad0e 100644 --- a/app/locales/taiga/locale-nb.json +++ b/app/locales/taiga/locale-nb.json @@ -124,7 +124,7 @@ "ISSUE": "Hendelse", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "Jeg har den! Ta meg...", + "PLACEHOLDER": "Enter tag", "DELETE": "Slett etikett", "ADD": "Legg til etikett" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Søk", "ACTION_SAVE_CUSTOM_FILTER": "lagre som egendefinert filter", "PLACEHOLDER_FILTER_NAME": "Skriv filternavnet og trykk enter", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Type", "STATUS": "Status", @@ -216,7 +217,8 @@ "TAGS": "Etiketter", "ASSIGNED_TO": "Tildelt til", "CREATED_BY": "Laget av", - "CUSTOM_FILTERS": "Egendefinert filtre" + "CUSTOM_FILTERS": "Egendefinert filtre", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Slett egendefinert filter", @@ -412,8 +414,8 @@ "UNASSIGNED": "Ikke tildelt" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { @@ -1070,9 +1072,9 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "Vi hadde ikke mulighet til å slette: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", "NEW_USERSTORY": "Ny brukerhistorie", "EXISTING_USERSTORY": "Existing user story", @@ -1081,6 +1083,7 @@ "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" diff --git a/app/locales/taiga/locale-nl.json b/app/locales/taiga/locale-nl.json index d637041e..b6484a18 100644 --- a/app/locales/taiga/locale-nl.json +++ b/app/locales/taiga/locale-nl.json @@ -124,7 +124,7 @@ "ISSUE": "Issue", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "Ik ben 'm! Tag me...", + "PLACEHOLDER": "Enter tag", "DELETE": "Verwijder tag", "ADD": "Tag tovoegen" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Zoek", "ACTION_SAVE_CUSTOM_FILTER": "Als eigen filter opslaan", "PLACEHOLDER_FILTER_NAME": "Geef de filternaam in en druk op enter", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Type", "STATUS": "Status", @@ -216,7 +217,8 @@ "TAGS": "Tags", "ASSIGNED_TO": "Toegewezen aan", "CREATED_BY": "Aangemaakt door", - "CUSTOM_FILTERS": "Eigen filters" + "CUSTOM_FILTERS": "Eigen filters", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Verwijder eigen filter", @@ -412,8 +414,8 @@ "UNASSIGNED": "Niet toegewezen" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { @@ -1070,17 +1072,18 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Nieuwe user story", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Onderwerp", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" @@ -1202,7 +1205,7 @@ "KANBAN_ORDER": "kanban volgorde", "TASKBOARD_ORDER": "taakbord volgorde", "US_ORDER": "us volgorde", - "COLOR": "color" + "COLOR": "kleur" } }, "BACKLOG": { diff --git a/app/locales/taiga/locale-pl.json b/app/locales/taiga/locale-pl.json index fceae681..a4f7c8d9 100644 --- a/app/locales/taiga/locale-pl.json +++ b/app/locales/taiga/locale-pl.json @@ -124,7 +124,7 @@ "ISSUE": "Zgłoszenie", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "Otaguj mnie!...", + "PLACEHOLDER": "Enter tag", "DELETE": "Usuń tag", "ADD": "Dodaj tag" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Szukaj", "ACTION_SAVE_CUSTOM_FILTER": "zapisz jako filtr niestandardowy", "PLACEHOLDER_FILTER_NAME": "Wpisz nazwę filtru i kliknij enter", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Typ", "STATUS": "Statusy", @@ -216,7 +217,8 @@ "TAGS": "Tagi", "ASSIGNED_TO": "Przypisane do", "CREATED_BY": "Stworzona przez", - "CUSTOM_FILTERS": "Filtry niestandardowe" + "CUSTOM_FILTERS": "Filtry niestandardowe", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Usuń filtr niestandardowy", @@ -412,8 +414,8 @@ "UNASSIGNED": "Nieprzypisane" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { @@ -1070,17 +1072,18 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Nowa historyjka użytkownika", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Temat", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" @@ -1202,7 +1205,7 @@ "KANBAN_ORDER": "kolejność kanban", "TASKBOARD_ORDER": "kolejność tablicy zadań", "US_ORDER": "Kolejność HU", - "COLOR": "color" + "COLOR": "kolor" } }, "BACKLOG": { diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index 7c2d54b4..38675046 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -124,7 +124,7 @@ "ISSUE": "Problema", "EPIC": "Épico", "TAGS": { - "PLACEHOLDER": "Adicionar tags...", + "PLACEHOLDER": "Enter tag", "DELETE": "Apagar tag", "ADD": "Adicionar tag" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Procurar", "ACTION_SAVE_CUSTOM_FILTER": "salve como filtro personalizado", "PLACEHOLDER_FILTER_NAME": "Digite o nome do filtro e pressione Enter", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Tipo", "STATUS": "Status", @@ -216,7 +217,8 @@ "TAGS": "Tags", "ASSIGNED_TO": "Atribuído a", "CREATED_BY": "Criado por", - "CUSTOM_FILTERS": "Filtros personalizados" + "CUSTOM_FILTERS": "Filtros personalizados", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Apagar filtro personalizado", @@ -256,7 +258,7 @@ }, "PERMISIONS_CATEGORIES": { "EPICS": { - "NAME": "Epics", + "NAME": "Épicos", "VIEW_EPICS": "View epics", "ADD_EPICS": "Add epics", "MODIFY_EPICS": "Modify epics", @@ -403,7 +405,7 @@ }, "EPICS": { "TITLE": "ÉPICOS", - "SECTION_NAME": "Epics", + "SECTION_NAME": "Épicos", "EPIC": "ÉPICO", "PAGE_TITLE": "Epics - {{projectName}}", "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", @@ -412,8 +414,8 @@ "UNASSIGNED": "Não-atribuído" }, "EMPTY": { - "TITLE": "Parece que você ainda não criou nenhum épico ", - "EXPLANATION": "Crie um épico para ter um nível superior de Histórias de Usuário. Épicos podem conter ou serem compostos por Histórias de Usuário deste ou de qualquer outro projeto", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Saiba mais sobre épicos" }, "TABLE": { @@ -845,7 +847,7 @@ "FILTER_TYPE_ALL_TITLE": "Mostrar tudo", "FILTER_TYPE_PROJECTS": "Projetos", "FILTER_TYPE_PROJECT_TITLES": "Mostrar somente projetos", - "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPICS": "Épicos", "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Histórias", "FILTER_TYPE_USER_STORIES_TITLES": "Mostrar apenas histórias de usuários", @@ -1069,18 +1071,19 @@ "EPIC": { "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", - "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "SECTION_NAME": "Épico", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Nova história de usuário", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Assunto", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" @@ -1202,7 +1205,7 @@ "KANBAN_ORDER": "pedido kanban", "TASKBOARD_ORDER": "Ordem de quadro de tarefa", "US_ORDER": "ordem da história de usuário", - "COLOR": "color" + "COLOR": "cor" } }, "BACKLOG": { diff --git a/app/locales/taiga/locale-ru.json b/app/locales/taiga/locale-ru.json index 5d5c6c53..0f41251b 100644 --- a/app/locales/taiga/locale-ru.json +++ b/app/locales/taiga/locale-ru.json @@ -124,7 +124,7 @@ "ISSUE": "Запрос", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "Назначьте тэг", + "PLACEHOLDER": "Enter tag", "DELETE": "Удалить тэг", "ADD": "Добавить тэг" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Поиск", "ACTION_SAVE_CUSTOM_FILTER": "сохранить как специальный фильтр", "PLACEHOLDER_FILTER_NAME": "Введите название фильтра и нажмите \"ввод\"", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Тип", "STATUS": "Статус", @@ -216,7 +217,8 @@ "TAGS": "Тэги", "ASSIGNED_TO": "Назначено", "CREATED_BY": "Создано", - "CUSTOM_FILTERS": "Собственные фильтры" + "CUSTOM_FILTERS": "Собственные фильтры", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Удалить фильтр", @@ -412,8 +414,8 @@ "UNASSIGNED": "Не назначено" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { @@ -1070,17 +1072,18 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Новая пользовательская история", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Тема", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" @@ -1202,7 +1205,7 @@ "KANBAN_ORDER": "порядок kanban", "TASKBOARD_ORDER": "порядок панели задач", "US_ORDER": "порядок ПИ", - "COLOR": "color" + "COLOR": "цвет" } }, "BACKLOG": { diff --git a/app/locales/taiga/locale-sv.json b/app/locales/taiga/locale-sv.json index 61bcc031..33aae0c2 100644 --- a/app/locales/taiga/locale-sv.json +++ b/app/locales/taiga/locale-sv.json @@ -124,7 +124,7 @@ "ISSUE": "ärende", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "Det är jag! Tagga mig ...", + "PLACEHOLDER": "Enter tag", "DELETE": "Ta bort etikett", "ADD": "Lägg till etikett" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Sök", "ACTION_SAVE_CUSTOM_FILTER": "spara som anpassad filter", "PLACEHOLDER_FILTER_NAME": "Skriv filternamnet och tryck på ", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Typ", "STATUS": "Status", @@ -216,7 +217,8 @@ "TAGS": "Etiketter", "ASSIGNED_TO": "Tilldelad till", "CREATED_BY": "Skapad av", - "CUSTOM_FILTERS": "Anpassad filter" + "CUSTOM_FILTERS": "Anpassad filter", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Ta bort anpassad filter.", @@ -412,8 +414,8 @@ "UNASSIGNED": "Ej tilldelad" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { @@ -1070,17 +1072,18 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Ny användarhistorie", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Titel", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" @@ -1202,7 +1205,7 @@ "KANBAN_ORDER": "kanban-sortering", "TASKBOARD_ORDER": "Sortera uppgiftstavlan", "US_ORDER": "sortera US", - "COLOR": "color" + "COLOR": "färg" } }, "BACKLOG": { diff --git a/app/locales/taiga/locale-tr.json b/app/locales/taiga/locale-tr.json index 4e8ca811..029bd85e 100644 --- a/app/locales/taiga/locale-tr.json +++ b/app/locales/taiga/locale-tr.json @@ -124,7 +124,7 @@ "ISSUE": "Sorun", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "Ben O'yum! Etiketle beni...", + "PLACEHOLDER": "Enter tag", "DELETE": "Etiket sil", "ADD": "Etiket ekle" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "Ara", "ACTION_SAVE_CUSTOM_FILTER": "özel filtre olarak kaydet", "PLACEHOLDER_FILTER_NAME": "Filtre adı yazın ve enter a basın", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "Tip", "STATUS": "Durum ", @@ -216,7 +217,8 @@ "TAGS": "Etiketler ", "ASSIGNED_TO": "Atanmış", "CREATED_BY": "Oluşturan", - "CUSTOM_FILTERS": "Özel filtreler" + "CUSTOM_FILTERS": "Özel filtreler", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "Özel filtre sil", @@ -412,8 +414,8 @@ "UNASSIGNED": "Atama Yok" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { @@ -1070,17 +1072,18 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "Yeni kullanıcı hikayesi", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "Konu", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" @@ -1202,7 +1205,7 @@ "KANBAN_ORDER": "kanban sırası", "TASKBOARD_ORDER": "Görev panosu sırası", "US_ORDER": "kh sırası", - "COLOR": "color" + "COLOR": "renk" } }, "BACKLOG": { diff --git a/app/locales/taiga/locale-zh-hant.json b/app/locales/taiga/locale-zh-hant.json index 0dd3f5af..6af41fbe 100644 --- a/app/locales/taiga/locale-zh-hant.json +++ b/app/locales/taiga/locale-zh-hant.json @@ -124,7 +124,7 @@ "ISSUE": "問題", "EPIC": "Epic", "TAGS": { - "PLACEHOLDER": "我在這裏,請標注我", + "PLACEHOLDER": "Enter tag", "DELETE": "刪除Tag", "ADD": "新增標籤" }, @@ -208,6 +208,7 @@ "TITLE_ACTION_SEARCH": "搜尋", "ACTION_SAVE_CUSTOM_FILTER": "儲存為客製過濾器 ", "PLACEHOLDER_FILTER_NAME": "寫入過濾器名稱後按下enter ", + "APPLIED_FILTERS_NUM": "filters applied", "CATEGORIES": { "TYPE": "類型", "STATUS": "狀態", @@ -216,7 +217,8 @@ "TAGS": "標籤", "ASSIGNED_TO": "指派給 ", "CREATED_BY": "由創建", - "CUSTOM_FILTERS": "客製過濾器 " + "CUSTOM_FILTERS": "客製過濾器 ", + "EPIC": "Epic" }, "CONFIRM_DELETE": { "TITLE": "刪除客製過濾器 ", @@ -412,8 +414,8 @@ "UNASSIGNED": "未指派" }, "EMPTY": { - "TITLE": "It looks like you have not created any epics yet", - "EXPLANATION": "Create an epic to have a superior level of User Stories. Epics can contain or be composed by User Stories from this or any other project", + "TITLE": "It looks like there aren't any epics yet", + "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", "HELP": "Learn more about epics" }, "TABLE": { @@ -1070,17 +1072,18 @@ "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", - "TITLE_LIGHTBOX_DELETE_RELATED_USERSTORY": "Delete related userstory...", - "MSG_LIGHTBOX_DELETE_RELATED_USERSTORY": "the related userstory '{{subject}}'", - "ERROR_DELETE_RELATED_USERSTORY": "We have not been able to delete: {{errorMessage}}", + "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", + "MSG_LIGHTBOX_UNLINK_RELATED_USERSTORY": "It will delete the link to the related userstory '{{subject}}'", + "ERROR_UNLINK_RELATED_USERSTORY": "We have not been able to unlink: {{errorMessage}}", "CREATE_RELATED_USERSTORIES": "Create a relationship with", - "NEW_USERSTORY": "New user story", + "NEW_USERSTORY": "新使用者故事", "EXISTING_USERSTORY": "Existing user story", "CHOOSE_PROJECT_FOR_CREATION": "What's the project?", - "SUBJECT": "Subject", + "SUBJECT": "主旨", "SUBJECT_BULK_MODE": "Subject (bulk insert)", "CHOOSE_PROJECT_FROM": "What's the project?", "CHOOSE_USERSTORY": "What's the user story?", + "NO_USERSTORIES": "This project has no User Stories yet. Please select another project.", "FILTER_USERSTORIES": "Filter user stories", "LIGHTBOX_TITLE_BLOKING_EPIC": "Blocking epic", "ACTION_DELETE": "Delete epic" @@ -1202,7 +1205,7 @@ "KANBAN_ORDER": "kanban看板次序", "TASKBOARD_ORDER": "任務板次序", "US_ORDER": "使用者故事次序", - "COLOR": "color" + "COLOR": "顏色" } }, "BACKLOG": { From 0bd1ebcd4458bfa5c35994840e179734b7e4e9e6 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 28 Sep 2016 10:56:51 +0200 Subject: [PATCH 297/315] fix block project page navigation --- app/index.jade | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/index.jade b/app/index.jade index 6f5cd237..7ba7bb63 100644 --- a/app/index.jade +++ b/app/index.jade @@ -21,7 +21,8 @@ html(lang="en") body(tg-main) div(tg-navigation-bar, ng-if="!errorHandling.showingError") - div.master(ng-view, ng-if="!errorHandling.showingError") + div(ng-if="!errorHandling.showingError") + div.master(ng-view) div(ng-if="errorHandling.notfound", ng-include="'error/not-found.html'") div(ng-if="errorHandling.error", ng-include="'error/error.html'") From 593a62ce4abec3880f1d2ec006ae021c567f4cc5 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 28 Sep 2016 13:01:50 +0200 Subject: [PATCH 298/315] fix reset tags component in lb --- app/coffee/modules/common/lightboxes.coffee | 52 ++++++++++++------- app/coffee/modules/issues/lightboxes.coffee | 7 +-- .../modules/taskboard/lightboxes.coffee | 10 +++- .../modules/lightbox-create-issue.jade | 2 +- .../modules/lightbox-task-create-edit.jade | 2 +- .../modules/lightbox-us-create-edit.jade | 2 +- 6 files changed, 48 insertions(+), 27 deletions(-) diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee index 0f149171..a012d064 100644 --- a/app/coffee/modules/common/lightboxes.coffee +++ b/app/coffee/modules/common/lightboxes.coffee @@ -36,9 +36,11 @@ trim = @.taiga.trim # the lightboxContent hide/show doesn't have sense because is an IE hack class LightboxService extends taiga.Service - constructor: (@animationFrame, @q) -> + constructor: (@animationFrame, @q, @rootScope) -> + + open: ($el, onClose) -> + @.onClose = onClose - open: ($el) -> if _.isString($el) $el = $($el) defered = @q.defer() @@ -71,25 +73,29 @@ class LightboxService extends taiga.Service return defered.promise close: ($el) -> - if _.isString($el) - $el = $($el) - docEl = angular.element(document) - docEl.off(".lightbox") - docEl.off(".keyboard-navigation") # Hack: to fix problems in the WYSIWYG textareas when press ENTER + return new Promise (resolve) => + if _.isString($el) + $el = $($el) + docEl = angular.element(document) + docEl.off(".lightbox") + docEl.off(".keyboard-navigation") # Hack: to fix problems in the WYSIWYG textareas when press ENTER - @animationFrame.add -> - $el.addClass('close') + @animationFrame.add => + $el.addClass('close') - $el.one "transitionend", => - $el.removeAttr('style') - $el.removeClass("open").removeClass('close') + $el.one "transitionend", => + $el.removeAttr('style') + $el.removeClass("open").removeClass('close') + if @.onClose + @rootScope.$apply(@.onClose) + resolve() - if $el.hasClass("remove-on-close") - scope = $el.data("scope") - scope.$destroy() if scope - $el.remove() + if $el.hasClass("remove-on-close") + scope = $el.data("scope") + scope.$destroy() if scope + $el.remove() closeAll: -> docEl = angular.element(document) @@ -97,7 +103,7 @@ class LightboxService extends taiga.Service @.close($(lightboxEl)) -module.service("lightboxService", ["animationFrame", "$q", LightboxService]) +module.service("lightboxService", ["animationFrame", "$q", "$rootScope", LightboxService]) class LightboxKeyboardNavigationService extends taiga.Service @@ -358,7 +364,10 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, $el.find("label.team-requirement").removeClass("selected") $el.find("label.client-requirement").removeClass("selected") - lightboxService.open($el) + $scope.createEditUsOpen = true + + lightboxService.open $el, () -> + $scope.createEditUsOpen = false $scope.$on "usform:edit", (ctx, us, attachments) -> form.reset() if form @@ -391,7 +400,10 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, else $el.find("label.client-requirement").removeClass("selected") - lightboxService.open($el) + $scope.createEditUsOpen = true + + lightboxService.open $el, () -> + $scope.createEditUsOpen = false createAttachments = (obj) -> promises = _.map attachmentsToAdd.toJS(), (attachment) -> @@ -451,8 +463,10 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, $el.on "click", ".close", (event) -> event.preventDefault() + $scope.$apply -> $scope.us.revert() + lightboxService.close($el) $el.keydown (event) -> diff --git a/app/coffee/modules/issues/lightboxes.coffee b/app/coffee/modules/issues/lightboxes.coffee index a9782e59..e94f485e 100644 --- a/app/coffee/modules/issues/lightboxes.coffee +++ b/app/coffee/modules/issues/lightboxes.coffee @@ -45,8 +45,8 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading, resetAttachments() $el.find(".tag-input").val("") - - lightboxService.open($el) + lightboxService.open $el, () -> + $scope.createIssueOpen = false $scope.issue = { project: project.id @@ -58,10 +58,11 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading, tags: [] } + $scope.createIssueOpen = true + $scope.$on "$destroy", -> $el.off() - createAttachments = (obj) -> promises = _.map attachmentsToAdd.toJS(), (attachment) -> return attachmentsService.upload(attachment.file, obj.id, $scope.issue.project, 'issue') diff --git a/app/coffee/modules/taskboard/lightboxes.coffee b/app/coffee/modules/taskboard/lightboxes.coffee index 9626e245..276cadc9 100644 --- a/app/coffee/modules/taskboard/lightboxes.coffee +++ b/app/coffee/modules/taskboard/lightboxes.coffee @@ -119,7 +119,10 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer $el.find(".title").html(newTask + " ") $el.find(".tag-input").val("") - lightboxService.open($el) + lightboxService.open $el, () -> + $scope.createEditTaskOpen = false + + $scope.createEditTaskOpen = true $scope.$on "taskform:edit", (ctx, task, attachments) -> $scope.task = task @@ -137,7 +140,10 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer $el.find(".title").html(edit + " ") $el.find(".tag-input").val("") - lightboxService.open($el) + lightboxService.open $el, () -> + $scope.createEditTaskOpen = false + + $scope.createEditTaskOpen = true submitButton = $el.find(".submit-button") diff --git a/app/partials/includes/modules/lightbox-create-issue.jade b/app/partials/includes/modules/lightbox-create-issue.jade index f572c874..ec245f0a 100644 --- a/app/partials/includes/modules/lightbox-create-issue.jade +++ b/app/partials/includes/modules/lightbox-create-issue.jade @@ -30,7 +30,7 @@ form fieldset tg-tag-line-common.tags-block( - ng-if="project" + ng-if="project && createIssueOpen" project="project" tags="issue.tags" permissions="add_issue" diff --git a/app/partials/includes/modules/lightbox-task-create-edit.jade b/app/partials/includes/modules/lightbox-task-create-edit.jade index 3cd129b5..4e61d4f6 100644 --- a/app/partials/includes/modules/lightbox-task-create-edit.jade +++ b/app/partials/includes/modules/lightbox-task-create-edit.jade @@ -31,7 +31,7 @@ form fieldset tg-tag-line-common.tags-block( - ng-if="project" + ng-if="project && createEditTaskOpen" project="project" tags="task.tags" permissions="add_task" diff --git a/app/partials/includes/modules/lightbox-us-create-edit.jade b/app/partials/includes/modules/lightbox-us-create-edit.jade index d33b86b1..2c9d7ccb 100644 --- a/app/partials/includes/modules/lightbox-us-create-edit.jade +++ b/app/partials/includes/modules/lightbox-us-create-edit.jade @@ -25,7 +25,7 @@ form fieldset tg-tag-line-common.tags-block( - ng-if="project" + ng-if="project && createEditUsOpen" project="project" tags="us.tags" permissions="add_us" From 28c95d8547820bb006ee2672bcf9aa0d0bfd795c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 28 Sep 2016 14:01:53 +0200 Subject: [PATCH 299/315] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32b15b1e..b4d17be6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,11 @@ - Errors (not found, server error, permissions and blocked project) don't change the current url. - Neew Attachments image slider in preview mode. - New admin area to edit the tag colors used in your project. +- Set color when add a new tags to epics, stories, tasks or issues. - Display the current user (me) at first in assignment lightbox (thanks to [@mikaoelitiana](https://github.com/mikaoelitiana)) - Divide the user dashboard in two columns in large screens. - Upvote and downvote issues from the issues list. -- Show points per role in statsection of the taskboard panel. +- Show points per role in statsection of the taskboard panel. (thanks to [@fmartingr](https://github.com/fmartingr)) - Show a funny randon animals/color for users with no avatar (like project logos). - Show Open Sprints in the left navigation menu (backlog submenu). - Filters: From f6f251d2dc366adbd876685bbd04e95e8cf98627 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 29 Sep 2016 09:18:13 +0200 Subject: [PATCH 300/315] Fixing epics e2e suite --- e2e/helpers/epic-detail-helper.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/helpers/epic-detail-helper.js b/e2e/helpers/epic-detail-helper.js index 84368661..4db091e9 100644 --- a/e2e/helpers/epic-detail-helper.js +++ b/e2e/helpers/epic-detail-helper.js @@ -32,6 +32,7 @@ helper.colorEditor = function() { helper.relatedUserstories = function() { let el = $('tg-related-userstories'); + let lightboxCreateRelatedUserStories = el.$(".lightbox-create-related-user-stories"); let obj = { el: el, @@ -42,7 +43,7 @@ helper.relatedUserstories = function() { el.$(".e2e-single-creation-label").click(); el.$(".e2e-new-userstory-input-text").sendKeys(subject); el.$(".e2e-create-userstory-button").click(); - await browser.waitForAngular(); + await utils.lightbox.close(lightboxCreateRelatedUserStories); }, createNewUserStories: async function(subject) { @@ -51,17 +52,16 @@ helper.relatedUserstories = function() { el.$(".e2e-bulk-creation-label").click(); el.$(".e2e-new-userstories-input-textarea").sendKeys(subject); el.$(".e2e-create-userstory-button").click(); - await browser.waitForAngular(); + await utils.lightbox.close(lightboxCreateRelatedUserStories); }, selectFirstRelatedUserstory: async function() { el.$(".e2e-add-userstory-button").click(); el.$(".e2e-existing-user-story-label").click(); el.$(".e2e-filter-userstories-input").click().sendKeys("#1"); - await browser.waitForAngular(); el.$$(".e2e-userstories-select option").get(1).click() el.$(".e2e-select-related-userstory-button").click(); - await browser.waitForAngular(); + await utils.lightbox.close(lightboxCreateRelatedUserStories); }, deleteFirstRelatedUserstory: async function() { From c8204899fd9a6e4f4ad7d686c3b9146cc3bfbb1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 29 Sep 2016 10:04:09 +0200 Subject: [PATCH 301/315] Add shadow to filters box --- app/modules/components/filter/filter.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/modules/components/filter/filter.scss b/app/modules/components/filter/filter.scss index 1d78ca71..9b9b7276 100644 --- a/app/modules/components/filter/filter.scss +++ b/app/modules/components/filter/filter.scss @@ -1,5 +1,6 @@ tg-filter { background-color: $mass-white; + box-shadow: 1px 1px 5px rgba(0, 0, 0, .2); display: block; left: 0; min-height: 100%; From f1534cbb8c5873cfadc676c96ae3c35a1c837ac6 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 29 Sep 2016 10:01:25 +0200 Subject: [PATCH 302/315] Fixing discover e2e suite --- e2e/suites/discover/discover-search.e2e.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/e2e/suites/discover/discover-search.e2e.js b/e2e/suites/discover/discover-search.e2e.js index b843bde5..2cbb2ddf 100644 --- a/e2e/suites/discover/discover-search.e2e.js +++ b/e2e/suites/discover/discover-search.e2e.js @@ -29,8 +29,6 @@ describe('discover search', () => { discoverHelper.searchFilter(3); - await htmlChanges(); - let url = await browser.getCurrentUrl(); let projects = discoverHelper.searchProjects(); From b64fd1d44c72c3f50dc65852a528cfb8d1632d04 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 29 Sep 2016 10:30:24 +0200 Subject: [PATCH 303/315] Fixing home e2e suite --- e2e/utils/lightbox.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/e2e/utils/lightbox.js b/e2e/utils/lightbox.js index 8c041672..d88b50cd 100644 --- a/e2e/utils/lightbox.js +++ b/e2e/utils/lightbox.js @@ -56,8 +56,11 @@ lightbox.close = async function(el) { }, 4000); } catch (e) { new Error('Lightbox doesn\'t close') + return false; } } + + return true; }; lightbox.confirm = {}; From 4d2c56d4ae45d12d1f2f0a429d087fe117f16e8d Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 29 Sep 2016 11:07:24 +0200 Subject: [PATCH 304/315] fix taskboard tasks --- app/coffee/modules/taskboard/main.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee index 97252b9b..74f95d84 100644 --- a/app/coffee/modules/taskboard/main.coffee +++ b/app/coffee/modules/taskboard/main.coffee @@ -330,7 +330,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga refreshTagsColors: -> return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) => - @scope.project.tags_colors = tags_colors + @scope.project.tags_colors = tags_colors._attrs loadSprint: -> return @rs.sprints.get(@scope.projectId, @scope.sprintId).then (sprint) => From bfcc3bb2dfa714e49eb25acfe4598b5065e552e0 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Thu, 29 Sep 2016 12:26:02 +0200 Subject: [PATCH 305/315] fix kanban tags --- app/coffee/modules/kanban/main.coffee | 2 +- app/modules/components/filter/filter.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/coffee/modules/kanban/main.coffee b/app/coffee/modules/kanban/main.coffee index f50f9007..643472f5 100644 --- a/app/coffee/modules/kanban/main.coffee +++ b/app/coffee/modules/kanban/main.coffee @@ -179,7 +179,7 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi refreshTagsColors: -> return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) => - @scope.project.tags_colors = tags_colors + @scope.project.tags_colors = tags_colors._attrs loadUserstories: -> params = { diff --git a/app/modules/components/filter/filter.scss b/app/modules/components/filter/filter.scss index 9b9b7276..3782d855 100644 --- a/app/modules/components/filter/filter.scss +++ b/app/modules/components/filter/filter.scss @@ -1,6 +1,6 @@ tg-filter { background-color: $mass-white; - box-shadow: 1px 1px 5px rgba(0, 0, 0, .2); + box-shadow: 1px 1px 5px rgbag($primary, .2); display: block; left: 0; min-height: 100%; From 01fcbfcf5ed6eb2ceff1ceac13b0c658e0309711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 29 Sep 2016 12:26:38 +0200 Subject: [PATCH 306/315] Change in user avatar the gray color --- app/modules/services/avatar.service.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/services/avatar.service.coffee b/app/modules/services/avatar.service.coffee index 0b55dca7..f2ba8739 100644 --- a/app/modules/services/avatar.service.coffee +++ b/app/modules/services/avatar.service.coffee @@ -31,7 +31,7 @@ class AvatarService "rgba( 178, 176, 204, 1 )" "rgba( 183, 203, 131, 1 )" "rgba( 210, 198, 139, 1 )" - "rgba( 178, 178, 178, 1 )" + "rgba( 214, 161, 212, 1 )" "rgba( 247, 154, 154, 1 )" ] From cd4f3bef66b8a1ca2c9995770e44d8613277118a Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 29 Sep 2016 12:49:58 +0200 Subject: [PATCH 307/315] ISSUE #4579:Wiki, hide activity tab if it's empty --- app/modules/wiki/history/wiki-history.jade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/modules/wiki/history/wiki-history.jade b/app/modules/wiki/history/wiki-history.jade index 57715241..131df94b 100644 --- a/app/modules/wiki/history/wiki-history.jade +++ b/app/modules/wiki/history/wiki-history.jade @@ -1,11 +1,11 @@ -nav.history-tabs(ng-if="vm.historyEntries") +nav.history-tabs(ng-if="vm.historyEntries.count()>0") a.history-tab.active( href="" title="{{ACTIVITY.TITLE}}" translate="ACTIVITY.TITLE" ) -section.wiki-history(ng-if="vm.historyEntries") +section.wiki-history(ng-if="vm.historyEntries.count()>0") tg-wiki-history-entry.wiki-history-entry( tg-repeat="historyEntry in vm.historyEntries" history-entry="historyEntry" From cf6d2548a71ab5cdab39ddbbec9883e10f0cdb72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 29 Sep 2016 13:02:32 +0200 Subject: [PATCH 308/315] Fix avatar width --- app/modules/history/history-lightbox/history-entry.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/modules/history/history-lightbox/history-entry.scss b/app/modules/history/history-lightbox/history-entry.scss index 763921aa..fa0a2763 100644 --- a/app/modules/history/history-lightbox/history-entry.scss +++ b/app/modules/history/history-lightbox/history-entry.scss @@ -11,6 +11,7 @@ flex-grow: 0; flex-shrink: 0; margin-right: 1.5rem; + width: 50px; } .entry-main { flex: 1; @@ -19,7 +20,7 @@ .entry-data { align-items: flex-start; display: flex; - margin-bottom: 1rem; + margin-bottom: .5rem; } .entry-creator { color: $primary; From 4e6f49f8a401c808d474fc6ce4d81b5db567f99c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 29 Sep 2016 13:37:21 +0200 Subject: [PATCH 309/315] Fix issues table arrows --- app/styles/modules/issues/issues-table.scss | 23 ++++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/styles/modules/issues/issues-table.scss b/app/styles/modules/issues/issues-table.scss index 2eddaf5b..1b0ad746 100644 --- a/app/styles/modules/issues/issues-table.scss +++ b/app/styles/modules/issues/issues-table.scss @@ -51,11 +51,11 @@ } } .level-field { - flex-basis: 75px; + flex-basis: 85px; flex-grow: 0; flex-shrink: 0; text-align: center; - width: 75px; + width: 85px; } .votes { color: $gray; @@ -75,12 +75,19 @@ &.inactive { color: $gray-light; } - svg { - @include svg-size(.75rem); - fill: $gray; - margin-right: .25rem; - vertical-align: middle; - } + } + .icon-upvote { + @include svg-size(.75rem); + fill: $gray; + margin-right: .25rem; + vertical-align: middle; + } + .icon-arrow-up, + .icon-arrow-down { + @include svg-size(.7rem); + fill: $gray-light; + margin-left: .25rem; + vertical-align: middle; } .is-voted { color: $primary-light; From dc423af0e93a875cadb94fce6ac6cdb6641f7a55 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 29 Sep 2016 13:12:11 +0200 Subject: [PATCH 310/315] Issue: #4575, #4576, #4578 Missing tag colors --- app/coffee/modules/common/tags.coffee | 6 +++--- app/coffee/modules/kanban/kanban-usertories.coffee | 3 +-- app/coffee/modules/taskboard/taskboard-tasks.coffee | 5 ++--- app/modules/components/card/card-templates/card-tags.jade | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/coffee/modules/common/tags.coffee b/app/coffee/modules/common/tags.coffee index faa3ec3a..cb38eee9 100644 --- a/app/coffee/modules/common/tags.coffee +++ b/app/coffee/modules/common/tags.coffee @@ -63,7 +63,7 @@ ColorizeTagsDirective = -> backlog: _.template(""" <% _.each(tags, function(tag) { %> + <% if (tag[1] !== null) { %> style="border-left: 5px solid <%- tag[1] %>" <% } %> title="<%- tag[0] %>"><%- tag[0] %> @@ -73,7 +73,7 @@ ColorizeTagsDirective = -> <% _.each(tags, function(tag) { %> + <% if (tag[1] !== null) { %> style="border-color: <%- tag[1] %>" <% } %> title="<%- tag[0] %>" /> @@ -83,7 +83,7 @@ ColorizeTagsDirective = -> <% _.each(tags, function(tag) { %> + <% if (tag[1] !== null) { %> style="border-color: <%- tag[1] %>" <% } %> title="<%- tag[0] %>" /> diff --git a/app/coffee/modules/kanban/kanban-usertories.coffee b/app/coffee/modules/kanban/kanban-usertories.coffee index 0e811b13..dc1faf82 100644 --- a/app/coffee/modules/kanban/kanban-usertories.coffee +++ b/app/coffee/modules/kanban/kanban-usertories.coffee @@ -176,8 +176,7 @@ class KanbanUserstoriesService extends taiga.Service us.id = usModel.id us.assigned_to = @.usersById[usModel.assigned_to] us.colorized_tags = _.map us.model.tags, (tag) => - color = @.project.tags_colors[tag] - return {name: tag, color: color} + return {name: tag[0], color: tag[1]} return us diff --git a/app/coffee/modules/taskboard/taskboard-tasks.coffee b/app/coffee/modules/taskboard/taskboard-tasks.coffee index cc8f087d..013238fb 100644 --- a/app/coffee/modules/taskboard/taskboard-tasks.coffee +++ b/app/coffee/modules/taskboard/taskboard-tasks.coffee @@ -162,9 +162,8 @@ class TaskboardTasksService extends taiga.Service task.images = _.filter taskModel.attachments, (it) -> return !!it.thumbnail_card_url task.id = taskModel.id task.assigned_to = @.usersById[taskModel.assigned_to] - task.colorized_tags = _.map task.model.tags, (tag) => - color = @.project.tags_colors[tag] - return {name: tag, color: color} + task.colorized_tags = _.map task.model.tags, (tag) => + return {name: tag[0], color: tag[1]} usTasks[taskModel.user_story][taskModel.status].push(task) diff --git a/app/modules/components/card/card-templates/card-tags.jade b/app/modules/components/card/card-templates/card-tags.jade index a5161331..cb18254d 100644 --- a/app/modules/components/card/card-templates/card-tags.jade +++ b/app/modules/components/card/card-templates/card-tags.jade @@ -1,7 +1,7 @@ -.card-tags(ng-if="vm.visible('tags')") +.card-tags(ng-if="vm.visible('tags')") span.card-tag( tg-repeat="tag in vm.item.get('colorized_tags') track by tag.get('name')" - style="background-color: {{tag.get('color')}}" + style="background-color: {{tag.get('color')}}" title="{{tag.get('name')}}" ng-if="tag.get('color')" ) From ad26894225ed6a9954bfe2f97b1d7379e393a84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Thu, 29 Sep 2016 15:19:17 +0200 Subject: [PATCH 311/315] Enlarge wiki links layout --- app/partials/wiki/wiki-list.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/partials/wiki/wiki-list.jade b/app/partials/wiki/wiki-list.jade index 413e694d..97b4025c 100644 --- a/app/partials/wiki/wiki-list.jade +++ b/app/partials/wiki/wiki-list.jade @@ -10,7 +10,7 @@ div.wrapper( tg-wiki-nav ng-model="wikiLinks" ) - section.main.wiki + section.main header h1 span(tg-bo-bind="project.name") From 2dc38b516ddfae63d16d076d2c25836e4619b37d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 29 Sep 2016 16:45:13 +0200 Subject: [PATCH 312/315] Add some loading spinners in Admin > Attributes > Tags --- .../modules/admin/project-values.coffee | 47 ++++++++++++------- .../includes/modules/admin/project-tags.jade | 32 +++++++------ app/styles/layout/admin-project-tags.scss | 12 +++++ 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index 42df12a3..a00d4710 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -724,8 +724,8 @@ class ProjectTagsController extends taiga.Controller loadTags: => return @rs.projects.tagsColors(@scope.projectId).then (tags) => - @scope.projectTagsAll = _.map(tags.getAttrs(), (color, name) => - @model.make_model('tag', {name: name, color: color})) + @scope.projectTagsAll = _.map tags.getAttrs(), (color, name) => + @model.make_model('tag', {name: name, color: color}) @.filterAndSortTags() @.loading = false @@ -735,17 +735,20 @@ class ProjectTagsController extends taiga.Controller (tag) => tag.name.indexOf(@scope.tagsFilter.name) != -1 ) - deleteTag: (tag) => - return @rs.projects.deleteTag(@scope.projectId, tag) - createTag: (tag, color) => return @rs.projects.createTag(@scope.projectId, tag, color) editTag: (from_tag, to_tag, color) => if from_tag == to_tag to_tag = null + return @rs.projects.editTag(@scope.projectId, from_tag, to_tag, color) + deleteTag: (tag) => + @scope.loadingDelete = true + return @rs.projects.deleteTag(@scope.projectId, tag).finally => + @scope.loadingDelete = false + startMixingTags: (tag) => @scope.mixingTags.toTag = tag.name @@ -760,9 +763,13 @@ class ProjectTagsController extends taiga.Controller confirmMixingTags: () => toTag = @scope.mixingTags.toTag fromTags = @scope.mixingTags.fromTags - @rs.projects.mixTags(@scope.projectId, toTag, fromTags).then => - @.cancelMixingTags() - @.loadTags() + @scope.loadingMixing = true + @rs.projects.mixTags(@scope.projectId, toTag, fromTags) + .then => + @.cancelMixingTags() + @.loadTags() + .finally => + @scope.loadingMixing = false cancelMixingTags: () => @scope.mixingTags.toTag = null @@ -825,7 +832,7 @@ ProjectTagsDirective = ($log, $repo, $confirm, $location, animationFrame, $trans if focus $el.find(".new-value input:visible").first().focus() - saveValue = (target) -> + saveValue = (target) => formEl = target.parents("form") form = formEl.checksley() return if not form.validate() @@ -834,29 +841,35 @@ ProjectTagsDirective = ($log, $repo, $confirm, $location, animationFrame, $trans originalTag = tag.clone() originalTag.revert() + $scope.loadingEdit = true promise = $ctrl.editTag(originalTag.name, tag.name, tag.color) promise.then => - $ctrl.loadTags() - row = target.parents(".row.table-main") - row.addClass("hidden") - row.siblings(".visualization").removeClass('hidden') + $ctrl.loadTags().then => + row = target.parents(".row.table-main") + row.addClass("hidden") + $scope.loadingEdit = false + row.siblings(".visualization").removeClass('hidden') promise.then null, (response) -> + $scope.loadingEdit = false form.setErrors(response.data) - saveNewValue = (target) -> + saveNewValue = (target) => formEl = target.parents("form") formEl = target form = formEl.checksley() return if not form.validate() + $scope.loadingCreate = true promise = $ctrl.createTag($scope.newValue.tag, $scope.newValue.color) promise.then (data) => - target.addClass("hidden") - $ctrl.loadTags() - initializeNewValue() + $ctrl.loadTags().then => + $scope.loadingCreate = false + target.addClass("hidden") + initializeNewValue() promise.then null, (response) -> + $scope.loadingCreate = false form.setErrors(response.data) cancel = (target) -> diff --git a/app/partials/includes/modules/admin/project-tags.jade b/app/partials/includes/modules/admin/project-tags.jade index dd339453..2c31ea1b 100644 --- a/app/partials/includes/modules/admin/project-tags.jade +++ b/app/partials/includes/modules/admin/project-tags.jade @@ -22,7 +22,7 @@ section data-maxlength="255" ) - .options-column + .options-column(tg-loading="loadingCreate") a.add-new.e2e-save(href="") tg-svg( title="{{'COMMON.ADD' | translate}}", @@ -103,7 +103,7 @@ section title="{{'ADMIN.COMMON.TITLE_ACTION_EDIT_VALUE' | translate}}" ) - a.delete-tag(href="") + a.delete-tag(href="", tg-loading="loadingDelete") tg-svg( svg-icon="icon-trash" title="{{'ADMIN.COMMON.TITLE_ACTION_DELETE_VALUE' | translate}}" @@ -127,7 +127,7 @@ section data-maxlength="255" ) - .options-column + .options-column(tg-loading="loadingEdit") a.save.e2e-save(href="") tg-svg( title="{{'COMMON.SAVE' | translate}}" @@ -145,7 +145,9 @@ section tg-bind-scope ) form(tg-bind-scope) - .row.mixing-row.table-main.visualization(class="{{ ctrl.mixingClass(tag) }}") + .row.mixing-row.table-main.visualization( + ng-class="ctrl.mixingClass(tag)" + ) .color-column .current-color( ng-if="tag.color" @@ -158,25 +160,27 @@ section .status-name span(tg-bo-html="tag.name") - .mixing-options-column + .mixing-options-column( + ng-if="mixingTags.toTag === tag.name" + tg-loading="loadingMixing" + ) .mixing-help-text( - ng-if="mixingTags.toTag === tag.name" translate="ADMIN.PROJECT_VALUES_TAGS.MIXING_HELP_TEXT" ) a.mixing-confirm.button-green( href="" - ng-if="mixingTags.toTag === tag.name && mixingTags.fromTags.length" + ng-if="mixingTags.fromTags.length" translate="ADMIN.PROJECT_VALUES_TAGS.MIXING_MERGE" ) a.mixing-cancel.button-gray( href="" - ng-if="mixingTags.toTag === tag.name" translate="COMMON.CANCEL" ) - span.mixing-selected( - ng-if="mixingTags.fromTags.indexOf(tag.name) !== -1" + + .mixing-options-column( + ng-if="mixingTags.fromTags.indexOf(tag.name) !== -1" + ) + tg-svg.mixing-selected( + title="{{'ADMIN.PROJECT_VALUES_TAGS.SELECTED' | translate}}" + svg-icon="icon-merge" ) - tg-svg( - title="{{'ADMIN.PROJECT_VALUES_TAGS.SELECTED' | translate}}" - svg-icon="icon-merge" - ) diff --git a/app/styles/layout/admin-project-tags.scss b/app/styles/layout/admin-project-tags.scss index d9d7fedf..982b554c 100644 --- a/app/styles/layout/admin-project-tags.scss +++ b/app/styles/layout/admin-project-tags.scss @@ -17,6 +17,10 @@ } .options-column { display: flex; + .loading-spinner { + margin-right: 1.2rem; + width: 1.2rem; + } } .current-color { &.empty-color { @@ -80,6 +84,10 @@ cursor: default; } } + .loading-spinner { + margin-right: 1.2rem; + width: 1.2rem; + } } .mix-tags { position: relative; @@ -94,6 +102,10 @@ } .mixing-options-column { text-align: right; + .loading-spinner { + margin-right: 1.2rem; + width: 1.2rem; + } } .mixing-tags-from, .mixing-tags-to { From 546cfed2cb2fbb1774e1ed82a4bf020a94978e09 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 30 Sep 2016 08:51:13 +0200 Subject: [PATCH 313/315] increase tg-loading priority --- app/coffee/modules/common/loading.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/coffee/modules/common/loading.coffee b/app/coffee/modules/common/loading.coffee index aec1069e..5ee375e2 100644 --- a/app/coffee/modules/common/loading.coffee +++ b/app/coffee/modules/common/loading.coffee @@ -59,6 +59,7 @@ TgLoadingService = ($compile) -> start: -> target = service.settings.target + service.settings.classes.map (className) -> target.removeClass(className) if not target.hasClass('loading') && !service.settings.template @@ -109,6 +110,7 @@ LoadingDirective = ($loading) -> template = $el.html() $scope.$watch attr.tgLoading, (showLoading) => + if showLoading currentLoading = $loading() .target($el) @@ -120,6 +122,7 @@ LoadingDirective = ($loading) -> currentLoading.finish() return { + priority: 99999, link:link } From 5f79c4cbbb062ed3789934525e001f34ca4eaf34 Mon Sep 17 00:00:00 2001 From: Juanfran Date: Fri, 30 Sep 2016 09:35:04 +0200 Subject: [PATCH 314/315] fix padding color attributes --- app/styles/modules/common/colors-table.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/styles/modules/common/colors-table.scss b/app/styles/modules/common/colors-table.scss index fda0e53d..fabf7df4 100644 --- a/app/styles/modules/common/colors-table.scss +++ b/app/styles/modules/common/colors-table.scss @@ -22,6 +22,7 @@ border: 0; } &.edition { + padding-left: 3rem; .current-color { cursor: pointer; } From 98656d19e048c9c8e4e6f8b3d78f0ab595bf5b5b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 30 Sep 2016 13:37:40 +0200 Subject: [PATCH 315/315] Updating translations --- app/locales/taiga/locale-es.json | 28 ++++++++++++++-------------- app/locales/taiga/locale-fr.json | 2 +- app/locales/taiga/locale-pt-br.json | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/locales/taiga/locale-es.json b/app/locales/taiga/locale-es.json index 2fc850e1..b142068e 100644 --- a/app/locales/taiga/locale-es.json +++ b/app/locales/taiga/locale-es.json @@ -258,7 +258,7 @@ }, "PERMISIONS_CATEGORIES": { "EPICS": { - "NAME": "Epics", + "NAME": "Épicas", "VIEW_EPICS": "View epics", "ADD_EPICS": "Add epics", "MODIFY_EPICS": "Modify epics", @@ -404,19 +404,19 @@ "DASHBOARD": "Dashboard de proyecto" }, "EPICS": { - "TITLE": "EPICS", - "SECTION_NAME": "Epics", - "EPIC": "EPIC", - "PAGE_TITLE": "Epics - {{projectName}}", - "PAGE_DESCRIPTION": "The epics list of the project {{projectName}}: {{projectDescription}}", + "TITLE": "ÉPICAS", + "SECTION_NAME": "Épicas", + "EPIC": "Épica", + "PAGE_TITLE": "Épicas - {{projectName}}", + "PAGE_DESCRIPTION": "El listado de épicas del proyecto {{projectName}}: {{projectDescription}}", "DASHBOARD": { - "ADD": "+ ADD EPIC", + "ADD": "+ AÑADIR ÉPICA", "UNASSIGNED": "No asignado" }, "EMPTY": { - "TITLE": "It looks like there aren't any epics yet", + "TITLE": "Parece que todavía no hay épicas.", "EXPLANATION": "Epics are items at a higher level that encompass user stories.
Epics are at the top of the hierarchy and can be used to group user stories together.", - "HELP": "Learn more about epics" + "HELP": "Aprende más sobre Épicas" }, "TABLE": { "VOTES": "Votos", @@ -429,7 +429,7 @@ "VIEW_OPTIONS": "View options" }, "CREATE": { - "TITLE": "New Epic", + "TITLE": "Nueva Épica", "PLACEHOLDER_DESCRIPTION": "Please add descriptive text to help others better understand this epic", "TEAM_REQUIREMENT": "Team requirement", "CLIENT_REQUIREMENT": "Client requirement", @@ -508,7 +508,7 @@ "TITLE": "Módulos", "ENABLE": "Activado", "DISABLE": "Desactivado", - "EPICS": "Epics", + "EPICS": "Épicas", "EPICS_DESCRIPTION": "Visualize and manage the most strategic part of your project", "BACKLOG": "Backlog", "BACKLOG_DESCRIPTION": "Gestiona tus historias de usuario para mantener una vista organizada y priorizada de los próximos trabajos que deberás afrontar. ", @@ -847,7 +847,7 @@ "FILTER_TYPE_ALL_TITLE": "Mostrar todos", "FILTER_TYPE_PROJECTS": "Proyectos", "FILTER_TYPE_PROJECT_TITLES": "Mostrar sólo proyectos", - "FILTER_TYPE_EPICS": "Epics", + "FILTER_TYPE_EPICS": "Épicas", "FILTER_TYPE_EPIC_TITLES": "Show only epics", "FILTER_TYPE_USER_STORIES": "Historias", "FILTER_TYPE_USER_STORIES_TITLES": "Mostrar sólo historias de usuario", @@ -1069,7 +1069,7 @@ } }, "EPIC": { - "PAGE_TITLE": "{{epicSubject}} - Epic {{epicRef}} - {{projectName}}", + "PAGE_TITLE": "{{epicSubject}} - Épica {{epicRef}} - {{projectName}}", "PAGE_DESCRIPTION": "Status: {{epicStatus }}. Description: {{epicDescription}}", "SECTION_NAME": "Epic", "TITLE_LIGHTBOX_UNLINK_RELATED_USERSTORY": "Unlink related userstory", @@ -1436,7 +1436,7 @@ "SEARCH": { "PAGE_TITLE": "Buscar - {{projectName}}", "PAGE_DESCRIPTION": "Busca cualquier cosa: historias de usuario, peticiones, tareas o páginas del wiki en el proyecto {{projectName}}: {{projectDescription}}", - "FILTER_EPICS": "Epics", + "FILTER_EPICS": "Épicas", "FILTER_USER_STORIES": "Historias de Usuario", "FILTER_ISSUES": "Peticiones", "FILTER_TASKS": "Tareas", diff --git a/app/locales/taiga/locale-fr.json b/app/locales/taiga/locale-fr.json index c54acaf6..175404bd 100644 --- a/app/locales/taiga/locale-fr.json +++ b/app/locales/taiga/locale-fr.json @@ -218,7 +218,7 @@ "ASSIGNED_TO": "Affecté à", "CREATED_BY": "Créé par", "CUSTOM_FILTERS": "Filtres personnalisés", - "EPIC": "Epic" + "EPIC": "Épopée" }, "CONFIRM_DELETE": { "TITLE": "Supprime le filtre personnalisé", diff --git a/app/locales/taiga/locale-pt-br.json b/app/locales/taiga/locale-pt-br.json index 38675046..992f5e69 100644 --- a/app/locales/taiga/locale-pt-br.json +++ b/app/locales/taiga/locale-pt-br.json @@ -218,7 +218,7 @@ "ASSIGNED_TO": "Atribuído a", "CREATED_BY": "Criado por", "CUSTOM_FILTERS": "Filtros personalizados", - "EPIC": "Epic" + "EPIC": "Épico" }, "CONFIRM_DELETE": { "TITLE": "Apagar filtro personalizado",