From 78e04578436b54e2d47c89b9f82e8dcc96f6daf9 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 15 Jan 2019 15:47:46 +0200 Subject: [PATCH 01/29] Update translations (ca). --- i18n/ca.i18n.json | 74 +++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/i18n/ca.i18n.json b/i18n/ca.i18n.json index 44e80a62f..d8d90a715 100644 --- a/i18n/ca.i18n.json +++ b/i18n/ca.i18n.json @@ -1,6 +1,6 @@ { "accept": "Accepta", - "act-activity-notify": "Activity Notification", + "act-activity-notify": "Notificació d'activitat", "act-addAttachment": "adjuntat __attachment__ a __card__", "act-addSubtask": "added subtask __checklist__ to __card__", "act-addChecklist": "afegida la checklist _checklist__ a __card__", @@ -11,28 +11,28 @@ "act-createCustomField": "created custom field __customField__", "act-createList": "afegit/da __list__ a __board__", "act-addBoardMember": "afegit/da __member__ a __board__", - "act-archivedBoard": "__board__ moved to Archive", - "act-archivedCard": "__card__ moved to Archive", - "act-archivedList": "__list__ moved to Archive", + "act-archivedBoard": "__tauler__ mogut al Arxiu", + "act-archivedCard": "__fitxa__ moguda al Arxiu", + "act-archivedList": "__llista__ mogud al Arxiu", "act-archivedSwimlane": "__swimlane__ moved to Archive", "act-importBoard": "__board__ importat", "act-importCard": "__card__ importat", "act-importList": "__list__ importat", "act-joinMember": "afegit/da __member__ a __card__", "act-moveCard": "mou __card__ de __oldList__ a __list__", - "act-removeBoardMember": "elimina __member__ de __board__", + "act-removeBoardMember": "elimina __usuari__ del __tauler__", "act-restoredCard": "recupera __card__ a __board__", "act-unjoinMember": "elimina __member__ de __card__", - "act-withBoardTitle": "__board__", + "act-withBoardTitle": "__tauler__", "act-withCardTitle": "[__board__] __card__", "actions": "Accions", "activities": "Activitats", "activity": "Activitat", "activity-added": "ha afegit %s a %s", - "activity-archived": "%s moved to Archive", + "activity-archived": "%s mogut al Arxiu", "activity-attached": "ha adjuntat %s a %s", "activity-created": "ha creat %s", - "activity-customfield-created": "created custom field %s", + "activity-customfield-created": "camp personalitzat creat %s", "activity-excluded": "ha exclòs %s de %s", "activity-imported": "importat %s dins %s des de %s", "activity-imported-board": "importat %s des de %s", @@ -60,9 +60,9 @@ "add-board": "Afegeix Tauler", "add-card": "Afegeix fitxa", "add-swimlane": "Afegix Carril de Natació", - "add-subtask": "Add Subtask", + "add-subtask": "Afegir Subtasca", "add-checklist": "Afegeix checklist", - "add-checklist-item": "Afegeix un ítem", + "add-checklist-item": "Afegeix un ítem al checklist", "add-cover": "Afegeix coberta", "add-label": "Afegeix etiqueta", "add-list": "Afegeix llista", @@ -79,18 +79,18 @@ "and-n-other-card_plural": "And __count__ other cards", "apply": "Aplica", "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", + "archive": "Moure al arxiu", + "archive-all": "Moure tot al arxiu", + "archive-board": "Moure Tauler al Arxiu", + "archive-card": "Moure Fitxa al Arxiu", + "archive-list": "Moure Llista al Arxiu", "archive-swimlane": "Move Swimlane to Archive", - "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", + "archive-selection": "Moure selecció al Arxiu", + "archiveBoardPopup-title": "Moure el Tauler al Arxiu?", "archived-items": "Desa", - "archived-boards": "Boards in Archive", + "archived-boards": "Taulers al Arxiu", "restore-board": "Restaura Tauler", - "no-archived-boards": "No Boards in Archive.", + "no-archived-boards": "No hi han Taulers al Arxiu.", "archives": "Desa", "assign-member": "Assignar membre", "attached": "adjuntat", @@ -113,12 +113,12 @@ "boardMenuPopup-title": "Menú del tauler", "boards": "Taulers", "board-view": "Visió del tauler", - "board-view-cal": "Calendar", + "board-view-cal": "Calendari", "board-view-swimlanes": "Carrils de Natació", "board-view-lists": "Llistes", "bucket-example": "Igual que “Bucket List”, per exemple", "cancel": "Cancel·la", - "card-archived": "This card is moved to Archive.", + "card-archived": "Aquesta fitxa ha estat moguda al Arxiu.", "board-archived": "This board is moved to Archive.", "card-comments-title": "Aquesta fitxa té %s comentaris.", "card-delete-notice": "L'esborrat és permanent. Perdreu totes les accions associades a aquesta fitxa.", @@ -128,7 +128,7 @@ "card-due-on": "Finalitza a", "card-spent": "Temps Dedicat", "card-edit-attachments": "Edita arxius adjunts", - "card-edit-custom-fields": "Edit custom fields", + "card-edit-custom-fields": "Editar camps personalitzats", "card-edit-labels": "Edita etiquetes", "card-edit-members": "Edita membres", "card-labels-title": "Canvia les etiquetes de la fitxa", @@ -136,8 +136,8 @@ "card-start": "Comença", "card-start-on": "Comença a", "cardAttachmentsPopup-title": "Adjunta des de", - "cardCustomField-datePopup-title": "Change date", - "cardCustomFieldsPopup-title": "Edit custom fields", + "cardCustomField-datePopup-title": "Canviar data", + "cardCustomFieldsPopup-title": "Editar camps personalitzats", "cardDeletePopup-title": "Esborrar fitxa?", "cardDetailsActionsPopup-title": "Accions de fitxes", "cardLabelsPopup-title": "Etiquetes", @@ -146,7 +146,7 @@ "cards": "Fitxes", "cards-count": "Fitxes", "casSignIn": "Sign In with CAS", - "cardType-card": "Card", + "cardType-card": "Fitxa", "cardType-linkedCard": "Linked Card", "cardType-linkedBoard": "Linked Board", "change": "Canvia", @@ -159,7 +159,7 @@ "changePasswordPopup-title": "Canvia la contrasenya", "changePermissionsPopup-title": "Canvia permisos", "changeSettingsPopup-title": "Canvia configuració", - "subtasks": "Subtasks", + "subtasks": "Subtasca", "checklists": "Checklists", "click-to-star": "Fes clic per destacar aquest tauler.", "click-to-unstar": "Fes clic per deixar de destacar aquest tauler.", @@ -181,14 +181,14 @@ "comment-placeholder": "Escriu un comentari", "comment-only": "Només comentaris", "comment-only-desc": "Només pots fer comentaris a les fitxes", - "no-comments": "No comments", + "no-comments": "Sense comentaris", "no-comments-desc": "Can not see comments and activities.", "computer": "Ordinador", - "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", + "confirm-subtask-delete-dialog": "Esteu segur que voleu eliminar la subtasca?", "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", "copy-card-link-to-clipboard": "Copia l'enllaç de la ftixa al porta-retalls", "linkCardPopup-title": "Link Card", - "searchCardPopup-title": "Search Card", + "searchCardPopup-title": "Buscar Fitxa", "copyCardPopup-title": "Copia la fitxa", "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", "copyChecklistToManyCardsPopup-instructions": "Títols de fitxa i Descripcions de destí en aquest format JSON", @@ -197,20 +197,20 @@ "createBoardPopup-title": "Crea tauler", "chooseBoardSourcePopup-title": "Importa Tauler", "createLabelPopup-title": "Crea etiqueta", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", + "createCustomField": "Crear camp", + "createCustomFieldPopup-title": "Crear camp", "current": "Actual", "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", "custom-field-checkbox": "Checkbox", "custom-field-date": "Data", "custom-field-dropdown": "Dropdown List", "custom-field-dropdown-none": "(none)", - "custom-field-dropdown-options": "List Options", + "custom-field-dropdown-options": "Llista d'opcions", "custom-field-dropdown-options-placeholder": "Press enter to add more options", "custom-field-dropdown-unknown": "(unknown)", "custom-field-number": "Number", "custom-field-text": "Text", - "custom-fields": "Custom Fields", + "custom-fields": "Camps Personalitzats", "date": "Data", "decline": "Declina", "default-avatar": "Avatar per defecte", @@ -230,7 +230,7 @@ "soft-wip-limit": "Soft WIP Limit", "editCardStartDatePopup-title": "Canvia data d'inici", "editCardDueDatePopup-title": "Canvia data de finalització", - "editCustomFieldPopup-title": "Edit Field", + "editCustomFieldPopup-title": "Modificar camp", "editCardSpentTimePopup-title": "Canvia temps dedicat", "editLabelPopup-title": "Canvia etiqueta", "editNotificationPopup-title": "Edita la notificació", @@ -500,8 +500,8 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", - "assigned-by": "Assigned By", - "requested-by": "Requested By", + "assigned-by": "Assignat Per", + "requested-by": "Demanat Per", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", @@ -570,7 +570,7 @@ "r-top-of": "Top of", "r-bottom-of": "Bottom of", "r-its-list": "its list", - "r-archive": "Move to Archive", + "r-archive": "Moure al arxiu", "r-unarchive": "Restore from Archive", "r-card": "card", "r-add": "Afegeix", From 889aa6d65208db2f3f364cc37a29be9325fa186a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 9 Nov 2018 16:00:21 +0100 Subject: [PATCH 02/29] Revert "models: boards: add PUT members entry point" This reverts commit f61942e5cb672d3e0fd4df6c5ff9b3f15f7cb778. Adding a member is actually already handled by POST', '/api/boards/:boardId/members/:userId/add' So this function is purely duplicated. Not to mention that the '/add' one allows to set permissions so this one in this commit is less interesting. --- models/boards.js | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/models/boards.js b/models/boards.js index 57f3a1f1f..db3b11496 100644 --- a/models/boards.js +++ b/models/boards.js @@ -278,10 +278,6 @@ Boards.helpers({ return Users.find({ _id: { $in: _.pluck(this.members, 'userId') } }); }, - getMember(id) { - return _.findWhere(this.members, { userId: id }); - }, - getLabel(name, color) { return _.findWhere(this.labels, { name, color }); }, @@ -847,34 +843,6 @@ if (Meteor.isServer) { } }); - JsonRoutes.add('PUT', '/api/boards/:boardId/members', function (req, res) { - Authentication.checkUserId(req.userId); - try { - const boardId = req.params.boardId; - const board = Boards.findOne({ _id: boardId }); - const userId = req.body.userId; - const user = Users.findOne({ _id: userId }); - - if (!board.getMember(userId)) { - user.addInvite(boardId); - board.addMember(userId); - JsonRoutes.sendResult(res, { - code: 200, - data: id, - }); - } else { - JsonRoutes.sendResult(res, { - code: 200, - }); - } - } - catch (error) { - JsonRoutes.sendResult(res, { - data: error, - }); - } - }); - JsonRoutes.add('POST', '/api/boards', function (req, res) { try { Authentication.checkUserId(req.userId); From 49d3eb5a3f21fad1eb2952eb3da2f93c5c5d6272 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 17 Jul 2018 10:14:26 +0200 Subject: [PATCH 03/29] Add OpenAPI description of the REST API The API is generated by a custom script that parses the models directory. Once the API is generated, tools like https://editor.swagger.io/ or Python bravado can parse the file and generate a language friendly API. Note that the tool generate an OpenAPI 2.0 version because bravado doesn't handle OpenAPI 3.0. The script also parses the JSDoc with a custom parser to allow customization of the description of the fields. --- openapi/README.md | 27 ++ openapi/generate_openapi.py | 911 ++++++++++++++++++++++++++++++++++++ 2 files changed, 938 insertions(+) create mode 100644 openapi/README.md create mode 100644 openapi/generate_openapi.py diff --git a/openapi/README.md b/openapi/README.md new file mode 100644 index 000000000..c353ffd49 --- /dev/null +++ b/openapi/README.md @@ -0,0 +1,27 @@ + +# OpenAPI tools and doc generation + +## Open API generation + +This folder contains a script (`generate_openapi.py`) that extracts +the REST API of Wekan and exports it under the OpenAPI 2.0 specification +(Swagger 2.0). + +### dependencies +- python3 +- [esprima-python](https://github.com/Kronuz/esprima-python) + +### calling the tool + + python3 generate_openapi.py --release v1.65 > ../public/wekan_api.yml + +## Generating docs +Now that we have the OpenAPI, it's easy enough to convert the YAML file into some nice Markdown with +[shins](https://github.com/Mermade/shins) and [api2html](https://github.com/tobilg/api2html), +or even [ReDoc](https://github.com/Rebilly/ReDoc): + + api2html -c ../public/wekan-logo-header.png -o api.html ../public/wekan_api.yml + +or + + redoc-cli serve ../public/wekan_api.yml diff --git a/openapi/generate_openapi.py b/openapi/generate_openapi.py new file mode 100644 index 000000000..2f206a2d5 --- /dev/null +++ b/openapi/generate_openapi.py @@ -0,0 +1,911 @@ +#!/bin/env python3 + +import argparse +import esprima +import json +import os +import re +import sys + + +def get_req_body_elems(obj, elems): + if obj.type == 'FunctionExpression': + get_req_body_elems(obj.body, elems) + elif obj.type == 'BlockStatement': + for s in obj.body: + get_req_body_elems(s, elems) + elif obj.type == 'TryStatement': + get_req_body_elems(obj.block, elems) + elif obj.type == 'ExpressionStatement': + get_req_body_elems(obj.expression, elems) + elif obj.type == 'MemberExpression': + left = get_req_body_elems(obj.object, elems) + right = obj.property.name + if left == 'req.body' and right not in elems: + elems.append(right) + return f'{left}.{right}' + elif obj.type == 'VariableDeclaration': + for s in obj.declarations: + get_req_body_elems(s, elems) + elif obj.type == 'VariableDeclarator': + if obj.id.type == "ObjectPattern": + # get_req_body_elems() can't be called directly here: + # const {isAdmin, isNoComments, isCommentOnly} = req.body; + right = get_req_body_elems(obj.init, elems) + if right == 'req.body': + for p in obj.id.properties: + name = p.key.name + if name not in elems: + elems.append(name) + else: + get_req_body_elems(obj.init, elems) + elif obj.type == 'Property': + get_req_body_elems(obj.value, elems) + elif obj.type == 'ObjectExpression': + for s in obj.properties: + get_req_body_elems(s, elems) + elif obj.type == 'CallExpression': + for s in obj.arguments: + get_req_body_elems(s, elems) + elif obj.type == 'ArrayExpression': + for s in obj.elements: + get_req_body_elems(s, elems) + elif obj.type == 'IfStatement': + get_req_body_elems(obj.test, elems) + if obj.consequent is not None: + get_req_body_elems(obj.consequent, elems) + if obj.alternate is not None: + get_req_body_elems(obj.alternate, elems) + elif obj.type in ('LogicalExpression', 'BinaryExpression', 'AssignmentExpression'): + get_req_body_elems(obj.left, elems) + get_req_body_elems(obj.right, elems) + elif obj.type in ('ReturnStatement', 'UnaryExpression'): + get_req_body_elems(obj.argument, elems) + elif obj.type == 'Literal': + pass + elif obj.type == 'Identifier': + return obj.name + elif obj.type == 'FunctionDeclaration': + pass + else: + print(obj) + return '' + + +def cleanup_jsdocs(jsdoc): + # remove leading spaces before the first '*' + doc = [s.lstrip() for s in jsdoc.value.split('\n')] + + # remove leading stars + doc = [s.lstrip('*') for s in doc] + + # remove leading empty lines + while len(doc) and not doc[0].strip(): + doc.pop(0) + + # remove terminating empty lines + while len(doc) and not doc[-1].strip(): + doc.pop(-1) + + return doc + + +class JS2jsonDecoder(json.JSONDecoder): + def decode(self, s): + result = super().decode(s) # result = super(Decoder, self).decode(s) for Python 2.x + return self._decode(result) + + def _decode(self, o): + if isinstance(o, str) or isinstance(o, unicode): + try: + return int(o) + except ValueError: + return o + elif isinstance(o, dict): + return {k: self._decode(v) for k, v in o.items()} + elif isinstance(o, list): + return [self._decode(v) for v in o] + else: + return o + + +def load_return_type_jsdoc_json(data): + regex_replace = [(r'\n', r' '), # replace new lines by spaces + (r'([\{\s,])(\w+)(:)', r'\1"\2"\3'), # insert double quotes in keys + (r'(:)\s*([^:\},\]]+)\s*([\},\]])', r'\1"\2"\3'), # insert double quotes in values + (r'(\[)\s*([^{].+)\s*(\])', r'\1"\2"\3'), # insert double quotes in array items + (r'^\s*([^\[{].+)\s*', r'"\1"')] # insert double quotes in single item + for r, s in regex_replace: + data = re.sub(r, s, data) + return json.loads(data) + + +class EntryPoint(object): + def __init__(self, schema, statements): + self.schema = schema + self.method, self._path, self.body = statements + self._jsdoc = None + self._doc = {} + self._raw_doc = None + self.path = self.compute_path() + self.method_name = self.method.value.lower() + self.body_params = [] + if self.method_name in ('post', 'put'): + get_req_body_elems(self.body, self.body_params) + + # replace the :parameter in path by {parameter} + self.url = re.sub(r':([^/]*)Id', r'{\1}', self.path) + self.url = re.sub(r':([^/]*)', r'{\1}', self.url) + + # reduce the api name + # get_boards_board_cards() should be get_board_cards() + tokens = self.url.split('/') + reduced_function_name = [] + for i, token in enumerate(tokens): + if token in ('api'): + continue + if (i < len(tokens) - 1 and # not the last item + tokens[i + 1].startswith('{')): # and the next token is a parameter + continue + reduced_function_name.append(token.strip('{}')) + self.reduced_function_name = '_'.join(reduced_function_name) + + # mark the schema as used + schema.used = True + + def compute_path(self): + return self._path.value.rstrip('/') + + def error(self, message): + if self._raw_doc is None: + sys.stderr.write(f'in {self.schema.name},\n') + sys.stderr.write(f'{message}\n') + return + sys.stderr.write(f'in {self.schema.name}, lines {self._raw_doc.loc.start.line}-{self._raw_doc.loc.end.line}\n') + sys.stderr.write(f'{self._raw_doc.value}\n') + sys.stderr.write(f'{message}\n') + + @property + def doc(self): + return self._doc + + @doc.setter + def doc(self, doc): + '''Parse the JSDoc attached to an entry point. + `jsdoc` will not get these right as they are not attached to a method. + So instead, we do our custom parsing here (yes, subject to errors). + + The expected format is the following (empty lines between entries + are ignored): + /** + * @operation name_of_entry_point + * @tag: a_tag_to_add + * @tag: an_other_tag_to_add + * @summary A nice summary, better in one line. + * + * @description This is a quite long description. + * We can use *mardown* as the final rendering is done + * by slate. + * + * indentation doesn't matter. + * + * @param param_0 description of param 0 + * @param {string} param_1 we can also put the type of the parameter + * before its name, like in JSDoc + * @param {boolean} [param_2] we can also tell if the parameter is + * optional by adding square brackets around its name + * + * @return Documents a return value + */ + + Notes: + - name_of_entry_point will be referenced in the ToC of the generated + document. This is also the operationId used in the resulting openapi + file. It needs to be uniq in the namesapce (the current schema.js + file) + - tags are appended to the current Schema attached to the file + ''' + + self._raw_doc = doc + + self._jsdoc = cleanup_jsdocs(doc) + + def store_tag(tag, data): + # check that there is something to store first + if not data.strip(): + return + + # remove terminating whitespaces and empty lines + data = data.rstrip() + + # parameters are handled specially + if tag == 'param': + if 'params' not in self._doc: + self._doc['params'] = {} + params = self._doc['params'] + + param_type = None + try: + name, desc = data.split(maxsplit=1) + except ValueError: + desc = '' + + if name.startswith('{'): + param_type = name.strip('{}') + if param_type not in ['string', 'number', 'boolean', 'integer', 'array', 'file']: + self.error(f'Warning, unknown type {param_type}\n allowed values: string, number, boolean, integer, array, file') + try: + name, desc = desc.split(maxsplit=1) + except ValueError: + desc = '' + + optional = name.startswith('[') and name.endswith(']') + + if optional: + name = name[1:-1] + + # we should not have 2 identical parameter names + if tag in params: + self.error(f'Warning, overwriting parameter {name}') + + params[name] = (param_type, optional, desc) + + if name.endswith('Id'): + # we strip out the 'Id' from the form parameters, we need + # to keep the actual description around + name = name[:-2] + if name not in params: + params[name] = (param_type, optional, desc) + + return + + # 'tag' can be set several times + if tag == 'tag': + if tag not in self._doc: + self._doc[tag] = [] + self._doc[tag].append(data) + + return + + # 'return' tag is json + if tag == 'return_type': + try: + data = load_return_type_jsdoc_json(data) + except json.decoder.JSONDecodeError: + pass + + # we should not have 2 identical tags but @param or @tag + if tag in self._doc: + self.error(f'Warning, overwriting tag {tag}') + + self._doc[tag] = data + + # reset the current doc fields + self._doc = {} + + # first item is supposed to be the description + current_tag = 'description' + current_data = '' + + for line in self._jsdoc: + if line.lstrip().startswith('@'): + tag, data = line.lstrip().split(maxsplit=1) + + if tag in ['@operation', '@summary', '@description', '@param', '@return_type', '@tag']: + # store the current data + store_tag(current_tag, current_data) + + current_tag = tag.lstrip('@') + current_data = '' + line = data + else: + self.error(f'Unknown tag {tag}, ignoring') + + current_data += line + '\n' + + store_tag(current_tag, current_data) + + @property + def summary(self): + if 'summary' in self._doc: + # new lines are not allowed + return self._doc['summary'].replace('\n', ' ') + + return None + + def doc_param(self, name): + if 'params' in self._doc and name in self._doc['params']: + return self._doc['params'][name] + return None, None, None + + def print_openapi_param(self, name, indent): + ptype, poptional, pdesc = self.doc_param(name) + if pdesc is not None: + print(f'{" " * indent}description: |') + print(f'{" " * (indent + 2)}{pdesc}') + else: + print(f'{" " * indent}description: the {name} value') + if ptype is not None: + print(f'{" " * indent}type: {ptype}') + else: + print(f'{" " * indent}type: string') + if poptional: + print(f'{" " * indent}required: false') + else: + print(f'{" " * indent}required: true') + + @property + def operationId(self): + if 'operation' in self._doc: + return self._doc['operation'] + return f'{self.method_name}_{self.reduced_function_name}' + + @property + def description(self): + if 'description' in self._doc: + return self._doc['description'] + return None + + @property + def returns(self): + if 'return_type' in self._doc: + return self._doc['return_type'] + return None + + @property + def tags(self): + tags = [] + if self.schema.fields is not None: + tags.append(self.schema.name) + if 'tag' in self._doc: + tags.extend(self._doc['tag']) + return tags + + def print_openapi_return(self, obj, indent): + if isinstance(obj, dict): + print(f'{" " * indent}type: object') + print(f'{" " * indent}properties:') + for k, v in obj.items(): + print(f'{" " * (indent + 2)}{k}:') + self.print_openapi_return(v, indent + 4) + + elif isinstance(obj, list): + if len(obj) > 1: + self.error('Error while parsing @return tag, an array should have only one type') + print(f'{" " * indent}type: array') + print(f'{" " * indent}items:') + self.print_openapi_return(obj[0], indent + 2) + + elif isinstance(obj, str) or isinstance(obj, unicode): + rtype = 'type: ' + obj + if obj == self.schema.name: + rtype = f'$ref: "#/definitions/{obj}"' + print(f'{" " * indent}{rtype}') + + def print_openapi(self): + parameters = [token[1:-2] if token.endswith('Id') else token[1:] + for token in self.path.split('/') + if token.startswith(':')] + + print(f' {self.method_name}:') + + print(f' operationId: {self.operationId}') + + if self.summary is not None: + print(f' summary: {self.summary}') + + if self.description is not None: + print(f' description: |') + for line in self.description.split('\n'): + if line.strip(): + print(f' {line}') + else: + print('') + + if len(self.tags) > 0: + print(f' tags:') + for tag in self.tags: + print(f' - {tag}') + + # export the parameters + if self.method_name in ('post', 'put'): + print(''' consumes: + - multipart/form-data + - application/json''') + if len(parameters) > 0 or self.method_name in ('post', 'put'): + print(' parameters:') + if self.method_name in ('post', 'put'): + for f in self.body_params: + print(f''' - name: {f} + in: formData''') + self.print_openapi_param(f, 10) + for p in parameters: + if p in self.body_params: + self.error(' '.join((p, self.path, self.method_name))) + print(f''' - name: {p} + in: path''') + self.print_openapi_param(p, 10) + print(''' produces: + - application/json + security: + - UserSecurity: [] + responses: + '200': + description: |- + 200 response''') + if self.returns is not None: + print(' schema:') + self.print_openapi_return(self.returns, 12) + + +class SchemaProperty(object): + def __init__(self, statement, schema): + self.schema = schema + self.statement = statement + self.name = statement.key.name or statement.key.value + self.type = 'object' + self.blackbox = False + self.required = True + for p in statement.value.properties: + if p.key.name == 'type': + if p.value.type == 'Identifier': + self.type = p.value.name.lower() + elif p.value.type == 'ArrayExpression': + self.type = 'array' + self.elements = [e.name.lower() for e in p.value.elements] + + elif p.key.name == 'allowedValues': + self.type = 'enum' + self.enum = [e.value.lower() for e in p.value.elements] + + elif p.key.name == 'blackbox': + self.blackbox = True + + elif p.key.name == 'optional' and p.value.value: + self.required = False + + self._doc = None + self._raw_doc = None + + @property + def doc(self): + return self._doc + + @doc.setter + def doc(self, jsdoc): + self._raw_doc = jsdoc + self._doc = cleanup_jsdocs(jsdoc) + + def process_jsdocs(self, jsdocs): + start = self.statement.key.loc.start.line + for index, doc in enumerate(jsdocs): + if start + 1 == doc.loc.start.line: + self.doc = doc + jsdocs.pop(index) + return + + def __repr__(self): + return f'SchemaProperty({self.name}{"*" if self.required else ""}, {self.doc})' + + def print_openapi(self, indent, current_schema, required_properties): + schema_name = self.schema.name + name = self.name + + # deal with subschemas + if '.' in name: + if name.endswith('$'): + # reference in reference + subschema = ''.join([n.capitalize() for n in self.name.split('.')[:-1]]) + subschema = self.schema.name + subschema + if current_schema != subschema: + if required_properties is not None and required_properties: + print(' required:') + for f in required_properties: + print(f' - {f}') + required_properties.clear() + + print(f''' {subschema}: + type: object''') + return current_schema + + subschema = name.split('.')[0] + schema_name = self.schema.name + subschema.capitalize() + name = name.split('.')[-1] + + if current_schema != schema_name: + if required_properties is not None and required_properties: + print(' required:') + for f in required_properties: + print(f' - {f}') + required_properties.clear() + + print(f''' {schema_name}: + type: object + properties:''') + + if required_properties is not None and self.required: + required_properties.append(name) + + print(f'{" "*indent}{name}:') + + if self.doc is not None: + print(f'{" "*indent} description: |') + for line in self.doc: + if line.strip(): + print(f'{" "*indent} {line}') + else: + print('') + + ptype = self.type + if ptype in ('enum', 'date'): + ptype = 'string' + if ptype != 'object': + print(f'{" "*indent} type: {ptype}') + + if self.type == 'array': + print(f'{" "*indent} items:') + for elem in self.elements: + if elem == 'object': + print(f'{" "*indent} $ref: "#/definitions/{schema_name + name.capitalize()}"') + else: + print(f'{" "*indent} type: {elem}') + if not self.required: + print(f'{" "*indent} x-nullable: true') + + elif self.type == 'object': + if self.blackbox: + print(f'{" "*indent} type: object') + else: + print(f'{" "*indent} $ref: "#/definitions/{schema_name + name.capitalize()}"') + + elif self.type == 'enum': + print(f'{" "*indent} enum:') + for enum in self.enum: + print(f'{" "*indent} - {enum}') + + if '.' not in self.name and not self.required: + print(f'{" "*indent} x-nullable: true') + + return schema_name + + +class Schemas(object): + def __init__(self, data=None, jsdocs=None, name=None): + self.name = name + self._data = data + self.fields = None + self.used = False + + if data is not None: + if self.name is None: + self.name = data.expression.callee.object.name + + content = data.expression.arguments[0].arguments[0] + self.fields = [SchemaProperty(p, self) for p in content.properties] + + self._doc = None + self._raw_doc = None + + if jsdocs is not None: + self.process_jsdocs(jsdocs) + + @property + def doc(self): + if self._doc is None: + return None + return ' '.join(self._doc) + + @doc.setter + def doc(self, jsdoc): + self._raw_doc = jsdoc + self._doc = cleanup_jsdocs(jsdoc) + + def process_jsdocs(self, jsdocs): + start = self._data.loc.start.line + end = self._data.loc.end.line + + for doc in jsdocs: + if doc.loc.end.line + 1 == start: + self.doc = doc + + docs = [doc + for doc in jsdocs + if doc.loc.start.line >= start and doc.loc.end.line <= end] + + for field in self.fields: + field.process_jsdocs(docs) + + def print_openapi(self): + # empty schemas are skipped + if self.fields is None: + return + + print(f' {self.name}:') + print(' type: object') + if self.doc is not None: + print(f' description: {self.doc}') + + print(' properties:') + + # first print out the object itself + properties = [field for field in self.fields if '.' not in field.name] + for prop in properties: + prop.print_openapi(6, None, None) + + required_properties = [f.name for f in properties if f.required] + if required_properties: + print(' required:') + for f in required_properties: + print(f' - {f}') + + # then print the references + current = None + required_properties = [] + properties = [f for f in self.fields if '.' in f.name and not f.name.endswith('$')] + for prop in properties: + current = prop.print_openapi(6, current, required_properties) + + if required_properties: + print(' required:') + for f in required_properties: + print(f' - {f}') + + required_properties = [] + # then print the references in the references + for prop in [f for f in self.fields if '.' in f.name and f.name.endswith('$')]: + current = prop.print_openapi(6, current, required_properties) + + if required_properties: + print(' required:') + for f in required_properties: + print(f' - {f}') + + +def parse_schemas(schemas_dir): + + schemas = {} + entry_points = [] + + for root, dirs, files in os.walk(schemas_dir): + files.sort() + for filename in files: + path = os.path.join(root, filename) + with open(path) as f: + data = ''.join(f.readlines()) + try: + # if the file failed, it's likely it doesn't contain a schema + program = esprima.parseScript(data, options={'comment': True, 'loc': True}) + except: + continue + + current_schema = None + jsdocs = [c for c in program.comments + if c.type == 'Block' and c.value.startswith('*\n')] + + for statement in program.body: + + # find the '.attachSchema(new SimpleSchema()' + # those are the schemas + if (statement.type == 'ExpressionStatement' and + statement.expression.callee is not None and + statement.expression.callee.property is not None and + statement.expression.callee.property.name == 'attachSchema' and + statement.expression.arguments[0].type == 'NewExpression' and + statement.expression.arguments[0].callee.name == 'SimpleSchema'): + + schema = Schemas(statement, jsdocs) + current_schema = schema.name + schemas[current_schema] = schema + + # find all the 'if (Meteor.isServer) { JsonRoutes.add(' + # those are the entry points of the API + elif (statement.type == 'IfStatement' and + statement.test.type == 'MemberExpression' and + statement.test.object.name == 'Meteor' and + statement.test.property.name == 'isServer'): + data = [s.expression.arguments + for s in statement.consequent.body + if (s.type == 'ExpressionStatement' and + s.expression.type == 'CallExpression' and + s.expression.callee.object.name == 'JsonRoutes')] + + # we found at least one entry point, keep them + if len(data) > 0: + if current_schema is None: + current_schema = filename + schemas[current_schema] = Schemas(name=current_schema) + + schema_entry_points = [EntryPoint(schemas[current_schema], d) + for d in data] + entry_points.extend(schema_entry_points) + + # try to match JSDoc to the operations + for entry_point in schema_entry_points: + operation = entry_point.method # POST/GET/PUT/DELETE + jsdoc = [j for j in jsdocs + if j.loc.end.line + 1 == operation.loc.start.line] + if bool(jsdoc): + entry_point.doc = jsdoc[0] + + return schemas, entry_points + + +def generate_openapi(schemas, entry_points, version): + print(f'''swagger: '2.0' +info: + title: Wekan REST API + version: {version} + description: | + The REST API allows you to control and extend Wekan with ease. + + If you are an end-user and not a dev or a tester, [create an issue](https://github.com/wekan/wekan/issues/new) to request new APIs. + + > All API calls in the documentation are made using `curl`. However, you are free to use Java / Python / PHP / Golang / Ruby / Swift / Objective-C / Rust / Scala / C# or any other programming languages. + + # Production Security Concerns + When calling a production Wekan server, ensure it is running via HTTPS and has a valid SSL Certificate. The login method requires you to post your username and password in plaintext, which is why we highly suggest only calling the REST login api over HTTPS. Also, few things to note: + + * Only call via HTTPS + * Implement a timed authorization token expiration strategy + * Ensure the calling user only has permissions for what they are calling and no more + +schemes: + - http + +securityDefinitions: + UserSecurity: + type: apiKey + in: header + name: Authorization + +paths: + /users/login: + post: + operationId: login + summary: Login with REST API + consumes: + - application/x-www-form-urlencoded + - application/json + tags: + - Login + parameters: + - name: username + in: formData + required: true + description: | + Your username + type: string + - name: password + in: formData + required: true + description: | + Your password + type: string + format: password + responses: + 200: + description: |- + Successful authentication + schema: + items: + properties: + id: + type: string + token: + type: string + tokenExpires: + type: string + 400: + description: | + Error in authentication + schema: + items: + properties: + error: + type: number + reason: + type: string + default: + description: | + Error in authentication + /users/register: + post: + operationId: register + summary: Register with REST API + description: | + Notes: + - You will need to provide the token for any of the authenticated methods. + consumes: + - application/x-www-form-urlencoded + - application/json + tags: + - Login + parameters: + - name: username + in: formData + required: true + description: | + Your username + type: string + - name: password + in: formData + required: true + description: | + Your password + type: string + format: password + - name: email + in: formData + required: true + description: | + Your email + type: string + responses: + 200: + description: |- + Successful registration + schema: + items: + properties: + id: + type: string + token: + type: string + tokenExpires: + type: string + 400: + description: | + Error in registration + schema: + items: + properties: + error: + type: number + reason: + type: string + default: + description: | + Error in registration +''') + + # GET and POST on the same path are valid, we need to reshuffle the paths + # with the path as the sorting key + methods = {} + for ep in entry_points: + if ep.path not in methods: + methods[ep.path] = [] + methods[ep.path].append(ep) + + sorted_paths = list(methods.keys()) + sorted_paths.sort() + + for path in sorted_paths: + print(f' {methods[path][0].url}:') + + for ep in methods[path]: + ep.print_openapi() + + print('definitions:') + for schema in schemas.values(): + # do not export the objects if there is no API attached + if not schema.used: + continue + + schema.print_openapi() + + +def main(): + parser = argparse.ArgumentParser(description='Generate an OpenAPI 2.0 from the given JS schemas.') + script_dir = os.path.dirname(os.path.realpath(__file__)) + parser.add_argument('--release', default=f'git-master', nargs=1, + help='the current version of the API, can be retrieved by running `git describe --tags --abbrev=0`') + parser.add_argument('dir', default=f'{script_dir}/../models', nargs='?', + help='the directory where to look for schemas') + + args = parser.parse_args() + schemas, entry_points = parse_schemas(args.dir) + generate_openapi(schemas, entry_points, args.release[0]) + + +if __name__ == '__main__': + main() From ff467402c0c24981078f1f8e2b92b26b0d67d00a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 26 Oct 2018 07:27:24 +0200 Subject: [PATCH 04/29] RESTAPI: Add some JSDoc So we can have a decent REST API documentation generated. --- models/boards.js | 176 +++++++++++++++++++++++++++++++++++++++ models/cardComments.js | 56 +++++++++++++ models/cards.js | 169 +++++++++++++++++++++++++++++++++++++ models/checklistItems.js | 55 ++++++++++++ models/checklists.js | 63 ++++++++++++++ models/customFields.js | 73 ++++++++++++++++ models/export.js | 15 +++- models/integrations.js | 99 ++++++++++++++++++++-- models/lists.js | 68 +++++++++++++++ models/swimlanes.js | 59 +++++++++++++ models/users.js | 172 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 994 insertions(+), 11 deletions(-) diff --git a/models/boards.js b/models/boards.js index db3b11496..99480ca70 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1,10 +1,19 @@ Boards = new Mongo.Collection('boards'); +/** + * This is a Board. + */ Boards.attachSchema(new SimpleSchema({ title: { + /** + * The title of the board + */ type: String, }, slug: { + /** + * The title slugified. + */ type: String, autoValue() { // eslint-disable-line consistent-return // XXX We need to improve slug management. Only the id should be necessary @@ -24,6 +33,9 @@ Boards.attachSchema(new SimpleSchema({ }, }, archived: { + /** + * Is the board archived? + */ type: Boolean, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -32,6 +44,9 @@ Boards.attachSchema(new SimpleSchema({ }, }, createdAt: { + /** + * Creation time of the board + */ type: Date, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -43,6 +58,9 @@ Boards.attachSchema(new SimpleSchema({ }, // XXX Inconsistent field naming modifiedAt: { + /** + * Last modification time of the board + */ type: Date, optional: true, autoValue() { // eslint-disable-line consistent-return @@ -55,6 +73,9 @@ Boards.attachSchema(new SimpleSchema({ }, // De-normalized number of users that have starred this board stars: { + /** + * How many stars the board has + */ type: Number, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -64,6 +85,9 @@ Boards.attachSchema(new SimpleSchema({ }, // De-normalized label system 'labels': { + /** + * List of labels attached to a board + */ type: [Object], autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -78,6 +102,9 @@ Boards.attachSchema(new SimpleSchema({ }, }, 'labels.$._id': { + /** + * Unique id of a label + */ // We don't specify that this field must be unique in the board because that // will cause performance penalties and is not necessary since this field is // always set on the server. @@ -86,10 +113,22 @@ Boards.attachSchema(new SimpleSchema({ type: String, }, 'labels.$.name': { + /** + * Name of a label + */ type: String, optional: true, }, 'labels.$.color': { + /** + * color of a label. + * + * Can be amongst `green`, `yellow`, `orange`, `red`, `purple`, + * `blue`, `sky`, `lime`, `pink`, `black`, + * `silver`, `peachpuff`, `crimson`, `plum`, `darkgreen`, + * `slateblue`, `magenta`, `gold`, `navy`, `gray`, + * `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo` + */ type: String, allowedValues: [ 'green', 'yellow', 'orange', 'red', 'purple', @@ -103,6 +142,9 @@ Boards.attachSchema(new SimpleSchema({ // documents like de-normalized meta-data (the date the member joined the // board, the number of contributions, etc.). 'members': { + /** + * List of members of a board + */ type: [Object], autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -117,27 +159,48 @@ Boards.attachSchema(new SimpleSchema({ }, }, 'members.$.userId': { + /** + * The uniq ID of the member + */ type: String, }, 'members.$.isAdmin': { + /** + * Is the member an admin of the board? + */ type: Boolean, }, 'members.$.isActive': { + /** + * Is the member active? + */ type: Boolean, }, 'members.$.isNoComments': { + /** + * Is the member not allowed to make comments + */ type: Boolean, optional: true, }, 'members.$.isCommentOnly': { + /** + * Is the member only allowed to comment on the board + */ type: Boolean, optional: true, }, permission: { + /** + * visibility of the board + */ type: String, allowedValues: ['public', 'private'], }, color: { + /** + * The color of the board. + */ type: String, allowedValues: [ 'belize', @@ -154,24 +217,45 @@ Boards.attachSchema(new SimpleSchema({ }, }, description: { + /** + * The description of the board + */ type: String, optional: true, }, subtasksDefaultBoardId: { + /** + * The default board ID assigned to subtasks. + */ type: String, optional: true, defaultValue: null, }, subtasksDefaultListId: { + /** + * The default List ID assigned to subtasks. + */ type: String, optional: true, defaultValue: null, }, allowsSubtasks: { + /** + * Does the board allows subtasks? + */ type: Boolean, defaultValue: true, }, presentParentTask: { + /** + * Controls how to present the parent task: + * + * - `prefix-with-full-path`: add a prefix with the full path + * - `prefix-with-parent`: add a prefisx with the parent name + * - `subtext-with-full-path`: add a subtext with the full path + * - `subtext-with-parent`: add a subtext with the parent name + * - `no-parent`: does not show the parent at all + */ type: String, allowedValues: [ 'prefix-with-full-path', @@ -184,23 +268,38 @@ Boards.attachSchema(new SimpleSchema({ defaultValue: 'no-parent', }, startAt: { + /** + * Starting date of the board. + */ type: Date, optional: true, }, dueAt: { + /** + * Due date of the board. + */ type: Date, optional: true, }, endAt: { + /** + * End date of the board. + */ type: Date, optional: true, }, spentTime: { + /** + * Time spent in the board. + */ type: Number, decimal: true, optional: true, }, isOvertime: { + /** + * Is the board overtimed? + */ type: Boolean, defaultValue: false, optional: true, @@ -774,6 +873,14 @@ if (Meteor.isServer) { //BOARDS REST API if (Meteor.isServer) { + /** + * @operation get_boards_from_user + * @summary Get all boards attached to a user + * + * @param {string} userId the ID of the user to retrieve the data + * @return_type [{_id: string, + title: string}] + */ JsonRoutes.add('GET', '/api/users/:userId/boards', function (req, res) { try { Authentication.checkLoggedIn(req.userId); @@ -804,6 +911,13 @@ if (Meteor.isServer) { } }); + /** + * @operation get_public_boards + * @summary Get all public boards + * + * @return_type [{_id: string, + title: string}] + */ JsonRoutes.add('GET', '/api/boards', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -825,6 +939,13 @@ if (Meteor.isServer) { } }); + /** + * @operation get_board + * @summary Get the board with that particular ID + * + * @param {string} boardId the ID of the board to retrieve the data + * @return_type Boards + */ JsonRoutes.add('GET', '/api/boards/:boardId', function (req, res) { try { const id = req.params.boardId; @@ -843,6 +964,31 @@ if (Meteor.isServer) { } }); + /** + * @operation new_board + * @summary Create a board + * + * @description This allows to create a board. + * + * The color has to be chosen between `belize`, `nephritis`, `pomegranate`, + * `pumpkin`, `wisteria`, `midnight`: + * + * Wekan logo + * + * @param {string} title the new title of the board + * @param {string} owner "ABCDE12345" <= User ID in Wekan. + * (Not username or email) + * @param {boolean} [isAdmin] is the owner an admin of the board (default true) + * @param {boolean} [isActive] is the board active (default true) + * @param {boolean} [isNoComments] disable comments (default false) + * @param {boolean} [isCommentOnly] only enable comments (default false) + * @param {string} [permission] "private" board <== Set to "public" if you + * want public Wekan board + * @param {string} [color] the color of the board + * + * @return_type {_id: string, + defaultSwimlaneId: string} + */ JsonRoutes.add('POST', '/api/boards', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -880,6 +1026,12 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_board + * @summary Delete a board + * + * @param {string} boardId the ID of the board + */ JsonRoutes.add('DELETE', '/api/boards/:boardId', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -900,6 +1052,19 @@ if (Meteor.isServer) { } }); + /** + * @operation add_board_label + * @summary Add a label to a board + * + * @description If the board doesn't have the name/color label, this function + * adds the label to the board. + * + * @param {string} boardId the board + * @param {string} color the color of the new label + * @param {string} name the name of the new label + * + * @return_type string + */ JsonRoutes.add('PUT', '/api/boards/:boardId/labels', function (req, res) { Authentication.checkUserId(req.userId); const id = req.params.boardId; @@ -929,6 +1094,17 @@ if (Meteor.isServer) { } }); + /** + * @operation set_board_member_permission + * @tag Users + * @summary Change the permission of a member of a board + * + * @param {string} boardId the ID of the board that we are changing + * @param {string} memberId the ID of the user to change permissions + * @param {boolean} isAdmin admin capability + * @param {boolean} isNoComments NoComments capability + * @param {boolean} isCommentOnly CommentsOnly capability + */ JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function (req, res) { try { const boardId = req.params.boardId; diff --git a/models/cardComments.js b/models/cardComments.js index b6cb10fa7..974c5ec93 100644 --- a/models/cardComments.js +++ b/models/cardComments.js @@ -1,19 +1,34 @@ CardComments = new Mongo.Collection('card_comments'); +/** + * A comment on a card + */ CardComments.attachSchema(new SimpleSchema({ boardId: { + /** + * the board ID + */ type: String, }, cardId: { + /** + * the card ID + */ type: String, }, // XXX Rename in `content`? `text` is a bit vague... text: { + /** + * the text of the comment + */ type: String, }, // XXX We probably don't need this information here, since we already have it // in the associated comment creation activity createdAt: { + /** + * when was the comment created + */ type: Date, denyUpdate: false, autoValue() { // eslint-disable-line consistent-return @@ -26,6 +41,9 @@ CardComments.attachSchema(new SimpleSchema({ }, // XXX Should probably be called `authorId` userId: { + /** + * the author ID of the comment + */ type: String, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -87,6 +105,16 @@ if (Meteor.isServer) { //CARD COMMENT REST API if (Meteor.isServer) { + /** + * @operation get_all_comments + * @summary Get all comments attached to a card + * + * @param {string} boardId the board ID of the card + * @param {string} cardId the ID of the card + * @return_type [{_id: string, + * comment: string, + * authorId: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', function (req, res) { try { Authentication.checkUserId( req.userId); @@ -111,6 +139,15 @@ if (Meteor.isServer) { } }); + /** + * @operation get_comment + * @summary Get a comment on a card + * + * @param {string} boardId the board ID of the card + * @param {string} cardId the ID of the card + * @param {string} commentId the ID of the comment to retrieve + * @return_type CardComments + */ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments/:commentId', function (req, res) { try { Authentication.checkUserId( req.userId); @@ -130,6 +167,16 @@ if (Meteor.isServer) { } }); + /** + * @operation new_comment + * @summary Add a comment on a card + * + * @param {string} boardId the board ID of the card + * @param {string} cardId the ID of the card + * @param {string} authorId the user who 'posted' the comment + * @param {string} text the content of the comment + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/cards/:cardId/comments', function (req, res) { try { Authentication.checkUserId( req.userId); @@ -160,6 +207,15 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_comment + * @summary Delete a comment on a card + * + * @param {string} boardId the board ID of the card + * @param {string} cardId the ID of the card + * @param {string} commentId the ID of the comment to delete + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/comments/:commentId', function (req, res) { try { Authentication.checkUserId( req.userId); diff --git a/models/cards.js b/models/cards.js index 7b05e4b5e..aa0bf93e7 100644 --- a/models/cards.js +++ b/models/cards.js @@ -5,11 +5,17 @@ Cards = new Mongo.Collection('cards'); // of comments just to display the number of them in the board view. Cards.attachSchema(new SimpleSchema({ title: { + /** + * the title of the card + */ type: String, optional: true, defaultValue: '', }, archived: { + /** + * is the card archived + */ type: Boolean, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -18,33 +24,51 @@ Cards.attachSchema(new SimpleSchema({ }, }, parentId: { + /** + * ID of the parent card + */ type: String, optional: true, defaultValue: '', }, listId: { + /** + * List ID where the card is + */ type: String, optional: true, defaultValue: '', }, swimlaneId: { + /** + * Swimlane ID where the card is + */ type: String, }, // The system could work without this `boardId` information (we could deduce // the board identifier from the card), but it would make the system more // difficult to manage and less efficient. boardId: { + /** + * Board ID of the card + */ type: String, optional: true, defaultValue: '', }, coverId: { + /** + * Cover ID of the card + */ type: String, optional: true, defaultValue: '', }, createdAt: { + /** + * creation date + */ type: Date, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -55,6 +79,9 @@ Cards.attachSchema(new SimpleSchema({ }, }, customFields: { + /** + * list of custom fields + */ type: [Object], optional: true, defaultValue: [], @@ -62,11 +89,17 @@ Cards.attachSchema(new SimpleSchema({ 'customFields.$': { type: new SimpleSchema({ _id: { + /** + * the ID of the related custom field + */ type: String, optional: true, defaultValue: '', }, value: { + /** + * value attached to the custom field + */ type: Match.OneOf(String, Number, Boolean, Date), optional: true, defaultValue: '', @@ -74,59 +107,95 @@ Cards.attachSchema(new SimpleSchema({ }), }, dateLastActivity: { + /** + * Date of last activity + */ type: Date, autoValue() { return new Date(); }, }, description: { + /** + * description of the card + */ type: String, optional: true, defaultValue: '', }, requestedBy: { + /** + * who requested the card (ID of the user) + */ type: String, optional: true, defaultValue: '', }, assignedBy: { + /** + * who assigned the card (ID of the user) + */ type: String, optional: true, defaultValue: '', }, labelIds: { + /** + * list of labels ID the card has + */ type: [String], optional: true, defaultValue: [], }, members: { + /** + * list of members (user IDs) + */ type: [String], optional: true, defaultValue: [], }, receivedAt: { + /** + * Date the card was received + */ type: Date, optional: true, }, startAt: { + /** + * Date the card was started to be worked on + */ type: Date, optional: true, }, dueAt: { + /** + * Date the card is due + */ type: Date, optional: true, }, endAt: { + /** + * Date the card ended + */ type: Date, optional: true, }, spentTime: { + /** + * How much time has been spent on this + */ type: Number, decimal: true, optional: true, defaultValue: 0, }, isOvertime: { + /** + * is the card over time? + */ type: Boolean, defaultValue: false, optional: true, @@ -134,6 +203,9 @@ Cards.attachSchema(new SimpleSchema({ // XXX Should probably be called `authorId`. Is it even needed since we have // the `members` field? userId: { + /** + * user ID of the author of the card + */ type: String, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -142,21 +214,33 @@ Cards.attachSchema(new SimpleSchema({ }, }, sort: { + /** + * Sort value + */ type: Number, decimal: true, defaultValue: '', }, subtaskSort: { + /** + * subtask sort value + */ type: Number, decimal: true, defaultValue: -1, optional: true, }, type: { + /** + * type of the card + */ type: String, defaultValue: '', }, linkedId: { + /** + * ID of the linked card + */ type: String, optional: true, defaultValue: '', @@ -1309,6 +1393,17 @@ if (Meteor.isServer) { } //SWIMLANES REST API if (Meteor.isServer) { + /** + * @operation get_swimlane_cards + * @summary get all cards attached to a swimlane + * + * @param {string} boardId the board ID + * @param {string} swimlaneId the swimlane ID + * @return_type [{_id: string, + * title: string, + * description: string, + * listId: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes/:swimlaneId/cards', function(req, res) { const paramBoardId = req.params.boardId; const paramSwimlaneId = req.params.swimlaneId; @@ -1332,6 +1427,16 @@ if (Meteor.isServer) { } //LISTS REST API if (Meteor.isServer) { + /** + * @operation get_all_cards + * @summary get all cards attached to a list + * + * @param {string} boardId the board ID + * @param {string} listId the list ID + * @return_type [{_id: string, + * title: string, + * description: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; @@ -1352,6 +1457,15 @@ if (Meteor.isServer) { }); }); + /** + * @operation get_card + * @summary get a card + * + * @param {string} boardId the board ID + * @param {string} listId the list ID of the card + * @param {string} cardId the card ID + * @return_type Cards + */ JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; @@ -1368,6 +1482,19 @@ if (Meteor.isServer) { }); }); + /** + * @operation new_card + * @summary creates a new card + * + * @param {string} boardId the board ID of the new card + * @param {string} listId the list ID of the new card + * @param {string} authorID the user ID of the person owning the card + * @param {string} title the title of the new card + * @param {string} description the description of the new card + * @param {string} swimlaneId the swimlane ID of the new card + * @param {string} [members] the member IDs list of the new card + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; @@ -1406,6 +1533,36 @@ if (Meteor.isServer) { } }); + /* + * Note for the JSDoc: + * 'list' will be interpreted as the path parameter + * 'listID' will be interpreted as the body parameter + */ + /** + * @operation edit_card + * @summary edit fields in a card + * + * @param {string} boardId the board ID of the card + * @param {string} list the list ID of the card + * @param {string} cardId the ID of the card + * @param {string} [title] the new title of the card + * @param {string} [listId] the new list ID of the card (move operation) + * @param {string} [description] the new description of the card + * @param {string} [authorId] change the owner of the card + * @param {string} [labelIds] the new list of label IDs attached to the card + * @param {string} [swimlaneId] the new swimlane ID of the card + * @param {string} [members] the new list of member IDs attached to the card + * @param {string} [requestedBy] the new requestedBy field of the card + * @param {string} [assignedBy] the new assignedBy field of the card + * @param {string} [receivedAt] the new receivedAt field of the card + * @param {string} [assignBy] the new assignBy field of the card + * @param {string} [startAt] the new startAt field of the card + * @param {string} [dueAt] the new dueAt field of the card + * @param {string} [endAt] the new endAt field of the card + * @param {string} [spentTime] the new spentTime field of the card + * @param {boolean} [isOverTime] the new isOverTime field of the card + * @param {string} [customFields] the new customFields value of the card + */ JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; @@ -1551,6 +1708,18 @@ if (Meteor.isServer) { }); }); + /** + * @operation delete_card + * @summary Delete a card from a board + * + * @description This operation **deletes** a card, and therefore the card + * is not put in the recycle bin. + * + * @param {string} boardId the board ID of the card + * @param {string} list the list ID of the card + * @param {string} cardId the ID of the card + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; diff --git a/models/checklistItems.js b/models/checklistItems.js index 9867dd945..35b18ed7f 100644 --- a/models/checklistItems.js +++ b/models/checklistItems.js @@ -1,21 +1,39 @@ ChecklistItems = new Mongo.Collection('checklistItems'); +/** + * An item in a checklist + */ ChecklistItems.attachSchema(new SimpleSchema({ title: { + /** + * the text of the item + */ type: String, }, sort: { + /** + * the sorting field of the item + */ type: Number, decimal: true, }, isFinished: { + /** + * Is the item checked? + */ type: Boolean, defaultValue: false, }, checklistId: { + /** + * the checklist ID the item is attached to + */ type: String, }, cardId: { + /** + * the card ID the item is attached to + */ type: String, }, })); @@ -193,6 +211,17 @@ if (Meteor.isServer) { } if (Meteor.isServer) { + /** + * @operation get_checklist_item + * @tag Checklists + * @summary Get a checklist item + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} checklistId the checklist ID + * @param {string} itemId the ID of the item + * @return_type ChecklistItems + */ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) { Authentication.checkUserId( req.userId); const paramItemId = req.params.itemId; @@ -209,6 +238,19 @@ if (Meteor.isServer) { } }); + /** + * @operation edit_checklist_item + * @tag Checklists + * @summary Edit a checklist item + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} checklistId the checklist ID + * @param {string} itemId the ID of the item + * @param {string} [isFinished] is the item checked? + * @param {string} [title] the new text of the item + * @return_type {_id: string} + */ JsonRoutes.add('PUT', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) { Authentication.checkUserId( req.userId); @@ -229,6 +271,19 @@ if (Meteor.isServer) { }); }); + /** + * @operation delete_checklist_item + * @tag Checklists + * @summary Delete a checklist item + * + * @description Note: this operation can't be reverted. + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} checklistId the checklist ID + * @param {string} itemId the ID of the item to be removed + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) { Authentication.checkUserId( req.userId); const paramItemId = req.params.itemId; diff --git a/models/checklists.js b/models/checklists.js index 425a10b2b..a372fafa4 100644 --- a/models/checklists.js +++ b/models/checklists.js @@ -1,18 +1,33 @@ Checklists = new Mongo.Collection('checklists'); +/** + * A Checklist + */ Checklists.attachSchema(new SimpleSchema({ cardId: { + /** + * The ID of the card the checklist is in + */ type: String, }, title: { + /** + * the title of the checklist + */ type: String, defaultValue: 'Checklist', }, finishedAt: { + /** + * When was the checklist finished + */ type: Date, optional: true, }, createdAt: { + /** + * Creation date of the checklist + */ type: Date, denyUpdate: false, autoValue() { // eslint-disable-line consistent-return @@ -24,6 +39,9 @@ Checklists.attachSchema(new SimpleSchema({ }, }, sort: { + /** + * sorting value of the checklist + */ type: Number, decimal: true, }, @@ -128,6 +146,15 @@ if (Meteor.isServer) { } if (Meteor.isServer) { + /** + * @operation get_all_checklists + * @summary Get the list of checklists attached to a card + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @return_type [{_id: string, + * title: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res) { Authentication.checkUserId( req.userId); const paramCardId = req.params.cardId; @@ -149,6 +176,22 @@ if (Meteor.isServer) { } }); + /** + * @operation get_checklist + * @summary Get a checklist + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} checklistId the ID of the checklist + * @return_type {cardId: string, + * title: string, + * finishedAt: string, + * createdAt: string, + * sort: number, + * items: [{_id: string, + * title: string, + * isFinished: boolean}]} + */ JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res) { Authentication.checkUserId( req.userId); const paramChecklistId = req.params.checklistId; @@ -173,6 +216,15 @@ if (Meteor.isServer) { } }); + /** + * @operation new_checklist + * @summary create a new checklist + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} title the title of the new checklist + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res) { Authentication.checkUserId( req.userId); @@ -204,6 +256,17 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_checklist + * @summary Delete a checklist + * + * @description The checklist will be removed, not put in the recycle bin. + * + * @param {string} boardId the board ID + * @param {string} cardId the card ID + * @param {string} checklistId the ID of the checklist to remove + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res) { Authentication.checkUserId( req.userId); const paramChecklistId = req.params.checklistId; diff --git a/models/customFields.js b/models/customFields.js index 5bb5e7439..3e8aa250d 100644 --- a/models/customFields.js +++ b/models/customFields.js @@ -1,40 +1,73 @@ CustomFields = new Mongo.Collection('customFields'); +/** + * A custom field on a card in the board + */ CustomFields.attachSchema(new SimpleSchema({ boardId: { + /** + * the ID of the board + */ type: String, }, name: { + /** + * name of the custom field + */ type: String, }, type: { + /** + * type of the custom field + */ type: String, allowedValues: ['text', 'number', 'date', 'dropdown'], }, settings: { + /** + * settings of the custom field + */ type: Object, }, 'settings.dropdownItems': { + /** + * list of drop down items objects + */ type: [Object], optional: true, }, 'settings.dropdownItems.$': { type: new SimpleSchema({ _id: { + /** + * ID of the drop down item + */ type: String, }, name: { + /** + * name of the drop down item + */ type: String, }, }), }, showOnCard: { + /** + * should we show on the cards this custom field + */ type: Boolean, }, automaticallyOnCard: { + /** + * should the custom fields automatically be added on cards? + */ type: Boolean, }, showLabelOnMiniCard: { + /** + * should the label of the custom field be shown on minicards? + */ type: Boolean, }, })); @@ -88,6 +121,15 @@ if (Meteor.isServer) { //CUSTOM FIELD REST API if (Meteor.isServer) { + /** + * @operation get_all_custom_fields + * @summary Get the list of Custom Fields attached to a board + * + * @param {string} boardID the ID of the board + * @return_type [{_id: string, + * name: string, + * type: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields', function (req, res) { Authentication.checkUserId( req.userId); const paramBoardId = req.params.boardId; @@ -103,6 +145,14 @@ if (Meteor.isServer) { }); }); + /** + * @operation get_custom_field + * @summary Get a Custom Fields attached to a board + * + * @param {string} boardID the ID of the board + * @param {string} customFieldId the ID of the custom field + * @return_type CustomFields + */ JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields/:customFieldId', function (req, res) { Authentication.checkUserId( req.userId); const paramBoardId = req.params.boardId; @@ -113,6 +163,19 @@ if (Meteor.isServer) { }); }); + /** + * @operation new_custom_field + * @summary Create a Custom Field + * + * @param {string} boardID the ID of the board + * @param {string} name the name of the custom field + * @param {string} type the type of the custom field + * @param {string} settings the settings object of the custom field + * @param {boolean} showOnCard should we show the custom field on cards? + * @param {boolean} automaticallyOnCard should the custom fields automatically be added on cards? + * @param {boolean} showLabelOnMiniCard should the label of the custom field be shown on minicards? + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/custom-fields', function (req, res) { Authentication.checkUserId( req.userId); const paramBoardId = req.params.boardId; @@ -137,6 +200,16 @@ if (Meteor.isServer) { }); }); + /** + * @operation delete_custom_field + * @summary Delete a Custom Fields attached to a board + * + * @description The Custom Field can't be retrieved after this operation + * + * @param {string} boardID the ID of the board + * @param {string} customFieldId the ID of the custom field + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/custom-fields/:customFieldId', function (req, res) { Authentication.checkUserId( req.userId); const paramBoardId = req.params.boardId; diff --git a/models/export.js b/models/export.js index 62d2687a8..fa4894d9f 100644 --- a/models/export.js +++ b/models/export.js @@ -6,13 +6,20 @@ if (Meteor.isServer) { // `ApiRoutes.path('boards/export', boardId)`` // on the client instead of copy/pasting the route path manually between the // client and the server. - /* - * This route is used to export the board FROM THE APPLICATION. - * If user is already logged-in, pass loginToken as param "authToken": - * '/api/boards/:boardId/export?authToken=:token' + /** + * @operation export + * @tag Boards + * + * @summary This route is used to export the board **FROM THE APPLICATION**. + * + * @description If user is already logged-in, pass loginToken as param + * "authToken": '/api/boards/:boardId/export?authToken=:token' * * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/ * for detailed explanations + * + * @param {string} boardId the ID of the board we are exporting + * @param {string} authToken the loginToken */ JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) { const boardId = req.params.boardId; diff --git a/models/integrations.js b/models/integrations.js index 1062b93bc..1c473b57e 100644 --- a/models/integrations.js +++ b/models/integrations.js @@ -1,33 +1,60 @@ Integrations = new Mongo.Collection('integrations'); +/** + * Integration with third-party applications + */ Integrations.attachSchema(new SimpleSchema({ enabled: { + /** + * is the integration enabled? + */ type: Boolean, defaultValue: true, }, title: { + /** + * name of the integration + */ type: String, optional: true, }, type: { + /** + * type of the integratation (Default to 'outgoing-webhooks') + */ type: String, defaultValue: 'outgoing-webhooks', }, activities: { + /** + * activities the integration gets triggered (list) + */ type: [String], defaultValue: ['all'], }, url: { // URL validation regex (https://mathiasbynens.be/demo/url-regex) + /** + * URL validation regex (https://mathiasbynens.be/demo/url-regex) + */ type: String, }, token: { + /** + * token of the integration + */ type: String, optional: true, }, boardId: { + /** + * Board ID of the integration + */ type: String, }, createdAt: { + /** + * Creation date of the integration + */ type: Date, denyUpdate: false, autoValue() { // eslint-disable-line consistent-return @@ -39,6 +66,9 @@ Integrations.attachSchema(new SimpleSchema({ }, }, userId: { + /** + * user ID who created the interation + */ type: String, }, })); @@ -58,7 +88,13 @@ Integrations.allow({ //INTEGRATIONS REST API if (Meteor.isServer) { - // Get all integrations in board + /** + * @operation get_all_integrations + * @summary Get all integrations in board + * + * @param {string} boardId the board ID + * @return_type [Integrations] + */ JsonRoutes.add('GET', '/api/boards/:boardId/integrations', function(req, res) { try { const paramBoardId = req.params.boardId; @@ -78,7 +114,14 @@ if (Meteor.isServer) { } }); - // Get a single integration in board + /** + * @operation get_integration + * @summary Get a single integration in board + * + * @param {string} boardId the board ID + * @param {string} intId the integration ID + * @return_type Integrations + */ JsonRoutes.add('GET', '/api/boards/:boardId/integrations/:intId', function(req, res) { try { const paramBoardId = req.params.boardId; @@ -98,7 +141,14 @@ if (Meteor.isServer) { } }); - // Create a new integration + /** + * @operation new_integration + * @summary Create a new integration + * + * @param {string} boardId the board ID + * @param {string} url the URL of the integration + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/integrations', function(req, res) { try { const paramBoardId = req.params.boardId; @@ -125,7 +175,19 @@ if (Meteor.isServer) { } }); - // Edit integration data + /** + * @operation edit_integration + * @summary Edit integration data + * + * @param {string} boardId the board ID + * @param {string} intId the integration ID + * @param {string} [enabled] is the integration enabled? + * @param {string} [title] new name of the integration + * @param {string} [url] new URL of the integration + * @param {string} [token] new token of the integration + * @param {string} [activities] new list of activities of the integration + * @return_type {_id: string} + */ JsonRoutes.add('PUT', '/api/boards/:boardId/integrations/:intId', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -173,7 +235,15 @@ if (Meteor.isServer) { } }); - // Delete subscribed activities + /** + * @operation delete_integration_activities + * @summary Delete subscribed activities + * + * @param {string} boardId the board ID + * @param {string} intId the integration ID + * @param {string} newActivities the activities to remove from the integration + * @return_type Integrations + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId/activities', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -197,7 +267,15 @@ if (Meteor.isServer) { } }); - // Add subscribed activities + /** + * @operation new_integration_activities + * @summary Add subscribed activities + * + * @param {string} boardId the board ID + * @param {string} intId the integration ID + * @param {string} newActivities the activities to add to the integration + * @return_type Integrations + */ JsonRoutes.add('POST', '/api/boards/:boardId/integrations/:intId/activities', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -221,7 +299,14 @@ if (Meteor.isServer) { } }); - // Delete integration + /** + * @operation delete_integration + * @summary Delete integration + * + * @param {string} boardId the board ID + * @param {string} intId the integration ID + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId', function (req, res) { try { const paramBoardId = req.params.boardId; diff --git a/models/lists.js b/models/lists.js index b99fe8f58..0e1ba8013 100644 --- a/models/lists.js +++ b/models/lists.js @@ -1,10 +1,19 @@ Lists = new Mongo.Collection('lists'); +/** + * A list (column) in the Wekan board. + */ Lists.attachSchema(new SimpleSchema({ title: { + /** + * the title of the list + */ type: String, }, archived: { + /** + * is the list archived + */ type: Boolean, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -13,9 +22,15 @@ Lists.attachSchema(new SimpleSchema({ }, }, boardId: { + /** + * the board associated to this list + */ type: String, }, createdAt: { + /** + * creation date + */ type: Date, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -26,12 +41,18 @@ Lists.attachSchema(new SimpleSchema({ }, }, sort: { + /** + * is the list sorted + */ type: Number, decimal: true, // XXX We should probably provide a default optional: true, }, updatedAt: { + /** + * last update of the list + */ type: Date, optional: true, autoValue() { // eslint-disable-line consistent-return @@ -43,19 +64,31 @@ Lists.attachSchema(new SimpleSchema({ }, }, wipLimit: { + /** + * WIP object, see below + */ type: Object, optional: true, }, 'wipLimit.value': { + /** + * value of the WIP + */ type: Number, decimal: false, defaultValue: 1, }, 'wipLimit.enabled': { + /** + * is the WIP enabled + */ type: Boolean, defaultValue: false, }, 'wipLimit.soft': { + /** + * is the WIP a soft or hard requirement + */ type: Boolean, defaultValue: false, }, @@ -212,6 +245,14 @@ if (Meteor.isServer) { //LISTS REST API if (Meteor.isServer) { + /** + * @operation get_all_lists + * @summary Get the list of Lists attached to a board + * + * @param {string} boardId the board ID + * @return_type [{_id: string, + * title: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/lists', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -235,6 +276,14 @@ if (Meteor.isServer) { } }); + /** + * @operation get_list + * @summary Get a List attached to a board + * + * @param {string} boardId the board ID + * @param {string} listId the List ID + * @return_type Lists + */ JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -253,6 +302,14 @@ if (Meteor.isServer) { } }); + /** + * @operation new_list + * @summary Add a List to a board + * + * @param {string} boardId the board ID + * @param {string} title the title of the List + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/lists', function (req, res) { try { Authentication.checkUserId( req.userId); @@ -276,6 +333,17 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_list + * @summary Delete a List + * + * @description This **deletes** a list from a board. + * The list is not put in the recycle bin. + * + * @param {string} boardId the board ID + * @param {string} listId the ID of the list to remove + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId', function (req, res) { try { Authentication.checkUserId( req.userId); diff --git a/models/swimlanes.js b/models/swimlanes.js index 3559bcd2a..fa5245da0 100644 --- a/models/swimlanes.js +++ b/models/swimlanes.js @@ -1,10 +1,19 @@ Swimlanes = new Mongo.Collection('swimlanes'); +/** + * A swimlane is an line in the kaban board. + */ Swimlanes.attachSchema(new SimpleSchema({ title: { + /** + * the title of the swimlane + */ type: String, }, archived: { + /** + * is the swimlane archived? + */ type: Boolean, autoValue() { // eslint-disable-line consistent-return if (this.isInsert && !this.isSet) { @@ -13,9 +22,15 @@ Swimlanes.attachSchema(new SimpleSchema({ }, }, boardId: { + /** + * the ID of the board the swimlane is attached to + */ type: String, }, createdAt: { + /** + * creation date of the swimlane + */ type: Date, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -26,12 +41,18 @@ Swimlanes.attachSchema(new SimpleSchema({ }, }, sort: { + /** + * the sort value of the swimlane + */ type: Number, decimal: true, // XXX We should probably provide a default optional: true, }, updatedAt: { + /** + * when was the swimlane last edited + */ type: Date, optional: true, autoValue() { // eslint-disable-line consistent-return @@ -131,6 +152,15 @@ if (Meteor.isServer) { //SWIMLANE REST API if (Meteor.isServer) { + /** + * @operation get_all_swimlanes + * + * @summary Get the list of swimlanes attached to a board + * + * @param {string} boardId the ID of the board + * @return_type [{_id: string, + * title: string}] + */ JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -154,6 +184,15 @@ if (Meteor.isServer) { } }); + /** + * @operation get_swimlane + * + * @summary Get a swimlane + * + * @param {string} boardId the ID of the board + * @param {string} swimlaneId the ID of the swimlane + * @return_type Swimlanes + */ JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes/:swimlaneId', function (req, res) { try { const paramBoardId = req.params.boardId; @@ -172,6 +211,15 @@ if (Meteor.isServer) { } }); + /** + * @operation new_swimlane + * + * @summary Add a swimlane to a board + * + * @param {string} boardId the ID of the board + * @param {string} title the new title of the swimlane + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/swimlanes', function (req, res) { try { Authentication.checkUserId( req.userId); @@ -195,6 +243,17 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_swimlane + * + * @summary Delete a swimlane + * + * @description The swimlane will be deleted, not moved to the recycle bin + * + * @param {string} boardId the ID of the board + * @param {string} swimlaneId the ID of the swimlane + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/boards/:boardId/swimlanes/:swimlaneId', function (req, res) { try { Authentication.checkUserId( req.userId); diff --git a/models/users.js b/models/users.js index d4c678b7a..566438488 100644 --- a/models/users.js +++ b/models/users.js @@ -4,8 +4,14 @@ const isSandstorm = Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm; Users = Meteor.users; +/** + * A User in wekan + */ Users.attachSchema(new SimpleSchema({ username: { + /** + * the username of the user + */ type: String, optional: true, autoValue() { // eslint-disable-line consistent-return @@ -18,17 +24,29 @@ Users.attachSchema(new SimpleSchema({ }, }, emails: { + /** + * the list of emails attached to a user + */ type: [Object], optional: true, }, 'emails.$.address': { + /** + * The email address + */ type: String, regEx: SimpleSchema.RegEx.Email, }, 'emails.$.verified': { + /** + * Has the email been verified + */ type: Boolean, }, createdAt: { + /** + * creation date of the user + */ type: Date, autoValue() { // eslint-disable-line consistent-return if (this.isInsert) { @@ -39,6 +57,9 @@ Users.attachSchema(new SimpleSchema({ }, }, profile: { + /** + * profile settings + */ type: Object, optional: true, autoValue() { // eslint-disable-line consistent-return @@ -50,50 +71,86 @@ Users.attachSchema(new SimpleSchema({ }, }, 'profile.avatarUrl': { + /** + * URL of the avatar of the user + */ type: String, optional: true, }, 'profile.emailBuffer': { + /** + * list of email buffers of the user + */ type: [String], optional: true, }, 'profile.fullname': { + /** + * full name of the user + */ type: String, optional: true, }, 'profile.hiddenSystemMessages': { + /** + * does the user wants to hide system messages? + */ type: Boolean, optional: true, }, 'profile.initials': { + /** + * initials of the user + */ type: String, optional: true, }, 'profile.invitedBoards': { + /** + * board IDs the user has been invited to + */ type: [String], optional: true, }, 'profile.language': { + /** + * language of the user + */ type: String, optional: true, }, 'profile.notifications': { + /** + * enabled notifications for the user + */ type: [String], optional: true, }, 'profile.showCardsCountAt': { + /** + * showCardCountAt field of the user + */ type: Number, optional: true, }, 'profile.starredBoards': { + /** + * list of starred board IDs + */ type: [String], optional: true, }, 'profile.icode': { + /** + * icode + */ type: String, optional: true, }, 'profile.boardView': { + /** + * boardView field of the user + */ type: String, optional: true, allowedValues: [ @@ -103,27 +160,45 @@ Users.attachSchema(new SimpleSchema({ ], }, services: { + /** + * services field of the user + */ type: Object, optional: true, blackbox: true, }, heartbeat: { + /** + * last time the user has been seen + */ type: Date, optional: true, }, isAdmin: { + /** + * is the user an admin of the board? + */ type: Boolean, optional: true, }, createdThroughApi: { + /** + * was the user created through the API? + */ type: Boolean, optional: true, }, loginDisabled: { + /** + * loginDisabled field of the user + */ type: Boolean, optional: true, }, 'authenticationMethod': { + /** + * authentication method of the user + */ type: String, optional: false, defaultValue: 'password', @@ -681,6 +756,12 @@ if (Meteor.isServer) { } }); + /** + * @operation get_current_user + * + * @summary returns the current user + * @return_type Users + */ JsonRoutes.add('GET', '/api/user', function(req, res) { try { Authentication.checkLoggedIn(req.userId); @@ -699,6 +780,15 @@ if (Meteor.isServer) { } }); + /** + * @operation get_all_users + * + * @summary return all the users + * + * @description Only the admin user (the first user) can call the REST API. + * @return_type [{ _id: string, + * username: string}] + */ JsonRoutes.add('GET', '/api/users', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -717,6 +807,16 @@ if (Meteor.isServer) { } }); + /** + * @operation get_user + * + * @summary get a given user + * + * @description Only the admin user (the first user) can call the REST API. + * + * @param {string} userId the user ID + * @return_type Users + */ JsonRoutes.add('GET', '/api/users/:userId', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -734,6 +834,23 @@ if (Meteor.isServer) { } }); + /** + * @operation edit_user + * + * @summary edit a given user + * + * @description Only the admin user (the first user) can call the REST API. + * + * Possible values for *action*: + * - `takeOwnership`: The admin takes the ownership of ALL boards of the user (archived and not archived) where the user is admin on. + * - `disableLogin`: Disable a user (the user is not allowed to login and his login tokens are purged) + * - `enableLogin`: Enable a user + * + * @param {string} userId the user ID + * @param {string} action the action + * @return_type {_id: string, + * title: string} + */ JsonRoutes.add('PUT', '/api/users/:userId', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -777,6 +894,25 @@ if (Meteor.isServer) { } }); + /** + * @operation add_board_member + * @tag Boards + * + * @summary Add New Board Member with Role + * + * @description Only the admin user (the first user) can call the REST API. + * + * **Note**: see [Boards.set_board_member_permission](#set_board_member_permission) + * to later change the permissions. + * + * @param {string} boardId the board ID + * @param {string} userId the user ID + * @param {boolean} isAdmin is the user an admin of the board + * @param {boolean} isNoComments disable comments + * @param {boolean} isCommentOnly only enable comments + * @return_type {_id: string, + * title: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/members/:userId/add', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -817,6 +953,20 @@ if (Meteor.isServer) { } }); + /** + * @operation remove_board_member + * @tag Boards + * + * @summary Remove Member from Board + * + * @description Only the admin user (the first user) can call the REST API. + * + * @param {string} boardId the board ID + * @param {string} userId the user ID + * @param {string} action the action (needs to be `remove`) + * @return_type {_id: string, + * title: string} + */ JsonRoutes.add('POST', '/api/boards/:boardId/members/:userId/remove', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -852,6 +1002,18 @@ if (Meteor.isServer) { } }); + /** + * @operation new_user + * + * @summary Create a new user + * + * @description Only the admin user (the first user) can call the REST API. + * + * @param {string} username the new username + * @param {string} email the email of the new user + * @param {string} password the password of the new user + * @return_type {_id: string} + */ JsonRoutes.add('POST', '/api/users/', function (req, res) { try { Authentication.checkUserId(req.userId); @@ -876,6 +1038,16 @@ if (Meteor.isServer) { } }); + /** + * @operation delete_user + * + * @summary Delete a user + * + * @description Only the admin user (the first user) can call the REST API. + * + * @param {string} userId the ID of the user to delete + * @return_type {_id: string} + */ JsonRoutes.add('DELETE', '/api/users/:userId', function (req, res) { try { Authentication.checkUserId(req.userId); From acc44935179cda665454d450ec246377f41f5afb Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 7 Nov 2018 18:49:21 +0100 Subject: [PATCH 05/29] Generate the OpenAPI in the Dockerfile When we build the docker container, we need to generate the openapi description in it so the geenrated API actually matches the code the container is running. --- Dockerfile | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0a7479b43..7be32b608 100644 --- a/Dockerfile +++ b/Dockerfile @@ -75,7 +75,7 @@ ARG DEFAULT_AUTHENTICATION_METHOD # Set the environment variables (defaults where required) # DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303 # ENV BUILD_DEPS="paxctl" -ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential python git ca-certificates gcc-7" \ +ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential python python3 python3-distutils git ca-certificates gcc-7" \ NODE_VERSION=v8.15.0 \ METEOR_RELEASE=1.6.0.1 \ USE_EDGE=false \ @@ -251,6 +251,16 @@ RUN \ cd /home/wekan/.meteor && \ gosu wekan:wekan /home/wekan/.meteor/meteor -- help; \ \ + # extract the OpenAPI specification + mkdir -p /home/wekan/python && \ + chown wekan:wekan --recursive /home/wekan/python && \ + cd /home/wekan/python && \ + gosu wekan:wekan git clone --depth 1 -b master git://github.com/Kronuz/esprima-python && \ + cd /home/wekan/python/esprima-python && \ + python3 setup.py install --record files.txt && \ + cd /home/wekan/app &&\ + mkdir -p ./public/api && \ + python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml; \ # Build app cd /home/wekan/app && \ gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js && \ @@ -279,6 +289,8 @@ RUN \ rm -R /home/wekan/.meteor && \ rm -R /home/wekan/app && \ rm -R /home/wekan/app_build && \ + cat /home/wekan/python/esprima-python/files.txt | xargs rm -R && \ + rm -R /home/wekan/python && \ rm /home/wekan/install_meteor.sh ENV PORT=8080 From 8be7eec2caea9daf7fd89d9fb3b715d3bcda3ba4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 17 Jan 2019 16:30:57 +0100 Subject: [PATCH 06/29] openapi: make the code python 3.5 compatible It is common to use Ubuntu 16.04 to build snaps. For example, the official docker container to build snaps is using this old distribution. However, Ubuntu 16.04 ships Python 3.5.X which is not compatible with the f-strings in generate_openapi.py. This is sad, because we need to use the `.format()` syntax to make it compatible. --- openapi/generate_openapi.py | 140 ++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 68 deletions(-) diff --git a/openapi/generate_openapi.py b/openapi/generate_openapi.py index 2f206a2d5..2a898f0e0 100644 --- a/openapi/generate_openapi.py +++ b/openapi/generate_openapi.py @@ -23,12 +23,12 @@ def get_req_body_elems(obj, elems): right = obj.property.name if left == 'req.body' and right not in elems: elems.append(right) - return f'{left}.{right}' + return '{}.{}'.format(left, right) elif obj.type == 'VariableDeclaration': for s in obj.declarations: get_req_body_elems(s, elems) elif obj.type == 'VariableDeclarator': - if obj.id.type == "ObjectPattern": + if obj.id.type == 'ObjectPattern': # get_req_body_elems() can't be called directly here: # const {isAdmin, isNoComments, isCommentOnly} = req.body; right = get_req_body_elems(obj.init, elems) @@ -158,12 +158,14 @@ class EntryPoint(object): def error(self, message): if self._raw_doc is None: - sys.stderr.write(f'in {self.schema.name},\n') - sys.stderr.write(f'{message}\n') + sys.stderr.write('in {},\n'.format(self.schema.name)) + sys.stderr.write('{}\n'.format(message)) return - sys.stderr.write(f'in {self.schema.name}, lines {self._raw_doc.loc.start.line}-{self._raw_doc.loc.end.line}\n') - sys.stderr.write(f'{self._raw_doc.value}\n') - sys.stderr.write(f'{message}\n') + sys.stderr.write('in {}, lines {}-{}\n'.format(self.schema.name, + self._raw_doc.loc.start.line, + self._raw_doc.loc.end.line)) + sys.stderr.write('{}\n'.format(self._raw_doc.value)) + sys.stderr.write('{}\n'.format(message)) @property def doc(self): @@ -233,7 +235,7 @@ class EntryPoint(object): if name.startswith('{'): param_type = name.strip('{}') if param_type not in ['string', 'number', 'boolean', 'integer', 'array', 'file']: - self.error(f'Warning, unknown type {param_type}\n allowed values: string, number, boolean, integer, array, file') + self.error('Warning, unknown type {}\n allowed values: string, number, boolean, integer, array, file'.format(param_type)) try: name, desc = desc.split(maxsplit=1) except ValueError: @@ -246,7 +248,7 @@ class EntryPoint(object): # we should not have 2 identical parameter names if tag in params: - self.error(f'Warning, overwriting parameter {name}') + self.error('Warning, overwriting parameter {}'.format(name)) params[name] = (param_type, optional, desc) @@ -276,7 +278,7 @@ class EntryPoint(object): # we should not have 2 identical tags but @param or @tag if tag in self._doc: - self.error(f'Warning, overwriting tag {tag}') + self.error('Warning, overwriting tag {}'.format(tag)) self._doc[tag] = data @@ -299,7 +301,7 @@ class EntryPoint(object): current_data = '' line = data else: - self.error(f'Unknown tag {tag}, ignoring') + self.error('Unknown tag {}, ignoring'.format(tag)) current_data += line + '\n' @@ -321,24 +323,24 @@ class EntryPoint(object): def print_openapi_param(self, name, indent): ptype, poptional, pdesc = self.doc_param(name) if pdesc is not None: - print(f'{" " * indent}description: |') - print(f'{" " * (indent + 2)}{pdesc}') + print('{}description: |'.format(' ' * indent)) + print('{}{}'.format(' ' * (indent + 2), pdesc)) else: - print(f'{" " * indent}description: the {name} value') + print('{}description: the {} value'.format(' ' * indent, name)) if ptype is not None: - print(f'{" " * indent}type: {ptype}') + print('{}type: {}'.format(' ' * indent, ptype)) else: - print(f'{" " * indent}type: string') + print('{}type: string'.format(' ' * indent)) if poptional: - print(f'{" " * indent}required: false') + print('{}required: false'.format(' ' * indent)) else: - print(f'{" " * indent}required: true') + print('{}required: true'.format(' ' * indent)) @property def operationId(self): if 'operation' in self._doc: return self._doc['operation'] - return f'{self.method_name}_{self.reduced_function_name}' + return '{}_{}'.format(self.method_name, self.reduced_function_name) @property def description(self): @@ -363,49 +365,49 @@ class EntryPoint(object): def print_openapi_return(self, obj, indent): if isinstance(obj, dict): - print(f'{" " * indent}type: object') - print(f'{" " * indent}properties:') + print('{}type: object'.format(' ' * indent)) + print('{}properties:'.format(' ' * indent)) for k, v in obj.items(): - print(f'{" " * (indent + 2)}{k}:') + print('{}{}:'.format(' ' * (indent + 2), k)) self.print_openapi_return(v, indent + 4) elif isinstance(obj, list): if len(obj) > 1: self.error('Error while parsing @return tag, an array should have only one type') - print(f'{" " * indent}type: array') - print(f'{" " * indent}items:') + print('{}type: array'.format(' ' * indent)) + print('{}items:'.format(' ' * indent)) self.print_openapi_return(obj[0], indent + 2) elif isinstance(obj, str) or isinstance(obj, unicode): rtype = 'type: ' + obj if obj == self.schema.name: - rtype = f'$ref: "#/definitions/{obj}"' - print(f'{" " * indent}{rtype}') + rtype = '$ref: "#/definitions/{}"'.format(obj) + print('{}{}'.format(' ' * indent, rtype)) def print_openapi(self): parameters = [token[1:-2] if token.endswith('Id') else token[1:] for token in self.path.split('/') if token.startswith(':')] - print(f' {self.method_name}:') + print(' {}:'.format(self.method_name)) - print(f' operationId: {self.operationId}') + print(' operationId: {}'.format(self.operationId)) if self.summary is not None: - print(f' summary: {self.summary}') + print(' summary: {}'.format(self.summary)) if self.description is not None: - print(f' description: |') + print(' description: |') for line in self.description.split('\n'): if line.strip(): - print(f' {line}') + print(' {}'.format(line)) else: print('') if len(self.tags) > 0: - print(f' tags:') + print(' tags:') for tag in self.tags: - print(f' - {tag}') + print(' - {}'.format(tag)) # export the parameters if self.method_name in ('post', 'put'): @@ -416,14 +418,14 @@ class EntryPoint(object): print(' parameters:') if self.method_name in ('post', 'put'): for f in self.body_params: - print(f''' - name: {f} - in: formData''') + print(''' - name: {} + in: formData'''.format(f)) self.print_openapi_param(f, 10) for p in parameters: if p in self.body_params: self.error(' '.join((p, self.path, self.method_name))) - print(f''' - name: {p} - in: path''') + print(''' - name: {} + in: path'''.format(p)) self.print_openapi_param(p, 10) print(''' produces: - application/json @@ -485,7 +487,9 @@ class SchemaProperty(object): return def __repr__(self): - return f'SchemaProperty({self.name}{"*" if self.required else ""}, {self.doc})' + return 'SchemaProperty({}{}, {})'.format(self.name, + '*' if self.required else '', + self.doc) def print_openapi(self, indent, current_schema, required_properties): schema_name = self.schema.name @@ -501,11 +505,11 @@ class SchemaProperty(object): if required_properties is not None and required_properties: print(' required:') for f in required_properties: - print(f' - {f}') + print(' - {}'.format(f)) required_properties.clear() - print(f''' {subschema}: - type: object''') + print(''' {}: + type: object'''.format(subschema)) return current_schema subschema = name.split('.')[0] @@ -516,23 +520,23 @@ class SchemaProperty(object): if required_properties is not None and required_properties: print(' required:') for f in required_properties: - print(f' - {f}') + print(' - {}'.format(f)) required_properties.clear() - print(f''' {schema_name}: + print(''' {}: type: object - properties:''') + properties:'''.format(schema_name)) if required_properties is not None and self.required: required_properties.append(name) - print(f'{" "*indent}{name}:') + print('{}{}:'.format(' ' * indent, name)) if self.doc is not None: - print(f'{" "*indent} description: |') + print('{} description: |'.format(' ' * indent)) for line in self.doc: if line.strip(): - print(f'{" "*indent} {line}') + print('{} {}'.format(' ' * indent, line)) else: print('') @@ -540,31 +544,31 @@ class SchemaProperty(object): if ptype in ('enum', 'date'): ptype = 'string' if ptype != 'object': - print(f'{" "*indent} type: {ptype}') + print('{} type: {}'.format(' ' * indent, ptype)) if self.type == 'array': - print(f'{" "*indent} items:') + print('{} items:'.format(' ' * indent)) for elem in self.elements: if elem == 'object': - print(f'{" "*indent} $ref: "#/definitions/{schema_name + name.capitalize()}"') + print('{} $ref: "#/definitions/{}"'.format(' ' * indent, schema_name + name.capitalize())) else: - print(f'{" "*indent} type: {elem}') + print('{} type: {}'.format(' ' * indent, elem)) if not self.required: - print(f'{" "*indent} x-nullable: true') + print('{} x-nullable: true'.format(' ' * indent)) elif self.type == 'object': if self.blackbox: - print(f'{" "*indent} type: object') + print('{} type: object'.format(' ' * indent)) else: - print(f'{" "*indent} $ref: "#/definitions/{schema_name + name.capitalize()}"') + print('{} $ref: "#/definitions/{}"'.format(' ' * indent, schema_name + name.capitalize())) elif self.type == 'enum': - print(f'{" "*indent} enum:') + print('{} enum:'.format(' ' * indent)) for enum in self.enum: - print(f'{" "*indent} - {enum}') + print('{} - {}'.format(' ' * indent, enum)) if '.' not in self.name and not self.required: - print(f'{" "*indent} x-nullable: true') + print('{} x-nullable: true'.format(' ' * indent)) return schema_name @@ -620,10 +624,10 @@ class Schemas(object): if self.fields is None: return - print(f' {self.name}:') + print(' {}:'.format(self.name)) print(' type: object') if self.doc is not None: - print(f' description: {self.doc}') + print(' description: {}'.format(self.doc)) print(' properties:') @@ -636,7 +640,7 @@ class Schemas(object): if required_properties: print(' required:') for f in required_properties: - print(f' - {f}') + print(' - {}'.format(f)) # then print the references current = None @@ -648,7 +652,7 @@ class Schemas(object): if required_properties: print(' required:') for f in required_properties: - print(f' - {f}') + print(' - {}'.format(f)) required_properties = [] # then print the references in the references @@ -658,7 +662,7 @@ class Schemas(object): if required_properties: print(' required:') for f in required_properties: - print(f' - {f}') + print(' - {}'.format(f)) def parse_schemas(schemas_dir): @@ -731,10 +735,10 @@ def parse_schemas(schemas_dir): def generate_openapi(schemas, entry_points, version): - print(f'''swagger: '2.0' + print('''swagger: '2.0' info: title: Wekan REST API - version: {version} + version: {0} description: | The REST API allows you to control and extend Wekan with ease. @@ -866,7 +870,7 @@ paths: default: description: | Error in registration -''') +'''.format(version)) # GET and POST on the same path are valid, we need to reshuffle the paths # with the path as the sorting key @@ -880,7 +884,7 @@ paths: sorted_paths.sort() for path in sorted_paths: - print(f' {methods[path][0].url}:') + print(' {}:'.format(methods[path][0].url)) for ep in methods[path]: ep.print_openapi() @@ -897,9 +901,9 @@ paths: def main(): parser = argparse.ArgumentParser(description='Generate an OpenAPI 2.0 from the given JS schemas.') script_dir = os.path.dirname(os.path.realpath(__file__)) - parser.add_argument('--release', default=f'git-master', nargs=1, + parser.add_argument('--release', default='git-master', nargs=1, help='the current version of the API, can be retrieved by running `git describe --tags --abbrev=0`') - parser.add_argument('dir', default=f'{script_dir}/../models', nargs='?', + parser.add_argument('dir', default='{}/../models'.format(script_dir), nargs='?', help='the directory where to look for schemas') args = parser.parse_args() From e91e3c076d93a2102c344432effbfa135469a9d4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 18 Jan 2019 11:41:32 +0100 Subject: [PATCH 07/29] snapcraft add nodejs and npm as build dependencies When pulling the docker container snapcore/snapcraft to build the snap, those 2 packages are not present by default leading to a failure in the snap creation. Note: it is good to call `apt-get update` before `snapcraft` or the build will fail. --- snapcraft.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/snapcraft.yaml b/snapcraft.yaml index d14d8037e..c8675fe1c 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -94,6 +94,8 @@ parts: - capnproto - curl - execstack + - nodejs + - npm stage-packages: - libfontconfig1 override-build: | From c83cdc933541ed8c9ef882a83affa1264833dd80 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 18 Jan 2019 11:44:53 +0100 Subject: [PATCH 08/29] Add openapi in snaps Same thing than in the Dockerfile, snaps need to embed the current openapi yaml file. --- snapcraft.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/snapcraft.yaml b/snapcraft.yaml index c8675fe1c..cba5bf304 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -90,6 +90,7 @@ parts: - ca-certificates - apt-utils - python + - python3 - g++ - capnproto - curl @@ -101,6 +102,16 @@ parts: override-build: | echo "Cleaning environment first" rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules + # Create the OpenAPI specification + rm -rf .build + mkdir -p .build/python + cd .build/python + git clone --depth 1 -b master git://github.com/Kronuz/esprima-python + cd esprima-python + python3 setup.py install + cd ../../.. + mkdir -p ./public/api + python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml # Node Fibers 100% CPU usage issue: # https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-381453161 # https://github.com/meteor/meteor/issues/9796#issuecomment-381676326 From 08ca353205df9612b1e8c0773fce7e85750dfe3b Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 18 Jan 2019 15:49:03 +0100 Subject: [PATCH 09/29] openapi: generate the HTML documentation too and embed it in the image Aligning with the requirement to run the container without external resources: embed the documentation of the REST API directly in the Docker image. --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7be32b608..240fb0b7e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -252,6 +252,7 @@ RUN \ gosu wekan:wekan /home/wekan/.meteor/meteor -- help; \ \ # extract the OpenAPI specification + npm install -g api2html && \ mkdir -p /home/wekan/python && \ chown wekan:wekan --recursive /home/wekan/python && \ cd /home/wekan/python && \ @@ -260,7 +261,8 @@ RUN \ python3 setup.py install --record files.txt && \ cd /home/wekan/app &&\ mkdir -p ./public/api && \ - python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml; \ + python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml && \ + /opt/nodejs/bin/api2html -c ./public/wekan-logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml; \ # Build app cd /home/wekan/app && \ gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js && \ @@ -285,6 +287,7 @@ RUN \ # Cleanup apt-get remove --purge -y ${BUILD_DEPS} && \ apt-get autoremove -y && \ + npm uninstall -g api2html &&\ rm -R /var/lib/apt/lists/* && \ rm -R /home/wekan/.meteor && \ rm -R /home/wekan/app && \ From 048c3cd14d5f4236898e927576f9d2ad24e0cdb0 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 18 Jan 2019 15:51:16 +0100 Subject: [PATCH 10/29] snap: also generate the html doc of the REST API Same for snap: embed the documentation of the REST API in the snap. --- snapcraft.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/snapcraft.yaml b/snapcraft.yaml index cba5bf304..b2b3dfd27 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -112,6 +112,12 @@ parts: cd ../../.. mkdir -p ./public/api python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml + # we temporary need api2html and mkdirp + npm install -g api2html + npm install -g mkdirp + api2html -c ./public/wekan-logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml + npm uninstall -g mkdirp + npm uninstall -g api2html # Node Fibers 100% CPU usage issue: # https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-381453161 # https://github.com/meteor/meteor/issues/9796#issuecomment-381676326 From f40d1f6bd55362599bf3e85ffeffcc3e82bb04ec Mon Sep 17 00:00:00 2001 From: AJ Rivera Date: Fri, 18 Jan 2019 17:25:48 -0400 Subject: [PATCH 11/29] update license to year 2019 --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 04faa72e9..c2d691582 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2018 The Wekan Team +Copyright (c) 2014-2019 The Wekan Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 68998e062e0ac647a433d5650d86a98d9493c2ba Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Sat, 19 Jan 2019 20:42:41 +0200 Subject: [PATCH 12/29] Update translations. --- i18n/ar.i18n.json | 104 +++++++++++++++++++++++----------------------- i18n/ca.i18n.json | 20 ++++----- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/i18n/ar.i18n.json b/i18n/ar.i18n.json index c2a484bda..6e3b9799e 100644 --- a/i18n/ar.i18n.json +++ b/i18n/ar.i18n.json @@ -1,20 +1,20 @@ { - "accept": "اقبلboard", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "ربط __attachment__ الى __card__", - "act-addSubtask": "added subtask __checklist__ to __card__", - "act-addChecklist": "added checklist __checklist__ to __card__", - "act-addChecklistItem": "added __checklistItem__ to checklist __checklist__ on __card__", - "act-addComment": "علق على __comment__ : __card__", - "act-createBoard": "احدث __board__", - "act-createCard": "اضاف __card__ الى __list__", - "act-createCustomField": "created custom field __customField__", - "act-createList": "اضاف __list__ الى __board__", + "accept": "قبول", + "act-activity-notify": "اشعارات النشاط", + "act-addAttachment": "ربط __المرفق__ الى __بطاقة__", + "act-addSubtask": "تمة اضافة فرع المهمة __ قائمة التدقيق __ الى __ بطاقة", + "act-addChecklist": "تمة اضافة قائمة التدقيق __ قائمة التدقيق __ الى __ بطاقة", + "act-addChecklistItem": "تمة اضافة عنصر قائمة التدقيق __الى قائمة التدقيق __ قائمة التدقيق __ في __ بطاقة", + "act-addComment": "علق على __بطاقة__ : __تعليق__", + "act-createBoard": "احدث __لوحة__", + "act-createCard": "تمة اضافة __بطاقة__ الى __قائمة__", + "act-createCustomField": "احدث حقل مخصص __ حقل مخصص__", + "act-createList": "اضاف __قائمة__ الى __لوحة__", "act-addBoardMember": "اضاف __member__ الى __board__", - "act-archivedBoard": "__board__ moved to Archive", - "act-archivedCard": "__card__ moved to Archive", - "act-archivedList": "__list__ moved to Archive", - "act-archivedSwimlane": "__swimlane__ moved to Archive", + "act-archivedBoard": "__ لوح __ انتقل إلى الأرشيف", + "act-archivedCard": "__ بطاقة __ انتقلت إلى الأرشيف", + "act-archivedList": "__ القائمة __ انتقلت إلى الأرشيف", + "act-archivedSwimlane": "__خط السباحة__انتقل إلى الأرشيف", "act-importBoard": "إستورد __board__", "act-importCard": "إستورد __card__", "act-importList": "إستورد __list__", @@ -23,16 +23,16 @@ "act-removeBoardMember": "أزال __member__ من __board__", "act-restoredCard": "أعاد __card__ إلى __board__", "act-unjoinMember": "أزال __member__ من __card__", - "act-withBoardTitle": "__board__", + "act-withBoardTitle": "__لوح__", "act-withCardTitle": "[__board__] __card__", "actions": "الإجراءات", "activities": "الأنشطة", "activity": "النشاط", "activity-added": "تمت إضافة %s ل %s", - "activity-archived": "%s moved to Archive", + "activity-archived": "%s انتقل الى الارشيف", "activity-attached": "إرفاق %s ل %s", "activity-created": "أنشأ %s", - "activity-customfield-created": "created custom field %s", + "activity-customfield-created": "%s احدت حقل مخصص", "activity-excluded": "استبعاد %s عن %s", "activity-imported": "imported %s into %s from %s", "activity-imported-board": "imported %s from %s", @@ -42,15 +42,15 @@ "activity-removed": "حذف %s إلى %s", "activity-sent": "إرسال %s إلى %s", "activity-unjoined": "غادر %s", - "activity-subtask-added": "added subtask to %s", - "activity-checked-item": "checked %s in checklist %s of %s", - "activity-unchecked-item": "unchecked %s in checklist %s of %s", + "activity-subtask-added": "تم اضافة مهمة فرعية الى %s", + "activity-checked-item": "تحقق %s في قائمة التحقق %s من %s", + "activity-unchecked-item": "ازالة تحقق %s من قائمة التحقق %s من %s", "activity-checklist-added": "أضاف قائمة تحقق إلى %s", - "activity-checklist-removed": "removed a checklist from %s", - "activity-checklist-completed": "completed the checklist %s of %s", - "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", - "activity-checklist-item-added": "added checklist item to '%s' in %s", - "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", + "activity-checklist-removed": "ازالة قائمة التحقق من %s", + "activity-checklist-completed": "تم انجاز قائمة التحقق %s من %s", + "activity-checklist-uncompleted": "لم يتم انجاز قائمة التحقق %s من %s", + "activity-checklist-item-added": "تم اضافة عنصر قائمة التحقق الى '%s' في %s", + "activity-checklist-item-removed": "تم ازالة عنصر قائمة التحقق الى '%s' في %s", "add": "أضف", "activity-checked-item-card": "checked %s in checklist %s", "activity-unchecked-item-card": "unchecked %s in checklist %s", @@ -79,18 +79,18 @@ "and-n-other-card_plural": "And __count__ other بطاقات", "apply": "طبق", "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", - "archive-swimlane": "Move Swimlane to Archive", - "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", + "archive": "نقل الى الارشيف", + "archive-all": "نقل الكل الى الارشيف", + "archive-board": "نقل اللوح الى الارشيف", + "archive-card": "نقل البطاقة الى الارشيف", + "archive-list": "نقل القائمة الى الارشيف", + "archive-swimlane": "نقل خط السباحة الى الارشيف", + "archive-selection": "نقل التحديد إلى الأرشيف", + "archiveBoardPopup-title": "نقل الوح إلى الأرشيف", "archived-items": "أرشيف", - "archived-boards": "Boards in Archive", + "archived-boards": "الالواح في الأرشيف", "restore-board": "استعادة اللوحة", - "no-archived-boards": "No Boards in Archive.", + "no-archived-boards": "لا توجد لوحات في الأرشيف.", "archives": "أرشيف", "assign-member": "تعيين عضو", "attached": "أُرفق)", @@ -112,23 +112,23 @@ "boardChangeWatchPopup-title": "تغيير المتابعة", "boardMenuPopup-title": "قائمة اللوحة", "boards": "لوحات", - "board-view": "Board View", - "board-view-cal": "Calendar", - "board-view-swimlanes": "Swimlanes", + "board-view": "عرض اللوحات", + "board-view-cal": "التقويم", + "board-view-swimlanes": "خطوط السباحة", "board-view-lists": "القائمات", "bucket-example": "مثل « todo list » على سبيل المثال", "cancel": "إلغاء", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", + "card-archived": "البطاقة منقولة الى الارشيف", + "board-archived": "اللوحات منقولة الى الارشيف", "card-comments-title": "%s تعليقات لهذه البطاقة", "card-delete-notice": "هذا حذف أبديّ . سوف تفقد كل الإجراءات المنوطة بهذه البطاقة", "card-delete-pop": "سيتم إزالة جميع الإجراءات من تبعات النشاط، وأنك لن تكون قادرا على إعادة فتح البطاقة. لا يوجد التراجع.", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", + "card-delete-suggest-archive": "يمكنك نقل بطاقة إلى الأرشيف لإزالتها من اللوحة والمحافظة على النشاط.", "card-due": "مستحق", "card-due-on": "مستحق في", - "card-spent": "Spent Time", + "card-spent": "امضى وقتا", "card-edit-attachments": "تعديل المرفقات", - "card-edit-custom-fields": "Edit custom fields", + "card-edit-custom-fields": "تعديل الحقل المعدل", "card-edit-labels": "تعديل العلامات", "card-edit-members": "تعديل الأعضاء", "card-labels-title": "تعديل علامات البطاقة.", @@ -136,8 +136,8 @@ "card-start": "بداية", "card-start-on": "يبدأ في", "cardAttachmentsPopup-title": "إرفاق من", - "cardCustomField-datePopup-title": "Change date", - "cardCustomFieldsPopup-title": "Edit custom fields", + "cardCustomField-datePopup-title": "تغير التاريخ", + "cardCustomFieldsPopup-title": "تعديل الحقل المعدل", "cardDeletePopup-title": "حذف البطاقة ?", "cardDetailsActionsPopup-title": "إجراءات على البطاقة", "cardLabelsPopup-title": "علامات", @@ -145,9 +145,9 @@ "cardMorePopup-title": "المزيد", "cards": "بطاقات", "cards-count": "بطاقات", - "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", + "casSignIn": "تسجيل الدخول مع CAS", + "cardType-card": "بطاقة", + "cardType-linkedCard": "البطاقة المرتبطة", "cardType-linkedBoard": "Linked Board", "change": "Change", "change-avatar": "تعديل الصورة الشخصية", @@ -187,7 +187,7 @@ "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?", "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?", "copy-card-link-to-clipboard": "نسخ رابط البطاقة إلى الحافظة", - "linkCardPopup-title": "Link Card", + "linkCardPopup-title": "ربط البطاقة", "searchCardPopup-title": "Search Card", "copyCardPopup-title": "نسخ البطاقة", "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards", @@ -327,7 +327,7 @@ "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "القائمات", - "swimlanes": "Swimlanes", + "swimlanes": "خطوط السباحة", "log-out": "تسجيل الخروج", "log-in": "تسجيل الدخول", "loginPopup-title": "تسجيل الدخول", @@ -570,7 +570,7 @@ "r-top-of": "Top of", "r-bottom-of": "Bottom of", "r-its-list": "its list", - "r-archive": "Move to Archive", + "r-archive": "نقل الى الارشيف", "r-unarchive": "Restore from Archive", "r-card": "card", "r-add": "أضف", diff --git a/i18n/ca.i18n.json b/i18n/ca.i18n.json index d8d90a715..8d54a5fed 100644 --- a/i18n/ca.i18n.json +++ b/i18n/ca.i18n.json @@ -1,7 +1,7 @@ { "accept": "Accepta", "act-activity-notify": "Notificació d'activitat", - "act-addAttachment": "adjuntat __attachment__ a __card__", + "act-addAttachment": "afegit__adjunt__ a la __fitxa__", "act-addSubtask": "added subtask __checklist__ to __card__", "act-addChecklist": "afegida la checklist _checklist__ a __card__", "act-addChecklistItem": "afegit __checklistItem__ a la checklist __checklist__ on __card__", @@ -58,7 +58,7 @@ "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "add-attachment": "Afegeix adjunt", "add-board": "Afegeix Tauler", - "add-card": "Afegeix fitxa", + "add-card": "Afegeix Fitxa", "add-swimlane": "Afegix Carril de Natació", "add-subtask": "Afegir Subtasca", "add-checklist": "Afegeix checklist", @@ -71,9 +71,9 @@ "addMemberPopup-title": "Membres", "admin": "Administrador", "admin-desc": "Pots veure i editar fitxes, eliminar membres, i canviar la configuració del tauler", - "admin-announcement": "Bàndol", - "admin-announcement-active": "Activar bàndol del Sistema", - "admin-announcement-title": "Bàndol de l'administració", + "admin-announcement": "Alertes", + "admin-announcement-active": "Activar alertes del Sistema", + "admin-announcement-title": "Alertes d'administració", "all-boards": "Tots els taulers", "and-n-other-card": "And __count__ other card", "and-n-other-card_plural": "And __count__ other cards", @@ -279,7 +279,7 @@ "headerBarCreateBoardPopup-title": "Crea tauler", "home": "Inici", "import": "importa", - "link": "Link", + "link": "Enllaç", "import-board": "Importa tauler", "import-board-c": "Importa tauler", "import-board-title-trello": "Importa tauler des de Trello", @@ -383,7 +383,7 @@ "restore": "Restaura", "save": "Desa", "search": "Cerca", - "rules": "Rules", + "rules": "Regles", "search-cards": "Cerca títols de fitxa i descripcions en aquest tauler", "search-example": "Text que cercar?", "select-color": "Selecciona color", @@ -453,7 +453,7 @@ "smtp-tls-description": "Activa suport TLS pel servidor SMTP", "smtp-host": "Servidor SMTP", "smtp-port": "Port SMTP", - "smtp-username": "Nom d'Usuari", + "smtp-username": "Nom d'usuari", "smtp-password": "Contrasenya", "smtp-tls": "Suport TLS", "send-from": "De", @@ -532,12 +532,12 @@ "r-rule": "Rule", "r-add-trigger": "Add trigger", "r-add-action": "Add action", - "r-board-rules": "Board rules", + "r-board-rules": "Regles del tauler", "r-add-rule": "Add rule", "r-view-rule": "View rule", "r-delete-rule": "Delete rule", "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", + "r-no-rules": "No hi han regles", "r-when-a-card": "When a card", "r-is": "is", "r-is-moved": "is moved", From 8e8147b5acdf8639d5f164efc2acfb4efd12ff8a Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Sat, 19 Jan 2019 20:45:43 +0200 Subject: [PATCH 13/29] - Fix License to 2019. https://github.com/wekan/wekan/pull/2112 Thanks to ajRiverav ! --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a972a400d..1f0353a46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Update translations. - Fix typo, changelog year to 2019. Thanks to xorander00 ! +- Fix License to 2019. Thanks to ajRiverav ! # v2.01 2019-01-06 Wekan release From c960a8b909289527e6cd8a94f41f3b57ebf0eba8 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Sat, 19 Jan 2019 21:14:32 +0200 Subject: [PATCH 14/29] - [OpenAPI and generating of REST API Docs](https://github.com/wekan/wekan/pull/1965). Thanks to bentiss. --- CHANGELOG.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f0353a46..88c018ab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,19 @@ # Upcoming Wekan release -- Update translations. -- Fix typo, changelog year to 2019. Thanks to xorander00 ! -- Fix License to 2019. Thanks to ajRiverav ! +This release adds the following new features: + +- [OpenAPI and generating of REST API Docs](https://github.com/wekan/wekan/pull/1965). Thanks to bentiss. + +and adds these updates: + +- Update translations. Thanks to translators. + +and fixes these typos; + +- Fix typo, changelog year to 2019. Thanks to xorander00. +- Fix License to 2019. Thanks to ajRiverav. + +Thanks to above GitHub users for their contributions. # v2.01 2019-01-06 Wekan release From d5d71d70973a3b402691f9131bdf664f3aa88c48 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Sun, 20 Jan 2019 00:53:59 +0200 Subject: [PATCH 15/29] Update upcase/lowercase. --- models/cards.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/models/cards.js b/models/cards.js index aa0bf93e7..a20da5eca 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1429,7 +1429,7 @@ if (Meteor.isServer) { if (Meteor.isServer) { /** * @operation get_all_cards - * @summary get all cards attached to a list + * @summary Get all Cards attached to a List * * @param {string} boardId the board ID * @param {string} listId the list ID @@ -1459,7 +1459,7 @@ if (Meteor.isServer) { /** * @operation get_card - * @summary get a card + * @summary Get a Card * * @param {string} boardId the board ID * @param {string} listId the list ID of the card @@ -1484,7 +1484,7 @@ if (Meteor.isServer) { /** * @operation new_card - * @summary creates a new card + * @summary Create a new Card * * @param {string} boardId the board ID of the new card * @param {string} listId the list ID of the new card @@ -1540,7 +1540,7 @@ if (Meteor.isServer) { */ /** * @operation edit_card - * @summary edit fields in a card + * @summary Edit Fields in a Card * * @param {string} boardId the board ID of the card * @param {string} list the list ID of the card From c87a8b86aed70593bf8a9628df830ca5f03d50d5 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Mon, 21 Jan 2019 16:26:55 +0200 Subject: [PATCH 16/29] - Added missing translation for 'days' Thanks to Chartman123 ! Closes #2114 --- i18n/en.i18n.json | 1 + 1 file changed, 1 insertion(+) diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 5e21f767a..c59f10b35 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", From b0ac10d94a8200a1ad0199a82c3f9d9be7ac9882 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 24 Jul 2018 18:18:40 +0200 Subject: [PATCH 17/29] Add the ability to change the card background Currently the only way to set it is via the REST API --- client/components/cards/cardDetails.jade | 2 +- client/components/cards/cardDetails.js | 1 + client/components/cards/cardDetails.styl | 77 +++++++++++++++++++++ client/components/cards/minicard.jade | 3 +- client/components/cards/minicard.js | 4 ++ client/components/cards/minicard.styl | 83 +++++++++++++++++++++++ models/cards.js | 32 +++++++++ public/card-colors.png | Bin 0 -> 54127 bytes 8 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 public/card-colors.png diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index a6dc3dde7..f83c35263 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -1,6 +1,6 @@ template(name="cardDetails") section.card-details.js-card-details.js-perfect-scrollbar: .card-details-canvas - .card-details-header + .card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}') +inlinedForm(classNames="js-card-details-title") +editCardTitleForm else diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index e17e74670..060ded5ad 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -22,6 +22,7 @@ BlazeComponent.extendComponent({ onCreated() { this.currentBoard = Boards.findOne(Session.get('currentBoard')); this.isLoaded = new ReactiveVar(false); + this.currentColor = new ReactiveVar(this.data().color); const boardBody = this.parentComponent().parentComponent(); //in Miniview parent is Board, not BoardBody. if (boardBody !== null) { diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index 1dc56f584..5a486d849 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -140,3 +140,80 @@ input[type="submit"].attachment-add-link-submit .card-details-menu margin-right: 10px + +card-details-color(background, color...) + background: background !important + if color + color: color //overwrite text for better visibility + +.card-details-green + card-details-color(#3cb500, #ffffff) //White text for better visibility + +.card-details-yellow + card-details-color(#fad900) + +.card-details-orange + card-details-color(#ff9f19) + +.card-details-red + card-details-color(#eb4646, #ffffff) //White text for better visibility + +.card-details-purple + card-details-color(#a632db, #ffffff) //White text for better visibility + +.card-details-blue + card-details-color(#0079bf, #ffffff) //White text for better visibility + +.card-details-pink + card-details-color(#ff78cb) + +.card-details-sky + card-details-color(#00c2e0, #ffffff) //White text for better visibility + +.card-details-black + card-details-color(#4d4d4d, #ffffff) //White text for better visibility + +.card-details-lime + card-details-color(#51e898) + +.card-details-silver + card-details-color(#c0c0c0) + +.card-details-peachpuff + card-details-color(#ffdab9) + +.card-details-crimson + card-details-color(#dc143c, #ffffff) //White text for better visibility + +.card-details-plum + card-details-color(#dda0dd) + +.card-details-darkgreen + card-details-color(#006400, #ffffff) //White text for better visibility + +.card-details-slateblue + card-details-color(#6a5acd, #ffffff) //White text for better visibility + +.card-details-magenta + card-details-color(#ff00ff, #ffffff) //White text for better visibility + +.card-details-gold + card-details-color(#ffd700) + +.card-details-navy + card-details-color(#000080, #ffffff) //White text for better visibility + +.card-details-gray + card-details-color(#808080, #ffffff) //White text for better visibility + +.card-details-saddlebrown + card-details-color(#8b4513, #ffffff) //White text for better visibility + +.card-details-paleturquoise + card-details-color(#afeeee) + +.card-details-mistyrose + card-details-color(#ffe4e1) + +.card-details-indigo + card-details-color(#4b0082, #ffffff) //White text for better visibility diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index 0dfcee449..f47ae0c9a 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -1,7 +1,8 @@ template(name="minicard") .minicard( class="{{#if isLinkedCard}}linked-card{{/if}}" - class="{{#if isLinkedBoard}}linked-board{{/if}}") + class="{{#if isLinkedBoard}}linked-board{{/if}}" + class="minicard-{{colorClass}}") if cover .minicard-cover(style="background-image: url('{{cover.url}}');") if labels diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js index da7f9e01b..e468ec567 100644 --- a/client/components/cards/minicard.js +++ b/client/components/cards/minicard.js @@ -3,6 +3,10 @@ // }); BlazeComponent.extendComponent({ + onCreated() { + this.currentColor = new ReactiveVar(this.data().color); + }, + template() { return 'minicard'; }, diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl index 7ad51161f..e3d1ff20d 100644 --- a/client/components/cards/minicard.styl +++ b/client/components/cards/minicard.styl @@ -202,3 +202,86 @@ border-top-right-radius: 0 z-index: 15 box-shadow: 0 1px 2px rgba(0,0,0,.15) + +minicard-color(background, color...) + background-color: background + if color + color: color //overwrite text for better visibility + &:hover:not(.minicard-composer), + .is-selected &, + .draggable-hover-card & + background: darken(background, 3%) + .draggable-hover-card & + background: darken(background, 7%) + +.minicard-green + minicard-color(#3cb500, #ffffff) //White text for better visibility + +.minicard-yellow + minicard-color(#fad900) + +.minicard-orange + minicard-color(#ff9f19) + +.minicard-red + minicard-color(#eb4646, #ffffff) //White text for better visibility + +.minicard-purple + minicard-color(#a632db, #ffffff) //White text for better visibility + +.minicard-blue + minicard-color(#0079bf, #ffffff) //White text for better visibility + +.minicard-pink + minicard-color(#ff78cb) + +.minicard-sky + minicard-color(#00c2e0, #ffffff) //White text for better visibility + +.minicard-black + minicard-color(#4d4d4d, #ffffff) //White text for better visibility + +.minicard-lime + minicard-color(#51e898) + +.minicard-silver + minicard-color(#c0c0c0) + +.minicard-peachpuff + minicard-color(#ffdab9) + +.minicard-crimson + minicard-color(#dc143c, #ffffff) //White text for better visibility + +.minicard-plum + minicard-color(#dda0dd) + +.minicard-darkgreen + minicard-color(#006400, #ffffff) //White text for better visibility + +.minicard-slateblue + minicard-color(#6a5acd, #ffffff) //White text for better visibility + +.minicard-magenta + minicard-color(#ff00ff, #ffffff) //White text for better visibility + +.minicard-gold + minicard-color(#ffd700) + +.minicard-navy + minicard-color(#000080, #ffffff) //White text for better visibility + +.minicard-gray + minicard-color(#808080, #ffffff) //White text for better visibility + +.minicard-saddlebrown + minicard-color(#8b4513, #ffffff) //White text for better visibility + +.minicard-paleturquoise + minicard-color(#afeeee) + +.minicard-mistyrose + minicard-color(#ffe4e1) + +.minicard-indigo + minicard-color(#4b0082, #ffffff) //White text for better visibility diff --git a/models/cards.js b/models/cards.js index a20da5eca..7251faeb9 100644 --- a/models/cards.js +++ b/models/cards.js @@ -65,6 +65,17 @@ Cards.attachSchema(new SimpleSchema({ defaultValue: '', }, + color: { + type: String, + optional: true, + allowedValues: [ + 'green', 'yellow', 'orange', 'red', 'purple', + 'blue', 'sky', 'lime', 'pink', 'black', + 'silver', 'peachpuff', 'crimson', 'plum', 'darkgreen', + 'slateblue', 'magenta', 'gold', 'navy', 'gray', + 'saddlebrown', 'paleturquoise', 'mistyrose', 'indigo', + ], + }, createdAt: { /** * creation date @@ -435,7 +446,12 @@ Cards.helpers({ definition, }; }); + }, + colorClass() { + if (this.color) + return this.color; + return ''; }, absoluteUrl() { @@ -1542,6 +1558,15 @@ if (Meteor.isServer) { * @operation edit_card * @summary Edit Fields in a Card * + * @description Edit a card + * + * The color has to be chosen between `green`, `yellow`, `orange`, `red`, + * `purple`, `blue`, `sky`, `lime`, `pink`, `black`, `silver`, `peachpuff`, + * `crimson`, `plum`, `darkgreen`, `slateblue`, `magenta`, `gold`, `navy`, + * `gray`, `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo`: + * + * Wekan card colors + * * @param {string} boardId the board ID of the card * @param {string} list the list ID of the card * @param {string} cardId the ID of the card @@ -1562,6 +1587,8 @@ if (Meteor.isServer) { * @param {string} [spentTime] the new spentTime field of the card * @param {boolean} [isOverTime] the new isOverTime field of the card * @param {string} [customFields] the new customFields value of the card + * @param {string} [color] the new color of the card + * @return_type {_id: string} */ JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { Authentication.checkUserId(req.userId); @@ -1616,6 +1643,11 @@ if (Meteor.isServer) { }, }); } + if (req.body.hasOwnProperty('color')) { + const newColor = req.body.color; + Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, + {$set: {color: newColor}}); + } if (req.body.hasOwnProperty('labelIds')) { let newlabelIds = req.body.labelIds; if (_.isString(newlabelIds)) { diff --git a/public/card-colors.png b/public/card-colors.png new file mode 100644 index 0000000000000000000000000000000000000000..91d3a5878e9f9c5b7bbe4fc501447f3bcdd38c58 GIT binary patch literal 54127 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 F{{d(T`Tzg` literal 0 HcmV?d00001 From 3368ebf067779d1f306a2447e2357e34213f0126 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 22 Jan 2019 10:35:09 +0100 Subject: [PATCH 18/29] color: add option in hamburger to change the card color Currently only dropdown, no palette Fixes: #428 --- client/components/cards/cardDetails.jade | 34 +++++++++++++++++++++++- client/components/cards/cardDetails.js | 13 +++++++++ i18n/en.i18n.json | 16 +++++++++++ models/cards.js | 11 ++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index f83c35263..4838d82c2 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -234,6 +234,7 @@ template(name="cardDetailsActionsPopup") li: a.js-due-date {{_ 'editCardDueDatePopup-title'}} li: a.js-end-date {{_ 'editCardEndDatePopup-title'}} li: a.js-spent-time {{_ 'editCardSpentTimePopup-title'}} + li: a.js-set-card-color {{_ 'setCardColor-title'}} hr ul.pop-over-list li: a.js-move-card-to-top {{_ 'moveCardToTop-title'}} @@ -335,7 +336,38 @@ template(name="cardMorePopup") span.date(title=card.createdAt) {{ moment createdAt 'LLL' }} a.js-delete(title="{{_ 'card-delete-notice'}}") {{_ 'delete'}} - +template(name="setCardColorPopup") + p.quiet + span.clearfix + label {{_ "select-color"}} + select.js-select-colors + option(value="white") {{{_'color-white'}}} + option(value="green") {{{_'color-green'}}} + option(value="yellow") {{{_'color-yellow'}}} + option(value="orange") {{{_'color-orange'}}} + option(value="red") {{{_'color-red'}}} + option(value="purple") {{{_'color-purple'}}} + option(value="blue") {{{_'color-blue'}}} + option(value="sky") {{{_'color-sky'}}} + option(value="lime") {{{_'color-lime'}}} + option(value="pink") {{{_'color-pink'}}} + option(value="black") {{{_'color-black'}}} + option(value="silver") {{{_'color-silver'}}} + option(value="peachpuff") {{{_'color-peachpuff'}}} + option(value="crimson") {{{_'color-crimson'}}} + option(value="plum") {{{_'color-plum'}}} + option(value="darkgreen") {{{_'color-darkgreen'}}} + option(value="slateblue") {{{_'color-slateblue'}}} + option(value="magenta") {{{_'color-magenta'}}} + option(value="gold") {{{_'color-gold'}}} + option(value="navy") {{{_'color-navy'}}} + option(value="gray") {{{_'color-gray'}}} + option(value="saddlebrown") {{{_'color-saddlebrown'}}} + option(value="paleturquoise") {{{_'color-paleturquoise'}}} + option(value="mistyrose") {{{_'color-mistyrose'}}} + option(value="indigo") {{{_'color-indigo'}}} + .edit-controls.clearfix + button.primary.confirm.js-submit {{_ 'save'}} template(name="cardDeletePopup") p {{_ "card-delete-pop"}} diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 060ded5ad..7883f1290 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -338,6 +338,7 @@ Template.cardDetailsActionsPopup.events({ 'click .js-move-card': Popup.open('moveCard'), 'click .js-copy-card': Popup.open('copyCard'), 'click .js-copy-checklist-cards': Popup.open('copyChecklistToManyCards'), + 'click .js-set-card-color': Popup.open('setCardColor'), 'click .js-move-card-to-top' (evt) { evt.preventDefault(); const minOrder = _.min(this.list().cards(this.swimlaneId).map((c) => c.sort)); @@ -584,6 +585,18 @@ Template.copyChecklistToManyCardsPopup.events({ }, }); +Template.setCardColorPopup.events({ + 'click .js-submit' () { + // XXX We should *not* get the currentCard from the global state, but + // instead from a “component” state. + const card = Cards.findOne(Session.get('currentCard')); + const colorSelect = $('.js-select-colors')[0]; + newColor = colorSelect.options[colorSelect.selectedIndex].value; + card.setColor(newColor); + Popup.close(); + }, +}); + BlazeComponent.extendComponent({ onCreated() { this.currentCard = this.currentData(); diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index c59f10b35..36153ff73 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", diff --git a/models/cards.js b/models/cards.js index 7251faeb9..c5d9bf052 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1033,6 +1033,17 @@ Cards.mutations({ } }, + setColor(newColor) { + if (newColor === 'white') { + newColor = null; + } + return { + $set: { + color: newColor, + }, + }; + }, + assignMember(memberId) { return { $addToSet: { From f4f0f489ebf9e23dc8150d5a94239484256b0beb Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 22 Jan 2019 10:39:30 +0100 Subject: [PATCH 19/29] add action: set card color --- .../components/rules/actions/cardActions.jade | 39 ++++++++++++++++--- .../components/rules/actions/cardActions.js | 20 ++++++++++ i18n/en.i18n.json | 1 + server/rulesHelper.js | 3 ++ 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/client/components/rules/actions/cardActions.jade b/client/components/rules/actions/cardActions.jade index 74ad9ab55..dd92d8fea 100644 --- a/client/components/rules/actions/cardActions.jade +++ b/client/components/rules/actions/cardActions.jade @@ -35,9 +35,36 @@ template(name="cardActions") div.trigger-button.js-add-removeall-action.js-goto-rules i.fa.fa-plus - - - - - - + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-set-color'}}} + div.trigger-dropdown + select(id="color-action") + option(value="white") {{{_'color-white'}}} + option(value="green") {{{_'color-green'}}} + option(value="yellow") {{{_'color-yellow'}}} + option(value="orange") {{{_'color-orange'}}} + option(value="red") {{{_'color-red'}}} + option(value="purple") {{{_'color-purple'}}} + option(value="blue") {{{_'color-blue'}}} + option(value="sky") {{{_'color-sky'}}} + option(value="lime") {{{_'color-lime'}}} + option(value="pink") {{{_'color-pink'}}} + option(value="black") {{{_'color-black'}}} + option(value="silver") {{{_'color-silver'}}} + option(value="peachpuff") {{{_'color-peachpuff'}}} + option(value="crimson") {{{_'color-crimson'}}} + option(value="plum") {{{_'color-plum'}}} + option(value="darkgreen") {{{_'color-darkgreen'}}} + option(value="slateblue") {{{_'color-slateblue'}}} + option(value="magenta") {{{_'color-magenta'}}} + option(value="gold") {{{_'color-gold'}}} + option(value="navy") {{{_'color-navy'}}} + option(value="gray") {{{_'color-gray'}}} + option(value="saddlebrown") {{{_'color-saddlebrown'}}} + option(value="paleturquoise") {{{_'color-paleturquoise'}}} + option(value="mistyrose") {{{_'color-mistyrose'}}} + option(value="indigo") {{{_'color-indigo'}}} + div.trigger-button.js-set-color-action.js-goto-rules + i.fa.fa-plus diff --git a/client/components/rules/actions/cardActions.js b/client/components/rules/actions/cardActions.js index b04440bd0..b66556b4e 100644 --- a/client/components/rules/actions/cardActions.js +++ b/client/components/rules/actions/cardActions.js @@ -112,6 +112,26 @@ BlazeComponent.extendComponent({ boardId, }); }, + 'click .js-set-color-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const selectedColor = this.find('#color-action').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'setColor', + selectedColor, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + }, }]; }, diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 36153ff73..a4338363c 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -597,6 +597,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/server/rulesHelper.js b/server/rulesHelper.js index ccb5cb3bf..163bd41ee 100644 --- a/server/rulesHelper.js +++ b/server/rulesHelper.js @@ -87,6 +87,9 @@ RulesHelper = { if(action.actionType === 'unarchive'){ card.restore(); } + if(action.actionType === 'setColor'){ + card.setColor(action.selectedColor); + } if(action.actionType === 'addLabel'){ card.addLabel(action.labelId); } From d3b2ae1975a72b3be816538059d9c3dc6684a59d Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 22 Jan 2019 12:12:18 +0200 Subject: [PATCH 20/29] Update translations. --- i18n/ar.i18n.json | 1 + i18n/bg.i18n.json | 1 + i18n/br.i18n.json | 1 + i18n/ca.i18n.json | 25 +++++++++++++------------ i18n/cs.i18n.json | 1 + i18n/da.i18n.json | 1 + i18n/de.i18n.json | 1 + i18n/el.i18n.json | 1 + i18n/en-GB.i18n.json | 1 + i18n/eo.i18n.json | 1 + i18n/es-AR.i18n.json | 1 + i18n/es.i18n.json | 1 + i18n/eu.i18n.json | 1 + i18n/fa.i18n.json | 1 + i18n/fi.i18n.json | 1 + i18n/fr.i18n.json | 1 + i18n/gl.i18n.json | 1 + i18n/he.i18n.json | 1 + i18n/hi.i18n.json | 1 + i18n/hu.i18n.json | 1 + i18n/hy.i18n.json | 1 + i18n/id.i18n.json | 1 + i18n/ig.i18n.json | 1 + i18n/it.i18n.json | 1 + i18n/ja.i18n.json | 1 + i18n/ka.i18n.json | 1 + i18n/km.i18n.json | 1 + i18n/ko.i18n.json | 1 + i18n/lv.i18n.json | 1 + i18n/mn.i18n.json | 1 + i18n/nb.i18n.json | 1 + i18n/nl.i18n.json | 1 + i18n/pl.i18n.json | 1 + i18n/pt-BR.i18n.json | 1 + i18n/pt.i18n.json | 1 + i18n/ro.i18n.json | 1 + i18n/ru.i18n.json | 1 + i18n/sr.i18n.json | 1 + i18n/sv.i18n.json | 1 + i18n/sw.i18n.json | 1 + i18n/ta.i18n.json | 1 + i18n/th.i18n.json | 1 + i18n/tr.i18n.json | 17 +++++++++-------- i18n/uk.i18n.json | 1 + i18n/vi.i18n.json | 1 + i18n/zh-CN.i18n.json | 1 + i18n/zh-TW.i18n.json | 1 + 47 files changed, 67 insertions(+), 20 deletions(-) diff --git a/i18n/ar.i18n.json b/i18n/ar.i18n.json index 6e3b9799e..841c8d351 100644 --- a/i18n/ar.i18n.json +++ b/i18n/ar.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "الذاكرة الكلية لنظام التشغيل", "OS_Type": "نوع نظام التشغيل", "OS_Uptime": "مدة تشغيل نظام التشغيل", + "days": "days", "hours": "الساعات", "minutes": "الدقائق", "seconds": "الثواني", diff --git a/i18n/bg.i18n.json b/i18n/bg.i18n.json index e396bc8b9..b13e4447b 100644 --- a/i18n/bg.i18n.json +++ b/i18n/bg.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "ОС Общо памет", "OS_Type": "Тип ОС", "OS_Uptime": "OS Ъптайм", + "days": "days", "hours": "часа", "minutes": "минути", "seconds": "секунди", diff --git a/i18n/br.i18n.json b/i18n/br.i18n.json index ef2d06364..2e450434a 100644 --- a/i18n/br.i18n.json +++ b/i18n/br.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/ca.i18n.json b/i18n/ca.i18n.json index 8d54a5fed..8de577ed5 100644 --- a/i18n/ca.i18n.json +++ b/i18n/ca.i18n.json @@ -6,25 +6,25 @@ "act-addChecklist": "afegida la checklist _checklist__ a __card__", "act-addChecklistItem": "afegit __checklistItem__ a la checklist __checklist__ on __card__", "act-addComment": "comentat a __card__: __comment__", - "act-createBoard": "creat __board__", + "act-createBoard": "nou __tauler__", "act-createCard": "afegit/da __card__ a __list__", "act-createCustomField": "created custom field __customField__", - "act-createList": "afegit/da __list__ a __board__", - "act-addBoardMember": "afegit/da __member__ a __board__", + "act-createList": "llista __afegida__ al __tauler__", + "act-addBoardMember": "usuari __afegit__ al __tauler__", "act-archivedBoard": "__tauler__ mogut al Arxiu", "act-archivedCard": "__fitxa__ moguda al Arxiu", "act-archivedList": "__llista__ mogud al Arxiu", "act-archivedSwimlane": "__swimlane__ moved to Archive", - "act-importBoard": "__board__ importat", + "act-importBoard": "tauler __importat__", "act-importCard": "__card__ importat", "act-importList": "__list__ importat", "act-joinMember": "afegit/da __member__ a __card__", "act-moveCard": "mou __card__ de __oldList__ a __list__", "act-removeBoardMember": "elimina __usuari__ del __tauler__", - "act-restoredCard": "recupera __card__ a __board__", + "act-restoredCard": "fitxa __restaurada__ al __tauler__", "act-unjoinMember": "elimina __member__ de __card__", "act-withBoardTitle": "__tauler__", - "act-withCardTitle": "[__board__] __card__", + "act-withCardTitle": "[__tauler__] __fitxa__", "actions": "Accions", "activities": "Activitats", "activity": "Activitat", @@ -70,7 +70,7 @@ "added": "Afegit", "addMemberPopup-title": "Membres", "admin": "Administrador", - "admin-desc": "Pots veure i editar fitxes, eliminar membres, i canviar la configuració del tauler", + "admin-desc": "Pots veure i editar fitxes, eliminar usuaris, i canviar la configuració del tauler.", "admin-announcement": "Alertes", "admin-announcement-active": "Activar alertes del Sistema", "admin-announcement-title": "Alertes d'administració", @@ -98,15 +98,15 @@ "attachment-delete-pop": "L'esborrat d'un arxiu adjunt és permanent. No es pot desfer.", "attachmentDeletePopup-title": "Esborrar adjunt?", "attachments": "Adjunts", - "auto-watch": "Automàticament segueix el taulers quan són creats", + "auto-watch": "Segueix automàticament el taulers quan són creats", "avatar-too-big": "L'avatar es massa gran (70KM max)", "back": "Enrere", "board-change-color": "Canvia el color", "board-nb-stars": "%s estrelles", "board-not-found": "No s'ha trobat el tauler", - "board-private-info": "Aquest tauler serà privat .", - "board-public-info": "Aquest tauler serà públic .", - "boardChangeColorPopup-title": "Canvia fons", + "board-private-info": "Aquest tauler serà privat.", + "board-public-info": "Aquest tauler serà públic.", + "boardChangeColorPopup-title": "Canvia fons del tauler", "boardChangeTitlePopup-title": "Canvia el nom tauler", "boardChangeVisibilityPopup-title": "Canvia visibilitat", "boardChangeWatchPopup-title": "Canvia seguiment", @@ -119,7 +119,7 @@ "bucket-example": "Igual que “Bucket List”, per exemple", "cancel": "Cancel·la", "card-archived": "Aquesta fitxa ha estat moguda al Arxiu.", - "board-archived": "This board is moved to Archive.", + "board-archived": "Aquest tauler s'ha mogut al arxiu", "card-comments-title": "Aquesta fitxa té %s comentaris.", "card-delete-notice": "L'esborrat és permanent. Perdreu totes les accions associades a aquesta fitxa.", "card-delete-pop": "Totes les accions s'eliminaran de l'activitat i no podreu tornar a obrir la fitxa. No es pot desfer.", @@ -480,6 +480,7 @@ "OS_Totalmem": "Memòria total", "OS_Type": "Tipus de SO", "OS_Uptime": "Temps d'activitat", + "days": "days", "hours": "hores", "minutes": "minuts", "seconds": "segons", diff --git a/i18n/cs.i18n.json b/i18n/cs.i18n.json index e033d99c3..687df2d6e 100644 --- a/i18n/cs.i18n.json +++ b/i18n/cs.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Celková paměť", "OS_Type": "Typ OS", "OS_Uptime": "OS Doba běhu systému", + "days": "days", "hours": "hodin", "minutes": "minut", "seconds": "sekund", diff --git a/i18n/da.i18n.json b/i18n/da.i18n.json index d19fd2e75..67bfb37c4 100644 --- a/i18n/da.i18n.json +++ b/i18n/da.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/de.i18n.json b/i18n/de.i18n.json index bcb71d983..0169b63e6 100644 --- a/i18n/de.i18n.json +++ b/i18n/de.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "Gesamter Arbeitsspeicher", "OS_Type": "Typ des Betriebssystems", "OS_Uptime": "Laufzeit des Systems", + "days": "Tage", "hours": "Stunden", "minutes": "Minuten", "seconds": "Sekunden", diff --git a/i18n/el.i18n.json b/i18n/el.i18n.json index 5a9558bd0..2d9a0804d 100644 --- a/i18n/el.i18n.json +++ b/i18n/el.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "ώρες", "minutes": "λεπτά", "seconds": "δευτερόλεπτα", diff --git a/i18n/en-GB.i18n.json b/i18n/en-GB.i18n.json index a2e323659..7caa8226a 100644 --- a/i18n/en-GB.i18n.json +++ b/i18n/en-GB.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/eo.i18n.json b/i18n/eo.i18n.json index fc418b11f..f9ca3d141 100644 --- a/i18n/eo.i18n.json +++ b/i18n/eo.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/es-AR.i18n.json b/i18n/es-AR.i18n.json index fe9ba9a4e..13c3e973a 100644 --- a/i18n/es-AR.i18n.json +++ b/i18n/es-AR.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "Memoria Total del SO", "OS_Type": "Tipo de SO", "OS_Uptime": "Tiempo encendido del SO", + "days": "days", "hours": "horas", "minutes": "minutos", "seconds": "segundos", diff --git a/i18n/es.i18n.json b/i18n/es.i18n.json index 3f254057d..92615f92c 100644 --- a/i18n/es.i18n.json +++ b/i18n/es.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "Memoria Total del sistema", "OS_Type": "Tipo de sistema", "OS_Uptime": "Tiempo activo del sistema", + "days": "days", "hours": "horas", "minutes": "minutos", "seconds": "segundos", diff --git a/i18n/eu.i18n.json b/i18n/eu.i18n.json index b2314da7d..e56a6dd16 100644 --- a/i18n/eu.i18n.json +++ b/i18n/eu.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "SE memoria guztira", "OS_Type": "SE mota", "OS_Uptime": "SE denbora abiatuta", + "days": "days", "hours": "ordu", "minutes": "minutu", "seconds": "segundo", diff --git a/i18n/fa.i18n.json b/i18n/fa.i18n.json index e03419070..c3700da66 100644 --- a/i18n/fa.i18n.json +++ b/i18n/fa.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "ساعت", "minutes": "دقیقه", "seconds": "ثانیه", diff --git a/i18n/fi.i18n.json b/i18n/fi.i18n.json index a5090959a..e87302381 100644 --- a/i18n/fi.i18n.json +++ b/i18n/fi.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "Käyttöjärjestelmän muistin kokonaismäärä", "OS_Type": "Käyttöjärjestelmän tyyppi", "OS_Uptime": "Käyttöjärjestelmä ollut käynnissä", + "days": "päivää", "hours": "tuntia", "minutes": "minuuttia", "seconds": "sekuntia", diff --git a/i18n/fr.i18n.json b/i18n/fr.i18n.json index ea5f5add8..acf1020c9 100644 --- a/i18n/fr.i18n.json +++ b/i18n/fr.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Mémoire totale", "OS_Type": "Type d'OS", "OS_Uptime": "OS Durée de fonctionnement", + "days": "jours", "hours": "heures", "minutes": "minutes", "seconds": "secondes", diff --git a/i18n/gl.i18n.json b/i18n/gl.i18n.json index 746133a8e..09c0b013a 100644 --- a/i18n/gl.i18n.json +++ b/i18n/gl.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/he.i18n.json b/i18n/he.i18n.json index e9abdf15e..cad6d0ec6 100644 --- a/i18n/he.i18n.json +++ b/i18n/he.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "סך כל הזיכרון (RAM)", "OS_Type": "סוג מערכת ההפעלה", "OS_Uptime": "זמן שעבר מאז האתחול האחרון", + "days": "days", "hours": "שעות", "minutes": "דקות", "seconds": "שניות", diff --git a/i18n/hi.i18n.json b/i18n/hi.i18n.json index 165ccca9b..f05862341 100644 --- a/i18n/hi.i18n.json +++ b/i18n/hi.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/hu.i18n.json b/i18n/hu.i18n.json index f04eaf03c..c6d388a81 100644 --- a/i18n/hu.i18n.json +++ b/i18n/hu.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "Operációs rendszer összes memóriája", "OS_Type": "Operációs rendszer típusa", "OS_Uptime": "Operációs rendszer üzemideje", + "days": "days", "hours": "óra", "minutes": "perc", "seconds": "másodperc", diff --git a/i18n/hy.i18n.json b/i18n/hy.i18n.json index 6c48bcfae..728f6abce 100644 --- a/i18n/hy.i18n.json +++ b/i18n/hy.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/id.i18n.json b/i18n/id.i18n.json index c7788f93b..99ceea142 100644 --- a/i18n/id.i18n.json +++ b/i18n/id.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/ig.i18n.json b/i18n/ig.i18n.json index 3e1298b01..c1be77a08 100644 --- a/i18n/ig.i18n.json +++ b/i18n/ig.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "elekere", "minutes": "nkeji", "seconds": "seconds", diff --git a/i18n/it.i18n.json b/i18n/it.i18n.json index 1feffb8b7..ccb87c4c7 100644 --- a/i18n/it.i18n.json +++ b/i18n/it.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "Memoria totale del sistema operativo ", "OS_Type": "Tipo di sistema operativo ", "OS_Uptime": "Tempo di attività del sistema operativo. ", + "days": "days", "hours": "ore", "minutes": "minuti", "seconds": "secondi", diff --git a/i18n/ja.i18n.json b/i18n/ja.i18n.json index ea18d714d..5c061e01d 100644 --- a/i18n/ja.i18n.json +++ b/i18n/ja.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OSトータルメモリ", "OS_Type": "OS種類", "OS_Uptime": "OSアップタイム", + "days": "days", "hours": "時", "minutes": "分", "seconds": "秒", diff --git a/i18n/ka.i18n.json b/i18n/ka.i18n.json index 7e2e8d47f..94fb1af31 100644 --- a/i18n/ka.i18n.json +++ b/i18n/ka.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS მთლიანი მეხსიერება", "OS_Type": "OS ტიპი", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "საათები", "minutes": "წუთები", "seconds": "წამები", diff --git a/i18n/km.i18n.json b/i18n/km.i18n.json index 510b5ea36..136cf79c6 100644 --- a/i18n/km.i18n.json +++ b/i18n/km.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/ko.i18n.json b/i18n/ko.i18n.json index 90dbd71e8..0933e0452 100644 --- a/i18n/ko.i18n.json +++ b/i18n/ko.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/lv.i18n.json b/i18n/lv.i18n.json index 0546f280b..5eb48261b 100644 --- a/i18n/lv.i18n.json +++ b/i18n/lv.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/mn.i18n.json b/i18n/mn.i18n.json index a1a8da253..2903c8902 100644 --- a/i18n/mn.i18n.json +++ b/i18n/mn.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/nb.i18n.json b/i18n/nb.i18n.json index 25f04af4d..c5a882465 100644 --- a/i18n/nb.i18n.json +++ b/i18n/nb.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/nl.i18n.json b/i18n/nl.i18n.json index 657c2e4cf..8b692495e 100644 --- a/i18n/nl.i18n.json +++ b/i18n/nl.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Totaal Geheugen", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "uren", "minutes": "minuten", "seconds": "seconden", diff --git a/i18n/pl.i18n.json b/i18n/pl.i18n.json index c32096e25..65d3924c6 100644 --- a/i18n/pl.i18n.json +++ b/i18n/pl.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "Dostępna pamięć RAM", "OS_Type": "Typ systemu", "OS_Uptime": "Czas działania systemu", + "days": "days", "hours": "godzin", "minutes": "minut", "seconds": "sekund", diff --git a/i18n/pt-BR.i18n.json b/i18n/pt-BR.i18n.json index e9cf736a3..e58507561 100644 --- a/i18n/pt-BR.i18n.json +++ b/i18n/pt-BR.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "Memória Total do SO", "OS_Type": "Tipo do SO", "OS_Uptime": "Disponibilidade do SO", + "days": "days", "hours": "horas", "minutes": "minutos", "seconds": "segundos", diff --git a/i18n/pt.i18n.json b/i18n/pt.i18n.json index 80ec412ac..cd94e6b4d 100644 --- a/i18n/pt.i18n.json +++ b/i18n/pt.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/ro.i18n.json b/i18n/ro.i18n.json index e8bdc6047..0e5657fd8 100644 --- a/i18n/ro.i18n.json +++ b/i18n/ro.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/ru.i18n.json b/i18n/ru.i18n.json index e82017ca3..e0dc0559c 100644 --- a/i18n/ru.i18n.json +++ b/i18n/ru.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "Общая память", "OS_Type": "Тип ОС", "OS_Uptime": "Время работы", + "days": "days", "hours": "часы", "minutes": "минуты", "seconds": "секунды", diff --git a/i18n/sr.i18n.json b/i18n/sr.i18n.json index d8d801624..57faf87f9 100644 --- a/i18n/sr.i18n.json +++ b/i18n/sr.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/sv.i18n.json b/i18n/sv.i18n.json index 9b1946e71..b6bb5db35 100644 --- a/i18n/sv.i18n.json +++ b/i18n/sv.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS totalt minne", "OS_Type": "OS Typ", "OS_Uptime": "OS drifttid", + "days": "days", "hours": "timmar", "minutes": "minuter", "seconds": "sekunder", diff --git a/i18n/sw.i18n.json b/i18n/sw.i18n.json index 03e7d57b5..e2bb4f108 100644 --- a/i18n/sw.i18n.json +++ b/i18n/sw.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/ta.i18n.json b/i18n/ta.i18n.json index 75e606a4e..431ae5f77 100644 --- a/i18n/ta.i18n.json +++ b/i18n/ta.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/th.i18n.json b/i18n/th.i18n.json index 393685152..216293626 100644 --- a/i18n/th.i18n.json +++ b/i18n/th.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/tr.i18n.json b/i18n/tr.i18n.json index 4bbb1437d..ecbd0cc74 100644 --- a/i18n/tr.i18n.json +++ b/i18n/tr.i18n.json @@ -11,10 +11,10 @@ "act-createCustomField": "__customField__ adlı özel alan yaratıldı", "act-createList": "__list__ listesini __board__ panosuna ekledi", "act-addBoardMember": "__member__ kullanıcısını __board__ panosuna ekledi", - "act-archivedBoard": "__board__ moved to Archive", - "act-archivedCard": "__card__ moved to Archive", - "act-archivedList": "__list__ moved to Archive", - "act-archivedSwimlane": "__swimlane__ moved to Archive", + "act-archivedBoard": "__board__ arşive taşındı", + "act-archivedCard": "__card__ Arşive taşındı", + "act-archivedList": "__list__ Arşive taşındı", + "act-archivedSwimlane": "__swimlane__ Arşive taşındı", "act-importBoard": "__board__ panosunu içe aktardı", "act-importCard": "__card__ kartını içe aktardı", "act-importList": "__list__ listesini içe aktardı", @@ -480,6 +480,7 @@ "OS_Totalmem": "İşletim Sistemi Toplam Belleği", "OS_Type": "İşletim Sistemi Tipi", "OS_Uptime": "İşletim Sistemi Toplam Açık Kalınan Süre", + "days": "günler", "hours": "saat", "minutes": "dakika", "seconds": "saniye", @@ -526,7 +527,7 @@ "activity-added-label": "added label '%s' to %s", "activity-removed-label": "removed label '%s' from %s", "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", + "activity-added-label-card": "etiket eklendi '%s'", "activity-removed-label-card": "removed label '%s'", "activity-delete-attach-card": "Ek silindi", "r-rule": "Kural", @@ -538,14 +539,14 @@ "r-delete-rule": "Kuralı sil", "r-new-rule-name": "Yeni kural başlığı", "r-no-rules": "Kural yok", - "r-when-a-card": "When a card", + "r-when-a-card": "Kart eklendiğinde", "r-is": "is", "r-is-moved": "is moved", "r-added-to": "added to", "r-removed-from": "Removed from", "r-the-board": "pano", "r-list": "liste", - "set-filter": "Set Filter", + "set-filter": "Filtrele", "r-moved-to": "Moved to", "r-moved-from": "Moved from", "r-archived": "Arşive taşındı", @@ -575,7 +576,7 @@ "r-card": "Kart", "r-add": "Ekle", "r-remove": "Kaldır", - "r-label": "label", + "r-label": "etiket", "r-member": "üye", "r-remove-all": "Tüm üyeleri karttan çıkarın", "r-checklist": "Kontrol Listesi", diff --git a/i18n/uk.i18n.json b/i18n/uk.i18n.json index 2bea1d311..8de67ca0d 100644 --- a/i18n/uk.i18n.json +++ b/i18n/uk.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/vi.i18n.json b/i18n/vi.i18n.json index 7cf7b840a..841523c8f 100644 --- a/i18n/vi.i18n.json +++ b/i18n/vi.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "OS Total Memory", "OS_Type": "OS Type", "OS_Uptime": "OS Uptime", + "days": "days", "hours": "hours", "minutes": "minutes", "seconds": "seconds", diff --git a/i18n/zh-CN.i18n.json b/i18n/zh-CN.i18n.json index b1e08b712..508d9fd2e 100644 --- a/i18n/zh-CN.i18n.json +++ b/i18n/zh-CN.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "系统全部内存", "OS_Type": "系统类型", "OS_Uptime": "系统运行时间", + "days": "天", "hours": "小时", "minutes": "分钟", "seconds": "秒", diff --git a/i18n/zh-TW.i18n.json b/i18n/zh-TW.i18n.json index 2305ce7df..010eb69fc 100644 --- a/i18n/zh-TW.i18n.json +++ b/i18n/zh-TW.i18n.json @@ -480,6 +480,7 @@ "OS_Totalmem": "系統總記憶體", "OS_Type": "系統類型", "OS_Uptime": "系統運行時間", + "days": "days", "hours": "小時", "minutes": "分鐘", "seconds": "秒", From e8c4e394fdaa23b4af6c3e8c4beb4d64e24f0db8 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 22 Jan 2019 14:01:07 +0200 Subject: [PATCH 21/29] Update translations. --- i18n/ar.i18n.json | 17 +++++++++++++++++ i18n/bg.i18n.json | 17 +++++++++++++++++ i18n/br.i18n.json | 17 +++++++++++++++++ i18n/ca.i18n.json | 17 +++++++++++++++++ i18n/cs.i18n.json | 17 +++++++++++++++++ i18n/da.i18n.json | 17 +++++++++++++++++ i18n/de.i18n.json | 17 +++++++++++++++++ i18n/el.i18n.json | 17 +++++++++++++++++ i18n/en-GB.i18n.json | 17 +++++++++++++++++ i18n/eo.i18n.json | 17 +++++++++++++++++ i18n/es-AR.i18n.json | 17 +++++++++++++++++ i18n/es.i18n.json | 17 +++++++++++++++++ i18n/eu.i18n.json | 17 +++++++++++++++++ i18n/fa.i18n.json | 17 +++++++++++++++++ i18n/fi.i18n.json | 17 +++++++++++++++++ i18n/fr.i18n.json | 17 +++++++++++++++++ i18n/gl.i18n.json | 17 +++++++++++++++++ i18n/he.i18n.json | 17 +++++++++++++++++ i18n/hi.i18n.json | 17 +++++++++++++++++ i18n/hu.i18n.json | 17 +++++++++++++++++ i18n/hy.i18n.json | 17 +++++++++++++++++ i18n/id.i18n.json | 17 +++++++++++++++++ i18n/ig.i18n.json | 17 +++++++++++++++++ i18n/it.i18n.json | 17 +++++++++++++++++ i18n/ja.i18n.json | 17 +++++++++++++++++ i18n/ka.i18n.json | 17 +++++++++++++++++ i18n/km.i18n.json | 17 +++++++++++++++++ i18n/ko.i18n.json | 17 +++++++++++++++++ i18n/lv.i18n.json | 17 +++++++++++++++++ i18n/mn.i18n.json | 17 +++++++++++++++++ i18n/nb.i18n.json | 17 +++++++++++++++++ i18n/nl.i18n.json | 17 +++++++++++++++++ i18n/pl.i18n.json | 17 +++++++++++++++++ i18n/pt-BR.i18n.json | 17 +++++++++++++++++ i18n/pt.i18n.json | 17 +++++++++++++++++ i18n/ro.i18n.json | 17 +++++++++++++++++ i18n/ru.i18n.json | 17 +++++++++++++++++ i18n/sr.i18n.json | 17 +++++++++++++++++ i18n/sv.i18n.json | 17 +++++++++++++++++ i18n/sw.i18n.json | 17 +++++++++++++++++ i18n/ta.i18n.json | 17 +++++++++++++++++ i18n/th.i18n.json | 17 +++++++++++++++++ i18n/tr.i18n.json | 17 +++++++++++++++++ i18n/uk.i18n.json | 17 +++++++++++++++++ i18n/vi.i18n.json | 17 +++++++++++++++++ i18n/zh-CN.i18n.json | 17 +++++++++++++++++ i18n/zh-TW.i18n.json | 17 +++++++++++++++++ 47 files changed, 799 insertions(+) diff --git a/i18n/ar.i18n.json b/i18n/ar.i18n.json index 841c8d351..f490691fc 100644 --- a/i18n/ar.i18n.json +++ b/i18n/ar.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "تعليق", "comment-placeholder": "أكتب تعليق", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/bg.i18n.json b/i18n/bg.i18n.json index b13e4447b..25cf67a52 100644 --- a/i18n/bg.i18n.json +++ b/i18n/bg.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "черно", "color-blue": "синьо", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "зелено", + "color-indigo": "indigo", "color-lime": "лайм", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "оранжево", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "розово", + "color-plum": "plum", "color-purple": "пурпурно", "color-red": "червено", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "светло синьо", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "жълто", "comment": "Коментирай", "comment-placeholder": "Напиши коментар", @@ -501,6 +516,7 @@ "card-end-on": "Завършена на", "editCardReceivedDatePopup-title": "Промени датата на получаване", "editCardEndDatePopup-title": "Промени датата на завършване", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/br.i18n.json b/i18n/br.i18n.json index 2e450434a..d17e232cf 100644 --- a/i18n/br.i18n.json +++ b/i18n/br.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "du", "color-blue": "glas", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "gwer", + "color-indigo": "indigo", "color-lime": "melen sitroñs", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orañjez", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "roz", + "color-plum": "plum", "color-purple": "mouk", "color-red": "ruz", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "pers", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "melen", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/ca.i18n.json b/i18n/ca.i18n.json index 8de577ed5..f742196b8 100644 --- a/i18n/ca.i18n.json +++ b/i18n/ca.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "negre", "color-blue": "blau", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "verd", + "color-indigo": "indigo", "color-lime": "llima", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "taronja", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "rosa", + "color-plum": "plum", "color-purple": "púrpura", "color-red": "vermell", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "cel", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "groc", "comment": "Comentari", "comment-placeholder": "Escriu un comentari", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assignat Per", "requested-by": "Demanat Per", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/cs.i18n.json b/i18n/cs.i18n.json index 687df2d6e..5a93dd31f 100644 --- a/i18n/cs.i18n.json +++ b/i18n/cs.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "černá", "color-blue": "modrá", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "zelená", + "color-indigo": "indigo", "color-lime": "světlezelená", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "oranžová", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "růžová", + "color-plum": "plum", "color-purple": "fialová", "color-red": "červená", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "nebeská", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "žlutá", "comment": "Komentář", "comment-placeholder": "Text komentáře", @@ -501,6 +516,7 @@ "card-end-on": "Končí v", "editCardReceivedDatePopup-title": "Změnit datum přijetí", "editCardEndDatePopup-title": "Změnit datum konce", + "setCardColor-title": "Set color", "assigned-by": "Přidělil(a)", "requested-by": "Vyžádal(a)", "board-delete-notice": "Smazání je trvalé. Přijdete o všechny sloupce, karty a akce spojené s tímto tablem.", @@ -579,6 +595,7 @@ "r-label": "štítek", "r-member": "člen", "r-remove-all": "Odstranit všechny členy z této karty", + "r-set-color": "Set color to", "r-checklist": "zaškrtávací seznam", "r-check-all": "Zaškrtnout vše", "r-uncheck-all": "Odškrtnout vše", diff --git a/i18n/da.i18n.json b/i18n/da.i18n.json index 67bfb37c4..f080d2e03 100644 --- a/i18n/da.i18n.json +++ b/i18n/da.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/de.i18n.json b/i18n/de.i18n.json index 0169b63e6..da6574c7b 100644 --- a/i18n/de.i18n.json +++ b/i18n/de.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "Sie können das Board wiederherstellen, indem Sie die Schaltfläche \"Archiv\" in der Kopfzeile der Startseite anklicken.", "color-black": "schwarz", "color-blue": "blau", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "grün", + "color-indigo": "indigo", "color-lime": "hellgrün", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "lila", "color-red": "rot", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "himmelblau", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "gelb", "comment": "Kommentar", "comment-placeholder": "Kommentar schreiben", @@ -501,6 +516,7 @@ "card-end-on": "Endet am", "editCardReceivedDatePopup-title": "Empfangsdatum ändern", "editCardEndDatePopup-title": "Enddatum ändern", + "setCardColor-title": "Set color", "assigned-by": "Zugewiesen von", "requested-by": "Angefordert von", "board-delete-notice": "Löschen kann nicht rückgängig gemacht werden. Sie werden alle Listen, Karten und Aktionen, die mit diesem Board verbunden sind, verlieren.", @@ -579,6 +595,7 @@ "r-label": "Label", "r-member": "Mitglied", "r-remove-all": "Entferne alle Mitglieder von der Karte", + "r-set-color": "Set color to", "r-checklist": "Checkliste", "r-check-all": "Alle markieren", "r-uncheck-all": "Alle abwählen", diff --git a/i18n/el.i18n.json b/i18n/el.i18n.json index 2d9a0804d..74dfe693c 100644 --- a/i18n/el.i18n.json +++ b/i18n/el.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "μαύρο", "color-blue": "μπλε", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "πράσινο", + "color-indigo": "indigo", "color-lime": "λάιμ", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "πορτοκαλί", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "ροζ", + "color-plum": "plum", "color-purple": "μωβ", "color-red": "κόκκινο", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "ουρανός", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "κίτρινο", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/en-GB.i18n.json b/i18n/en-GB.i18n.json index 7caa8226a..36f86e7b8 100644 --- a/i18n/en-GB.i18n.json +++ b/i18n/en-GB.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/eo.i18n.json b/i18n/eo.i18n.json index f9ca3d141..c1beb5471 100644 --- a/i18n/eo.i18n.json +++ b/i18n/eo.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "nigra", "color-blue": "blua", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "verda", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "oranĝa", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "ruĝa", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "flava", "comment": "Komento", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/es-AR.i18n.json b/i18n/es-AR.i18n.json index 13c3e973a..42a12e44a 100644 --- a/i18n/es-AR.i18n.json +++ b/i18n/es-AR.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "negro", "color-blue": "azul", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "verde", + "color-indigo": "indigo", "color-lime": "lima", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "naranja", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "rosa", + "color-plum": "plum", "color-purple": "púrpura", "color-red": "rojo", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "cielo", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "amarillo", "comment": "Comentario", "comment-placeholder": "Comentar", @@ -501,6 +516,7 @@ "card-end-on": "Termina en", "editCardReceivedDatePopup-title": "Cambiar fecha de recepción", "editCardEndDatePopup-title": "Cambiar fecha de término", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/es.i18n.json b/i18n/es.i18n.json index 92615f92c..02ff9944d 100644 --- a/i18n/es.i18n.json +++ b/i18n/es.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "Podrás restaurar el tablero haciendo clic en el botón \"Archivo\" del encabezado de la pantalla inicial.", "color-black": "negra", "color-blue": "azul", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "verde", + "color-indigo": "indigo", "color-lime": "lima", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "naranja", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "rosa", + "color-plum": "plum", "color-purple": "violeta", "color-red": "roja", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "celeste", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "amarilla", "comment": "Comentar", "comment-placeholder": "Escribir comentario", @@ -501,6 +516,7 @@ "card-end-on": "Finalizado el", "editCardReceivedDatePopup-title": "Cambiar la fecha de recepción", "editCardEndDatePopup-title": "Cambiar la fecha de finalización", + "setCardColor-title": "Set color", "assigned-by": "Asignado por", "requested-by": "Solicitado por", "board-delete-notice": "Se eliminarán todas las listas, tarjetas y acciones asociadas a este tablero. Esta acción no puede deshacerse.", @@ -579,6 +595,7 @@ "r-label": "etiqueta", "r-member": "miembro", "r-remove-all": "Eliminar todos los miembros de la tarjeta", + "r-set-color": "Set color to", "r-checklist": "lista de verificación", "r-check-all": "Marcar todo", "r-uncheck-all": "Desmarcar todo", diff --git a/i18n/eu.i18n.json b/i18n/eu.i18n.json index e56a6dd16..364b3d444 100644 --- a/i18n/eu.i18n.json +++ b/i18n/eu.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "beltza", "color-blue": "urdina", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "berdea", + "color-indigo": "indigo", "color-lime": "lima", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "laranja", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "larrosa", + "color-plum": "plum", "color-purple": "purpura", "color-red": "gorria", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "zerua", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "horia", "comment": "Iruzkina", "comment-placeholder": "Idatzi iruzkin bat", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/fa.i18n.json b/i18n/fa.i18n.json index c3700da66..11aea12c7 100644 --- a/i18n/fa.i18n.json +++ b/i18n/fa.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "شما می توانید با کلیک کردن بر روی دکمه «بایگانی» از صفحه هدر، صفحه را بازگردانید.", "color-black": "مشکی", "color-blue": "آبی", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "سبز", + "color-indigo": "indigo", "color-lime": "لیمویی", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "نارنجی", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "صورتی", + "color-plum": "plum", "color-purple": "بنفش", "color-red": "قرمز", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "آبی آسمانی", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "زرد", "comment": "نظر", "comment-placeholder": "درج نظر", @@ -501,6 +516,7 @@ "card-end-on": "پایان در", "editCardReceivedDatePopup-title": "تغییر تاریخ رسید", "editCardEndDatePopup-title": "تغییر تاریخ پایان", + "setCardColor-title": "Set color", "assigned-by": "محول شده توسط", "requested-by": "تقاضا شده توسط", "board-delete-notice": "حذف دائمی است شما تمام لیست ها، کارت ها و اقدامات مرتبط با این برد را از دست خواهید داد.", @@ -579,6 +595,7 @@ "r-label": "برچسب", "r-member": "عضو", "r-remove-all": "حذف همه کاربران از کارت", + "r-set-color": "Set color to", "r-checklist": "چک لیست", "r-check-all": "انتخاب همه", "r-uncheck-all": "لغو انتخاب همه", diff --git a/i18n/fi.i18n.json b/i18n/fi.i18n.json index e87302381..aa1a649c2 100644 --- a/i18n/fi.i18n.json +++ b/i18n/fi.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "Voit palauttaa taulun klikkaamalla “Arkisto” painiketta taululistan yläpalkista.", "color-black": "musta", "color-blue": "sininen", + "color-crimson": "karmiininpunainen", + "color-darkgreen": "tummanvihreä", + "color-gold": "kulta", + "color-gray": "harmaa", "color-green": "vihreä", + "color-indigo": "syvän sininen", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "vaaleanpunainen ruusu", + "color-navy": "laivastonsininen", "color-orange": "oranssi", + "color-paleturquoise": "vaalean turkoosi", + "color-peachpuff": "persikanpunainen", "color-pink": "vaaleanpunainen", + "color-plum": "luumunvärinen", "color-purple": "violetti", "color-red": "punainen", + "color-saddlebrown": "satulanruskea", + "color-silver": "hopea", "color-sky": "taivas", + "color-slateblue": "liuskekivi sininen", + "color-white": "valkoinen", "color-yellow": "keltainen", "comment": "Kommentti", "comment-placeholder": "Kirjoita kommentti", @@ -501,6 +516,7 @@ "card-end-on": "Loppuu", "editCardReceivedDatePopup-title": "Vaihda vastaanottamispäivää", "editCardEndDatePopup-title": "Vaihda loppumispäivää", + "setCardColor-title": "Aseta väri", "assigned-by": "Tehtävänantaja", "requested-by": "Pyytäjä", "board-delete-notice": "Poistaminen on lopullista. Menetät kaikki listat, kortit ja toimet tällä taululla.", @@ -579,6 +595,7 @@ "r-label": "tunniste", "r-member": "jäsen", "r-remove-all": "Poista kaikki jäsenet kortilta", + "r-set-color": "Aseta väriksi", "r-checklist": "tarkistuslista", "r-check-all": "Ruksaa kaikki", "r-uncheck-all": "Poista ruksi kaikista", diff --git a/i18n/fr.i18n.json b/i18n/fr.i18n.json index acf1020c9..919dad3e3 100644 --- a/i18n/fr.i18n.json +++ b/i18n/fr.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "Vous pouvez restaurer le tableau en cliquant sur le bouton « Archives » depuis le menu en entête.", "color-black": "noir", "color-blue": "bleu", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "vert", + "color-indigo": "indigo", "color-lime": "citron vert", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "rose", + "color-plum": "plum", "color-purple": "violet", "color-red": "rouge", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "ciel", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "jaune", "comment": "Commenter", "comment-placeholder": "Écrire un commentaire", @@ -501,6 +516,7 @@ "card-end-on": "Se termine le", "editCardReceivedDatePopup-title": "Modifier la date de réception", "editCardEndDatePopup-title": "Modifier la date de fin", + "setCardColor-title": "Set color", "assigned-by": "Assigné par", "requested-by": "Demandé par", "board-delete-notice": "La suppression est définitive. Vous perdrez toutes les listes, cartes et actions associées à ce tableau.", @@ -579,6 +595,7 @@ "r-label": "étiquette", "r-member": "membre", "r-remove-all": "Supprimer tous les membres de la carte", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Tout cocher", "r-uncheck-all": "Tout décocher", diff --git a/i18n/gl.i18n.json b/i18n/gl.i18n.json index 09c0b013a..518b6fd6f 100644 --- a/i18n/gl.i18n.json +++ b/i18n/gl.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "negro", "color-blue": "azul", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "verde", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "laranxa", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "rosa", + "color-plum": "plum", "color-purple": "purple", "color-red": "vermello", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "celeste", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "amarelo", "comment": "Comentario", "comment-placeholder": "Escribir un comentario", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/he.i18n.json b/i18n/he.i18n.json index cad6d0ec6..9c837e9c0 100644 --- a/i18n/he.i18n.json +++ b/i18n/he.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "ניתן לשחזר את הלוח בלחיצה על כפתור „ארכיונים“ מהכותרת העליונה.", "color-black": "שחור", "color-blue": "כחול", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "ירוק", + "color-indigo": "indigo", "color-lime": "ליים", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "כתום", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "ורוד", + "color-plum": "plum", "color-purple": "סגול", "color-red": "אדום", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "תכלת", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "צהוב", "comment": "לפרסם", "comment-placeholder": "כתיבת הערה", @@ -501,6 +516,7 @@ "card-end-on": "מועד הסיום", "editCardReceivedDatePopup-title": "החלפת מועד הקבלה", "editCardEndDatePopup-title": "החלפת מועד הסיום", + "setCardColor-title": "Set color", "assigned-by": "הוקצה על ידי", "requested-by": "התבקש על ידי", "board-delete-notice": "מחיקה היא לצמיתות. כל הרשימות, הכרטיבים והפעולות שקשורים בלוח הזה ילכו לאיבוד.", @@ -579,6 +595,7 @@ "r-label": "תווית", "r-member": "חבר", "r-remove-all": "הסרת כל החברים מהכרטיס", + "r-set-color": "Set color to", "r-checklist": "רשימת משימות", "r-check-all": "לסמן הכול", "r-uncheck-all": "לבטל את הסימון", diff --git a/i18n/hi.i18n.json b/i18n/hi.i18n.json index f05862341..ca7d502dc 100644 --- a/i18n/hi.i18n.json +++ b/i18n/hi.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "काला", "color-blue": "नीला", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "हरा", + "color-indigo": "indigo", "color-lime": "हल्का हरा", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "नारंगी", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "गुलाबी", + "color-plum": "plum", "color-purple": "बैंगनी", "color-red": "लाल", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "आकाशिया नीला", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "पीला", "comment": "टिप्पणी", "comment-placeholder": "टिप्पणी लिखें", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose संपूर्ण lists, कार्ड और actions associated साथ में यह बोर्ड.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "हटाएँ संपूर्ण सदस्य से the कार्ड", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/hu.i18n.json b/i18n/hu.i18n.json index c6d388a81..5a02ae64d 100644 --- a/i18n/hu.i18n.json +++ b/i18n/hu.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "fekete", "color-blue": "kék", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "zöld", + "color-indigo": "indigo", "color-lime": "citrus", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "narancssárga", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "rózsaszín", + "color-plum": "plum", "color-purple": "lila", "color-red": "piros", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "égszínkék", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "sárga", "comment": "Megjegyzés", "comment-placeholder": "Megjegyzés írása", @@ -501,6 +516,7 @@ "card-end-on": "Befejeződik ekkor", "editCardReceivedDatePopup-title": "Érkezési dátum megváltoztatása", "editCardEndDatePopup-title": "Befejezési dátum megváltoztatása", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/hy.i18n.json b/i18n/hy.i18n.json index 728f6abce..601503e05 100644 --- a/i18n/hy.i18n.json +++ b/i18n/hy.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/id.i18n.json b/i18n/id.i18n.json index 99ceea142..a9496d631 100644 --- a/i18n/id.i18n.json +++ b/i18n/id.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "hitam", "color-blue": "biru", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "hijau", + "color-indigo": "indigo", "color-lime": "hijau muda", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "jingga", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "merah muda", + "color-plum": "plum", "color-purple": "ungu", "color-red": "merah", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "biru muda", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "kuning", "comment": "Komentar", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/ig.i18n.json b/i18n/ig.i18n.json index c1be77a08..b55a7e00a 100644 --- a/i18n/ig.i18n.json +++ b/i18n/ig.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/it.i18n.json b/i18n/it.i18n.json index ccb87c4c7..fed71957e 100644 --- a/i18n/it.i18n.json +++ b/i18n/it.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "nero", "color-blue": "blu", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "verde", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "arancione", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "rosa", + "color-plum": "plum", "color-purple": "viola", "color-red": "rosso", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "azzurro", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "giallo", "comment": "Commento", "comment-placeholder": "Scrivi Commento", @@ -501,6 +516,7 @@ "card-end-on": "Termina il", "editCardReceivedDatePopup-title": "Cambia data ricezione", "editCardEndDatePopup-title": "Cambia data finale", + "setCardColor-title": "Set color", "assigned-by": "Assegnato da", "requested-by": "Richiesto da", "board-delete-notice": "L'eliminazione è permanente. Tutte le azioni, liste e schede associate a questa bacheca andranno perse.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/ja.i18n.json b/i18n/ja.i18n.json index 5c061e01d..3a4828948 100644 --- a/i18n/ja.i18n.json +++ b/i18n/ja.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "黒", "color-blue": "青", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "緑", + "color-indigo": "indigo", "color-lime": "ライム", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "オレンジ", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "ピンク", + "color-plum": "plum", "color-purple": "紫", "color-red": "赤", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "空", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "黄", "comment": "コメント", "comment-placeholder": "コメントを書く", @@ -501,6 +516,7 @@ "card-end-on": "終了日", "editCardReceivedDatePopup-title": "受付日の変更", "editCardEndDatePopup-title": "終了日の変更", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/ka.i18n.json b/i18n/ka.i18n.json index 94fb1af31..c4fe85c86 100644 --- a/i18n/ka.i18n.json +++ b/i18n/ka.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "შავი", "color-blue": "ლურჯი", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "მწვანე", + "color-indigo": "indigo", "color-lime": "ღია ყვითელი", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "ნარინჯისფერი", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "ვარდისფერი", + "color-plum": "plum", "color-purple": "იასამნისფერი", "color-red": "წითელი ", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "ცისფერი", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "ყვითელი", "comment": "კომენტარი", "comment-placeholder": "დაწერეთ კომენტარი", @@ -501,6 +516,7 @@ "card-end-on": "დასრულდება : ", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "შეცვალეთ საბოლოო თარიღი", + "setCardColor-title": "Set color", "assigned-by": "უფლებამოსილების გამცემი ", "requested-by": "მომთხოვნი", "board-delete-notice": "წაშლის შემთხვევაში თქვენ დაკარგავთ ამ დაფასთან ასოცირებულ ყველა მონაცემს მათ შორის : ჩამონათვალს, ბარათებს და მოქმედებებს. ", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/km.i18n.json b/i18n/km.i18n.json index 136cf79c6..71566be7a 100644 --- a/i18n/km.i18n.json +++ b/i18n/km.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/ko.i18n.json b/i18n/ko.i18n.json index 0933e0452..71dd10926 100644 --- a/i18n/ko.i18n.json +++ b/i18n/ko.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "블랙", "color-blue": "블루", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "그린", + "color-indigo": "indigo", "color-lime": "라임", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "오렌지", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "핑크", + "color-plum": "plum", "color-purple": "퍼플", "color-red": "레드", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "스카이", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "옐로우", "comment": "댓글", "comment-placeholder": "댓글 입력", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/lv.i18n.json b/i18n/lv.i18n.json index 5eb48261b..7edcac0a3 100644 --- a/i18n/lv.i18n.json +++ b/i18n/lv.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/mn.i18n.json b/i18n/mn.i18n.json index 2903c8902..2309fd5da 100644 --- a/i18n/mn.i18n.json +++ b/i18n/mn.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/nb.i18n.json b/i18n/nb.i18n.json index c5a882465..a67d4cb24 100644 --- a/i18n/nb.i18n.json +++ b/i18n/nb.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/nl.i18n.json b/i18n/nl.i18n.json index 8b692495e..e7509c8ee 100644 --- a/i18n/nl.i18n.json +++ b/i18n/nl.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "zwart", "color-blue": "blauw", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "groen", + "color-indigo": "indigo", "color-lime": "Felgroen", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "Oranje", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "Roze", + "color-plum": "plum", "color-purple": "Paars", "color-red": "Rood", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "Lucht", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "Geel", "comment": "Reageer", "comment-placeholder": "Schrijf reactie", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/pl.i18n.json b/i18n/pl.i18n.json index 65d3924c6..bdc28d328 100644 --- a/i18n/pl.i18n.json +++ b/i18n/pl.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "Będziesz w stanie przywrócić tablicę poprzez kliknięcie przycisku \"Archiwizuj\" w nagłówku strony domowej.", "color-black": "czarny", "color-blue": "niebieski", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "zielony", + "color-indigo": "indigo", "color-lime": "limonkowy", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "pomarańczowy", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "różowy", + "color-plum": "plum", "color-purple": "fioletowy", "color-red": "czerwony", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "błękitny", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "żółty", "comment": "Komentarz", "comment-placeholder": "Dodaj komentarz", @@ -501,6 +516,7 @@ "card-end-on": "Kończy się", "editCardReceivedDatePopup-title": "Zmień datę odebrania", "editCardEndDatePopup-title": "Zmień datę ukończenia", + "setCardColor-title": "Set color", "assigned-by": "Przypisane przez", "requested-by": "Zlecone przez", "board-delete-notice": "Usuwanie jest permanentne. Stracisz wszystkie listy, kart oraz czynności przypisane do tej tablicy.", @@ -579,6 +595,7 @@ "r-label": "etykieta", "r-member": "członek", "r-remove-all": "Usuń wszystkich członków tej karty", + "r-set-color": "Set color to", "r-checklist": "lista zadań", "r-check-all": "Zaznacz wszystkie", "r-uncheck-all": "Odznacz wszystkie", diff --git a/i18n/pt-BR.i18n.json b/i18n/pt-BR.i18n.json index e58507561..bfa3903c5 100644 --- a/i18n/pt-BR.i18n.json +++ b/i18n/pt-BR.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "Você será capaz de restaurar o quadro clicando no botão “Arquivo-morto” a partir do cabeçalho do Início.", "color-black": "preto", "color-blue": "azul", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "verde", + "color-indigo": "indigo", "color-lime": "verde limão", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "laranja", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "cor-de-rosa", + "color-plum": "plum", "color-purple": "roxo", "color-red": "vermelho", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "azul-celeste", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "amarelo", "comment": "Comentário", "comment-placeholder": "Escrever Comentário", @@ -501,6 +516,7 @@ "card-end-on": "Termina em", "editCardReceivedDatePopup-title": "Modificar data de recebimento", "editCardEndDatePopup-title": "Mudar data de fim", + "setCardColor-title": "Set color", "assigned-by": "Atribuído por", "requested-by": "Solicitado por", "board-delete-notice": "Excluir é permanente. Você perderá todas as listas, cartões e ações associados nesse quadro.", @@ -579,6 +595,7 @@ "r-label": "etiqueta", "r-member": "membro", "r-remove-all": "Remover todos os membros do cartão", + "r-set-color": "Set color to", "r-checklist": "lista de verificação", "r-check-all": "Marcar todos", "r-uncheck-all": "Desmarcar todos", diff --git a/i18n/pt.i18n.json b/i18n/pt.i18n.json index cd94e6b4d..4fc0889a7 100644 --- a/i18n/pt.i18n.json +++ b/i18n/pt.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comentário", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/ro.i18n.json b/i18n/ro.i18n.json index 0e5657fd8..ad64b79ab 100644 --- a/i18n/ro.i18n.json +++ b/i18n/ro.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/ru.i18n.json b/i18n/ru.i18n.json index e0dc0559c..24e582d61 100644 --- a/i18n/ru.i18n.json +++ b/i18n/ru.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "Вы сможете восстановить доску, нажав \"Архив\" в заголовке домашней страницы.", "color-black": "черный", "color-blue": "синий", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "зеленый", + "color-indigo": "indigo", "color-lime": "лимоновый", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "оранжевый", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "розовый", + "color-plum": "plum", "color-purple": "фиолетовый", "color-red": "красный", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "голубой", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "желтый", "comment": "Добавить комментарий", "comment-placeholder": "Написать комментарий", @@ -501,6 +516,7 @@ "card-end-on": "Завершится до", "editCardReceivedDatePopup-title": "Изменить дату получения", "editCardEndDatePopup-title": "Изменить дату завершения", + "setCardColor-title": "Set color", "assigned-by": "Поручил", "requested-by": "Запросил", "board-delete-notice": "Удаление является постоянным. Вы потеряете все списки, карты и действия, связанные с этой доской.", @@ -579,6 +595,7 @@ "r-label": "метку", "r-member": "участника", "r-remove-all": "Удалить всех участников из карточки", + "r-set-color": "Set color to", "r-checklist": "контрольный список", "r-check-all": "Отметить все", "r-uncheck-all": "Снять все", diff --git a/i18n/sr.i18n.json b/i18n/sr.i18n.json index 57faf87f9..f723c532d 100644 --- a/i18n/sr.i18n.json +++ b/i18n/sr.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/sv.i18n.json b/i18n/sv.i18n.json index b6bb5db35..c3cb5dcc1 100644 --- a/i18n/sv.i18n.json +++ b/i18n/sv.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "svart", "color-blue": "blå", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "grön", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "rosa", + "color-plum": "plum", "color-purple": "lila", "color-red": "röd", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "himmel", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "gul", "comment": "Kommentera", "comment-placeholder": "Skriv kommentar", @@ -501,6 +516,7 @@ "card-end-on": "Slutar den", "editCardReceivedDatePopup-title": "Ändra mottagningsdatum", "editCardEndDatePopup-title": "Ändra slutdatum", + "setCardColor-title": "Set color", "assigned-by": "Tilldelad av", "requested-by": "Efterfrågad av", "board-delete-notice": "Borttagningen är permanent. Du kommer förlora alla listor, kort och händelser kopplade till den här anslagstavlan.", @@ -579,6 +595,7 @@ "r-label": "etikett", "r-member": "medlem", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklista", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/sw.i18n.json b/i18n/sw.i18n.json index e2bb4f108..426ed6341 100644 --- a/i18n/sw.i18n.json +++ b/i18n/sw.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "Nyeusi", "color-blue": "Samawati", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "Kijani", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Changia", "comment-placeholder": "Andika changio", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/ta.i18n.json b/i18n/ta.i18n.json index 431ae5f77..c1847fc1e 100644 --- a/i18n/ta.i18n.json +++ b/i18n/ta.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/th.i18n.json b/i18n/th.i18n.json index 216293626..f0c134721 100644 --- a/i18n/th.i18n.json +++ b/i18n/th.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "ดำ", "color-blue": "น้ำเงิน", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "เขียว", + "color-indigo": "indigo", "color-lime": "เหลืองมะนาว", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "ส้ม", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "ชมพู", + "color-plum": "plum", "color-purple": "ม่วง", "color-red": "แดง", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "ฟ้า", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "เหลือง", "comment": "คอมเม็นต์", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/tr.i18n.json b/i18n/tr.i18n.json index ecbd0cc74..8d8acd4de 100644 --- a/i18n/tr.i18n.json +++ b/i18n/tr.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "\n92/5000\nAna başlıktaki “Arşiv” düğmesine tıklayarak tahtayı geri yükleyebilirsiniz.", "color-black": "siyah", "color-blue": "mavi", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "yeşil", + "color-indigo": "indigo", "color-lime": "misket limonu", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "turuncu", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pembe", + "color-plum": "plum", "color-purple": "mor", "color-red": "kırmızı", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "açık mavi", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "sarı", "comment": "Yorum", "comment-placeholder": "Yorum Yaz", @@ -501,6 +516,7 @@ "card-end-on": "Bitiş zamanı", "editCardReceivedDatePopup-title": "Giriş tarihini değiştir", "editCardEndDatePopup-title": "Bitiş tarihini değiştir", + "setCardColor-title": "Set color", "assigned-by": "Atamayı yapan", "requested-by": "Talep Eden", "board-delete-notice": "Silme kalıcıdır. Bu kartla ilişkili tüm listeleri, kartları ve işlemleri kaybedeceksiniz.", @@ -579,6 +595,7 @@ "r-label": "etiket", "r-member": "üye", "r-remove-all": "Tüm üyeleri karttan çıkarın", + "r-set-color": "Set color to", "r-checklist": "Kontrol Listesi", "r-check-all": "Tümünü işaretle", "r-uncheck-all": "Tüm işaretleri kaldır", diff --git a/i18n/uk.i18n.json b/i18n/uk.i18n.json index 8de67ca0d..5b6317cbe 100644 --- a/i18n/uk.i18n.json +++ b/i18n/uk.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "чорний", "color-blue": "синій", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "зелений", + "color-indigo": "indigo", "color-lime": "лайм", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "помаранчевий", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "рожевий", + "color-plum": "plum", "color-purple": "фіолетовий", "color-red": "червоний", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "жовтий", "comment": "Коментар", "comment-placeholder": "Написати коментар", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/vi.i18n.json b/i18n/vi.i18n.json index 841523c8f..ef4b6b8c9 100644 --- a/i18n/vi.i18n.json +++ b/i18n/vi.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "black", "color-blue": "blue", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "green", + "color-indigo": "indigo", "color-lime": "lime", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "orange", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "pink", + "color-plum": "plum", "color-purple": "purple", "color-red": "red", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "sky", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "yellow", "comment": "Comment", "comment-placeholder": "Write Comment", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", diff --git a/i18n/zh-CN.i18n.json b/i18n/zh-CN.i18n.json index 508d9fd2e..1bae6876f 100644 --- a/i18n/zh-CN.i18n.json +++ b/i18n/zh-CN.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "您可以通过主页头部的“归档”按钮,来恢复看板。", "color-black": "黑色", "color-blue": "蓝色", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "绿色", + "color-indigo": "indigo", "color-lime": "绿黄", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "橙色", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "粉红", + "color-plum": "plum", "color-purple": "紫色", "color-red": "红色", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "天蓝", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "黄色", "comment": "评论", "comment-placeholder": "添加评论", @@ -501,6 +516,7 @@ "card-end-on": "终止于", "editCardReceivedDatePopup-title": "修改接收日期", "editCardEndDatePopup-title": "修改终止日期", + "setCardColor-title": "Set color", "assigned-by": "分配人", "requested-by": "需求人", "board-delete-notice": "删除时永久操作,将会丢失此看板上的所有列表、卡片和动作。", @@ -579,6 +595,7 @@ "r-label": "标签", "r-member": "成员", "r-remove-all": "从卡片移除所有成员", + "r-set-color": "Set color to", "r-checklist": "清单", "r-check-all": "勾选所有", "r-uncheck-all": "取消勾选所有", diff --git a/i18n/zh-TW.i18n.json b/i18n/zh-TW.i18n.json index 010eb69fc..43eff7def 100644 --- a/i18n/zh-TW.i18n.json +++ b/i18n/zh-TW.i18n.json @@ -169,13 +169,28 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "黑色", "color-blue": "藍色", + "color-crimson": "crimson", + "color-darkgreen": "darkgreen", + "color-gold": "gold", + "color-gray": "gray", "color-green": "綠色", + "color-indigo": "indigo", "color-lime": "綠黃", + "color-magenta": "magenta", + "color-mistyrose": "mistyrose", + "color-navy": "navy", "color-orange": "橙色", + "color-paleturquoise": "paleturquoise", + "color-peachpuff": "peachpuff", "color-pink": "粉紅", + "color-plum": "plum", "color-purple": "紫色", "color-red": "紅色", + "color-saddlebrown": "saddlebrown", + "color-silver": "silver", "color-sky": "天藍", + "color-slateblue": "slateblue", + "color-white": "white", "color-yellow": "黃色", "comment": "留言", "comment-placeholder": "新增評論", @@ -501,6 +516,7 @@ "card-end-on": "Ends on", "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", + "setCardColor-title": "Set color", "assigned-by": "Assigned By", "requested-by": "Requested By", "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", @@ -579,6 +595,7 @@ "r-label": "label", "r-member": "member", "r-remove-all": "Remove all members from the card", + "r-set-color": "Set color to", "r-checklist": "checklist", "r-check-all": "Check all", "r-uncheck-all": "Uncheck all", From 2082480ddd91343250d0fc8752750bd8801b45da Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 22 Jan 2019 13:38:08 +0100 Subject: [PATCH 22/29] Set the card color with the color picker When triggered from the hamburger --- client/components/cards/cardDetails.jade | 35 ++++--------------- client/components/cards/cardDetails.js | 44 ++++++++++++++++++------ i18n/en.i18n.json | 1 + 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 4838d82c2..c1e771cb1 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -340,34 +340,13 @@ template(name="setCardColorPopup") p.quiet span.clearfix label {{_ "select-color"}} - select.js-select-colors - option(value="white") {{{_'color-white'}}} - option(value="green") {{{_'color-green'}}} - option(value="yellow") {{{_'color-yellow'}}} - option(value="orange") {{{_'color-orange'}}} - option(value="red") {{{_'color-red'}}} - option(value="purple") {{{_'color-purple'}}} - option(value="blue") {{{_'color-blue'}}} - option(value="sky") {{{_'color-sky'}}} - option(value="lime") {{{_'color-lime'}}} - option(value="pink") {{{_'color-pink'}}} - option(value="black") {{{_'color-black'}}} - option(value="silver") {{{_'color-silver'}}} - option(value="peachpuff") {{{_'color-peachpuff'}}} - option(value="crimson") {{{_'color-crimson'}}} - option(value="plum") {{{_'color-plum'}}} - option(value="darkgreen") {{{_'color-darkgreen'}}} - option(value="slateblue") {{{_'color-slateblue'}}} - option(value="magenta") {{{_'color-magenta'}}} - option(value="gold") {{{_'color-gold'}}} - option(value="navy") {{{_'color-navy'}}} - option(value="gray") {{{_'color-gray'}}} - option(value="saddlebrown") {{{_'color-saddlebrown'}}} - option(value="paleturquoise") {{{_'color-paleturquoise'}}} - option(value="mistyrose") {{{_'color-mistyrose'}}} - option(value="indigo") {{{_'color-indigo'}}} - .edit-controls.clearfix - button.primary.confirm.js-submit {{_ 'save'}} + form.edit-label + .palette-colors: each colors + span.card-label.palette-color.js-palette-color(class="card-details-{{color}}") + if(isSelected color) + i.fa.fa-check + button.primary.confirm.js-submit {{_ 'save'}} + button.js-remove-color.negate.wide.right {{_ 'unset-color'}} template(name="cardDeletePopup") p {{_ "card-delete-pop"}} diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 7883f1290..c936f0f49 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -1,6 +1,11 @@ const subManager = new SubsManager(); const { calculateIndexData, enableClickOnTouch } = Utils; +let cardColors; +Meteor.startup(() => { + cardColors = Cards.simpleSchema()._schema['color'].allowedValues; +}); + BlazeComponent.extendComponent({ mixins() { return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar]; @@ -585,17 +590,36 @@ Template.copyChecklistToManyCardsPopup.events({ }, }); -Template.setCardColorPopup.events({ - 'click .js-submit' () { - // XXX We should *not* get the currentCard from the global state, but - // instead from a “component” state. - const card = Cards.findOne(Session.get('currentCard')); - const colorSelect = $('.js-select-colors')[0]; - newColor = colorSelect.options[colorSelect.selectedIndex].value; - card.setColor(newColor); - Popup.close(); +BlazeComponent.extendComponent({ + onCreated() { + this.currentCard = this.currentData(); + this.currentColor = new ReactiveVar(this.currentCard.color); }, -}); + + colors() { + return cardColors.map((color) => ({ color, name: '' })); + }, + + isSelected(color) { + return this.currentColor.get() === color; + }, + + events() { + return [{ + 'click .js-palette-color'() { + this.currentColor.set(this.currentData().color); + }, + 'click .js-submit' () { + this.currentCard.setColor(this.currentColor.get()); + Popup.close(); + }, + 'click .js-remove-color'() { + this.currentCard.setColor(null); + Popup.close(); + }, + }]; + }, +}).register('setCardColorPopup'); BlazeComponent.extendComponent({ onCreated() { diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index a4338363c..7097af7da 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", From 44e4df2492b95226f1297e7f556d61b1afaab714 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 22 Jan 2019 15:22:31 +0200 Subject: [PATCH 23/29] - Translate and add colors to IFTTT Rules dropdown. Thanks to xet7 ! --- client/components/cards/cardDetails.js | 2 +- .../rules/triggers/cardTriggers.jade | 32 +++++++++---------- .../components/rules/triggers/cardTriggers.js | 3 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index c936f0f49..cc04b8307 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -3,7 +3,7 @@ const { calculateIndexData, enableClickOnTouch } = Utils; let cardColors; Meteor.startup(() => { - cardColors = Cards.simpleSchema()._schema['color'].allowedValues; + cardColors = Cards.simpleSchema()._schema.color.allowedValues; }); BlazeComponent.extendComponent({ diff --git a/client/components/rules/triggers/cardTriggers.jade b/client/components/rules/triggers/cardTriggers.jade index 4492502bb..72c4b8db9 100644 --- a/client/components/rules/triggers/cardTriggers.jade +++ b/client/components/rules/triggers/cardTriggers.jade @@ -1,13 +1,13 @@ template(name="cardTriggers") div.trigger-item div.trigger-content - div.trigger-text + div.trigger-text | {{_'r-when-a-label-is'}} div.trigger-dropdown select(id="label-action") option(value="added") {{_'r-added-to'}} option(value="removed") {{_'r-removed-from'}} - div.trigger-text + div.trigger-text | {{_'r-a-card'}} div.trigger-button.trigger-button-person.js-show-user-field i.fa.fa-user @@ -21,20 +21,20 @@ template(name="cardTriggers") div.trigger-item div.trigger-content - div.trigger-text + div.trigger-text | {{_'r-when-the-label-is'}} div.trigger-dropdown select(id="spec-label") each labels - option(value="#{_id}") - = name - div.trigger-text + option(value="#{_id}" style="background-color: #{name}") + = translatedname + div.trigger-text | {{_'r-is'}} div.trigger-dropdown select(id="spec-label-action") option(value="added") {{_'r-added-to'}} option(value="removed") {{_'r-removed-from'}} - div.trigger-text + div.trigger-text | {{_'r-a-card'}} div.trigger-button.trigger-button-person.js-show-user-field i.fa.fa-user @@ -48,13 +48,13 @@ template(name="cardTriggers") div.trigger-item div.trigger-content - div.trigger-text + div.trigger-text | {{_'r-when-a-member'}} div.trigger-dropdown select(id="gen-member-action") option(value="added") {{_'r-added-to'}} option(value="removed") {{_'r-removed-from'}} - div.trigger-text + div.trigger-text | {{_'r-a-card'}} div.trigger-button.trigger-button-person.js-show-user-field i.fa.fa-user @@ -69,17 +69,17 @@ template(name="cardTriggers") div.trigger-item div.trigger-content - div.trigger-text + div.trigger-text | {{_'r-when-the-member'}} div.trigger-dropdown - input(id="spec-member",type=text,placeholder="{{_'r-name'}}") - div.trigger-text + input(id="spec-member",type=text,placeholder="{{_'r-name'}}") + div.trigger-text | {{_'r-is'}} div.trigger-dropdown select(id="spec-member-action") option(value="added") {{_'r-added-to'}} option(value="removed") {{_'r-removed-from'}} - div.trigger-text + div.trigger-text | {{_'r-a-card'}} div.trigger-button.trigger-button-person.js-show-user-field i.fa.fa-user @@ -93,15 +93,15 @@ template(name="cardTriggers") div.trigger-item div.trigger-content - div.trigger-text + div.trigger-text | {{_'r-when-a-attach'}} - div.trigger-text + div.trigger-text | {{_'r-is'}} div.trigger-dropdown select(id="attach-action") option(value="added") {{_'r-added-to'}} option(value="removed") {{_'r-removed-from'}} - div.trigger-text + div.trigger-text | {{_'r-a-card'}} div.trigger-button.trigger-button-person.js-show-user-field i.fa.fa-user diff --git a/client/components/rules/triggers/cardTriggers.js b/client/components/rules/triggers/cardTriggers.js index 2303a85bd..ca282fa7b 100644 --- a/client/components/rules/triggers/cardTriggers.js +++ b/client/components/rules/triggers/cardTriggers.js @@ -6,7 +6,8 @@ BlazeComponent.extendComponent({ const labels = Boards.findOne(Session.get('currentBoard')).labels; for (let i = 0; i < labels.length; i++) { if (labels[i].name === '' || labels[i].name === undefined) { - labels[i].name = labels[i].color.toUpperCase(); + labels[i].name = labels[i].color; + labels[i].translatedname = `${TAPi18n.__(`color-${ labels[i].color}`)}`; } } return labels; From 26d7ba72aa85bd217fdb0e0e9ba16cdbdb9b4035 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Fri, 27 Jul 2018 07:12:29 +0200 Subject: [PATCH 24/29] api: export board: allow authentication through generic authentication This allows to retrieve the full export of the board from the API. When the board is big, retrieving individual cards is heavy for both the server and the number of requests. Allowing the API to directly call on export and then treat the data makes the whole process smoother. --- models/export.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/models/export.js b/models/export.js index fa4894d9f..50971c881 100644 --- a/models/export.js +++ b/models/export.js @@ -10,7 +10,7 @@ if (Meteor.isServer) { * @operation export * @tag Boards * - * @summary This route is used to export the board **FROM THE APPLICATION**. + * @summary This route is used to export the board. * * @description If user is already logged-in, pass loginToken as param * "authToken": '/api/boards/:boardId/export?authToken=:token' @@ -24,14 +24,16 @@ if (Meteor.isServer) { JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) { const boardId = req.params.boardId; let user = null; - // todo XXX for real API, first look for token in Authentication: header - // then fallback to parameter + const loginToken = req.query.authToken; if (loginToken) { const hashToken = Accounts._hashLoginToken(loginToken); user = Meteor.users.findOne({ 'services.resume.loginTokens.hashedToken': hashToken, }); + } else { + Authentication.checkUserId(req.userId); + user = Users.findOne({ _id: req.userId, isAdmin: true }); } const exporter = new Exporter(boardId); From 7261ccdc9074b443adb36e9365ba512eb74ffdfe Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 22 Jan 2019 16:32:43 +0200 Subject: [PATCH 25/29] Update translations. --- i18n/ar.i18n.json | 1 + i18n/bg.i18n.json | 1 + i18n/br.i18n.json | 1 + i18n/ca.i18n.json | 31 ++++++++++++++++--------------- i18n/cs.i18n.json | 1 + i18n/da.i18n.json | 1 + i18n/de.i18n.json | 1 + i18n/el.i18n.json | 1 + i18n/en-GB.i18n.json | 1 + i18n/eo.i18n.json | 1 + i18n/es-AR.i18n.json | 1 + i18n/es.i18n.json | 35 ++++++++++++++++++----------------- i18n/eu.i18n.json | 1 + i18n/fa.i18n.json | 1 + i18n/fi.i18n.json | 1 + i18n/fr.i18n.json | 31 ++++++++++++++++--------------- i18n/gl.i18n.json | 1 + i18n/he.i18n.json | 31 ++++++++++++++++--------------- i18n/hi.i18n.json | 1 + i18n/hu.i18n.json | 1 + i18n/hy.i18n.json | 1 + i18n/id.i18n.json | 1 + i18n/ig.i18n.json | 1 + i18n/it.i18n.json | 1 + i18n/ja.i18n.json | 1 + i18n/ka.i18n.json | 1 + i18n/km.i18n.json | 1 + i18n/ko.i18n.json | 1 + i18n/lv.i18n.json | 1 + i18n/mn.i18n.json | 1 + i18n/nb.i18n.json | 1 + i18n/nl.i18n.json | 1 + i18n/pl.i18n.json | 1 + i18n/pt-BR.i18n.json | 35 ++++++++++++++++++----------------- i18n/pt.i18n.json | 1 + i18n/ro.i18n.json | 1 + i18n/ru.i18n.json | 1 + i18n/sr.i18n.json | 1 + i18n/sv.i18n.json | 1 + i18n/sw.i18n.json | 1 + i18n/ta.i18n.json | 1 + i18n/th.i18n.json | 1 + i18n/tr.i18n.json | 1 + i18n/uk.i18n.json | 1 + i18n/vi.i18n.json | 1 + i18n/zh-CN.i18n.json | 1 + i18n/zh-TW.i18n.json | 1 + 47 files changed, 126 insertions(+), 79 deletions(-) diff --git a/i18n/ar.i18n.json b/i18n/ar.i18n.json index f490691fc..53508e424 100644 --- a/i18n/ar.i18n.json +++ b/i18n/ar.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "تعليق", "comment-placeholder": "أكتب تعليق", "comment-only": "التعليق فقط", diff --git a/i18n/bg.i18n.json b/i18n/bg.i18n.json index 25cf67a52..c374a4300 100644 --- a/i18n/bg.i18n.json +++ b/i18n/bg.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "жълто", + "unset-color": "Unset", "comment": "Коментирай", "comment-placeholder": "Напиши коментар", "comment-only": "Само коментар", diff --git a/i18n/br.i18n.json b/i18n/br.i18n.json index d17e232cf..899e2c00d 100644 --- a/i18n/br.i18n.json +++ b/i18n/br.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "melen", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/ca.i18n.json b/i18n/ca.i18n.json index f742196b8..09f3aee1d 100644 --- a/i18n/ca.i18n.json +++ b/i18n/ca.i18n.json @@ -169,29 +169,30 @@ "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", "color-black": "negre", "color-blue": "blau", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", + "color-crimson": "carmesí", + "color-darkgreen": "verd fosc", + "color-gold": "daurat", + "color-gray": "gris", "color-green": "verd", - "color-indigo": "indigo", + "color-indigo": "índigo", "color-lime": "llima", "color-magenta": "magenta", "color-mistyrose": "mistyrose", - "color-navy": "navy", + "color-navy": "marina", "color-orange": "taronja", "color-paleturquoise": "paleturquoise", "color-peachpuff": "peachpuff", "color-pink": "rosa", - "color-plum": "plum", + "color-plum": "pruna", "color-purple": "púrpura", "color-red": "vermell", "color-saddlebrown": "saddlebrown", - "color-silver": "silver", + "color-silver": "plata", "color-sky": "cel", "color-slateblue": "slateblue", - "color-white": "white", + "color-white": "blanc", "color-yellow": "groc", + "unset-color": "Unset", "comment": "Comentari", "comment-placeholder": "Escriu un comentari", "comment-only": "Només comentaris", @@ -286,7 +287,7 @@ "filter-on": "Filtra per", "filter-on-desc": "Estau filtrant fitxes en aquest tauler. Feu clic aquí per editar el filtre.", "filter-to-selection": "Filtra selecció", - "advanced-filter-label": "Advanced Filter", + "advanced-filter-label": "Filtre avançat", "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", "fullname": "Nom complet", "header-logo-title": "Torna a la teva pàgina de taulers", @@ -311,7 +312,7 @@ "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", "import-show-user-mapping": "Revisa l'assignació de membres", "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", + "importMapMembersAddPopup-title": "Selecciona un usuari", "info": "Versió", "initials": "Inicials", "invalid-date": "Data invàlida", @@ -330,7 +331,7 @@ "leave-board-pop": "De debò voleu abandonar __boardTitle__? Se us eliminarà de totes les fitxes d'aquest tauler.", "leaveBoardPopup-title": "Abandonar Tauler?", "link-card": "Enllaç a aquesta fitxa", - "list-archive-cards": "Move all cards in this list to Archive", + "list-archive-cards": "Moure totes les fitxes en aquesta llista al Arxiu", "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", "list-move-cards": "Mou totes les fitxes d'aquesta llista", "list-select-cards": "Selecciona totes les fitxes d'aquesta llista", @@ -360,8 +361,8 @@ "muted-info": "No seràs notificat dels canvis en aquest tauler", "my-boards": "Els meus taulers", "name": "Nom", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", + "no-archived-cards": "No hi ha fitxes a l'arxiu.", + "no-archived-lists": "No hi ha llistes al arxiu.", "no-archived-swimlanes": "No swimlanes in Archive.", "no-results": "Sense resultats", "normal": "Normal", @@ -433,7 +434,7 @@ "title": "Títol", "tracking": "En seguiment", "tracking-info": "Seràs notificat per cada canvi a aquelles fitxes de les que n'eres creador o membre", - "type": "Type", + "type": "Tipus", "unassign-member": "Desassignar membre", "unsaved-description": "Tens una descripció sense desar.", "unwatch": "Suprimeix observació", diff --git a/i18n/cs.i18n.json b/i18n/cs.i18n.json index 5a93dd31f..7a6a7ba0d 100644 --- a/i18n/cs.i18n.json +++ b/i18n/cs.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "žlutá", + "unset-color": "Unset", "comment": "Komentář", "comment-placeholder": "Text komentáře", "comment-only": "Pouze komentáře", diff --git a/i18n/da.i18n.json b/i18n/da.i18n.json index f080d2e03..7ba182e00 100644 --- a/i18n/da.i18n.json +++ b/i18n/da.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/de.i18n.json b/i18n/de.i18n.json index da6574c7b..0c155e55a 100644 --- a/i18n/de.i18n.json +++ b/i18n/de.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "gelb", + "unset-color": "Unset", "comment": "Kommentar", "comment-placeholder": "Kommentar schreiben", "comment-only": "Nur Kommentare", diff --git a/i18n/el.i18n.json b/i18n/el.i18n.json index 74dfe693c..84197b5df 100644 --- a/i18n/el.i18n.json +++ b/i18n/el.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "κίτρινο", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/en-GB.i18n.json b/i18n/en-GB.i18n.json index 36f86e7b8..0f38a3dae 100644 --- a/i18n/en-GB.i18n.json +++ b/i18n/en-GB.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/eo.i18n.json b/i18n/eo.i18n.json index c1beb5471..236aec5aa 100644 --- a/i18n/eo.i18n.json +++ b/i18n/eo.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "flava", + "unset-color": "Unset", "comment": "Komento", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/es-AR.i18n.json b/i18n/es-AR.i18n.json index 42a12e44a..e7d7f132b 100644 --- a/i18n/es-AR.i18n.json +++ b/i18n/es-AR.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "amarillo", + "unset-color": "Unset", "comment": "Comentario", "comment-placeholder": "Comentar", "comment-only": "Comentar solamente", diff --git a/i18n/es.i18n.json b/i18n/es.i18n.json index 02ff9944d..ac393ae31 100644 --- a/i18n/es.i18n.json +++ b/i18n/es.i18n.json @@ -169,29 +169,30 @@ "close-board-pop": "Podrás restaurar el tablero haciendo clic en el botón \"Archivo\" del encabezado de la pantalla inicial.", "color-black": "negra", "color-blue": "azul", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", + "color-crimson": "carmesí", + "color-darkgreen": "verde oscuro", + "color-gold": "oro", + "color-gray": "gris", "color-green": "verde", - "color-indigo": "indigo", + "color-indigo": "añil", "color-lime": "lima", "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", + "color-mistyrose": "rosa claro", + "color-navy": "azul marino", "color-orange": "naranja", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", + "color-paleturquoise": "turquesa", + "color-peachpuff": "melocotón", "color-pink": "rosa", - "color-plum": "plum", + "color-plum": "púrpura", "color-purple": "violeta", "color-red": "roja", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", + "color-saddlebrown": "marrón", + "color-silver": "plata", "color-sky": "celeste", - "color-slateblue": "slateblue", - "color-white": "white", + "color-slateblue": "azul", + "color-white": "blanco", "color-yellow": "amarilla", + "unset-color": "Unset", "comment": "Comentar", "comment-placeholder": "Escribir comentario", "comment-only": "Sólo comentarios", @@ -495,7 +496,7 @@ "OS_Totalmem": "Memoria Total del sistema", "OS_Type": "Tipo de sistema", "OS_Uptime": "Tiempo activo del sistema", - "days": "days", + "days": "días", "hours": "horas", "minutes": "minutos", "seconds": "segundos", @@ -516,7 +517,7 @@ "card-end-on": "Finalizado el", "editCardReceivedDatePopup-title": "Cambiar la fecha de recepción", "editCardEndDatePopup-title": "Cambiar la fecha de finalización", - "setCardColor-title": "Set color", + "setCardColor-title": "Cambiar color", "assigned-by": "Asignado por", "requested-by": "Solicitado por", "board-delete-notice": "Se eliminarán todas las listas, tarjetas y acciones asociadas a este tablero. Esta acción no puede deshacerse.", @@ -595,7 +596,7 @@ "r-label": "etiqueta", "r-member": "miembro", "r-remove-all": "Eliminar todos los miembros de la tarjeta", - "r-set-color": "Set color to", + "r-set-color": "Cambiar color a", "r-checklist": "lista de verificación", "r-check-all": "Marcar todo", "r-uncheck-all": "Desmarcar todo", diff --git a/i18n/eu.i18n.json b/i18n/eu.i18n.json index 364b3d444..e16ee3a1e 100644 --- a/i18n/eu.i18n.json +++ b/i18n/eu.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "horia", + "unset-color": "Unset", "comment": "Iruzkina", "comment-placeholder": "Idatzi iruzkin bat", "comment-only": "Iruzkinak besterik ez", diff --git a/i18n/fa.i18n.json b/i18n/fa.i18n.json index 11aea12c7..fe43bf9ad 100644 --- a/i18n/fa.i18n.json +++ b/i18n/fa.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "زرد", + "unset-color": "Unset", "comment": "نظر", "comment-placeholder": "درج نظر", "comment-only": "فقط نظر", diff --git a/i18n/fi.i18n.json b/i18n/fi.i18n.json index aa1a649c2..5f3db02d8 100644 --- a/i18n/fi.i18n.json +++ b/i18n/fi.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "liuskekivi sininen", "color-white": "valkoinen", "color-yellow": "keltainen", + "unset-color": "Peru väri", "comment": "Kommentti", "comment-placeholder": "Kirjoita kommentti", "comment-only": "Vain kommentointi", diff --git a/i18n/fr.i18n.json b/i18n/fr.i18n.json index 919dad3e3..040e34f83 100644 --- a/i18n/fr.i18n.json +++ b/i18n/fr.i18n.json @@ -169,29 +169,30 @@ "close-board-pop": "Vous pouvez restaurer le tableau en cliquant sur le bouton « Archives » depuis le menu en entête.", "color-black": "noir", "color-blue": "bleu", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", + "color-crimson": "rouge cramoisi", + "color-darkgreen": "vert foncé", + "color-gold": "or", + "color-gray": "gris", "color-green": "vert", "color-indigo": "indigo", "color-lime": "citron vert", "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", + "color-mistyrose": "rose brumeux", + "color-navy": "bleu marin", "color-orange": "orange", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", + "color-paleturquoise": "azurin", + "color-peachpuff": "beige pêche", "color-pink": "rose", - "color-plum": "plum", + "color-plum": "prune", "color-purple": "violet", "color-red": "rouge", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", + "color-saddlebrown": "brun cuir", + "color-silver": "argent", "color-sky": "ciel", - "color-slateblue": "slateblue", - "color-white": "white", + "color-slateblue": "bleu ardoise", + "color-white": "blanc", "color-yellow": "jaune", + "unset-color": "Unset", "comment": "Commenter", "comment-placeholder": "Écrire un commentaire", "comment-only": "Commentaire uniquement", @@ -516,7 +517,7 @@ "card-end-on": "Se termine le", "editCardReceivedDatePopup-title": "Modifier la date de réception", "editCardEndDatePopup-title": "Modifier la date de fin", - "setCardColor-title": "Set color", + "setCardColor-title": "Définir la couleur", "assigned-by": "Assigné par", "requested-by": "Demandé par", "board-delete-notice": "La suppression est définitive. Vous perdrez toutes les listes, cartes et actions associées à ce tableau.", @@ -595,7 +596,7 @@ "r-label": "étiquette", "r-member": "membre", "r-remove-all": "Supprimer tous les membres de la carte", - "r-set-color": "Set color to", + "r-set-color": "Définir la couleur à", "r-checklist": "checklist", "r-check-all": "Tout cocher", "r-uncheck-all": "Tout décocher", diff --git a/i18n/gl.i18n.json b/i18n/gl.i18n.json index 518b6fd6f..de4208cad 100644 --- a/i18n/gl.i18n.json +++ b/i18n/gl.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "amarelo", + "unset-color": "Unset", "comment": "Comentario", "comment-placeholder": "Escribir un comentario", "comment-only": "Comment only", diff --git a/i18n/he.i18n.json b/i18n/he.i18n.json index 9c837e9c0..afa54c076 100644 --- a/i18n/he.i18n.json +++ b/i18n/he.i18n.json @@ -169,29 +169,30 @@ "close-board-pop": "ניתן לשחזר את הלוח בלחיצה על כפתור „ארכיונים“ מהכותרת העליונה.", "color-black": "שחור", "color-blue": "כחול", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", + "color-crimson": "שני", + "color-darkgreen": "ירוק כהה", + "color-gold": "זהב", + "color-gray": "אפור", "color-green": "ירוק", - "color-indigo": "indigo", + "color-indigo": "אינדיגו", "color-lime": "ליים", - "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", + "color-magenta": "ארגמן", + "color-mistyrose": "ורד", + "color-navy": "כחול כהה", "color-orange": "כתום", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", + "color-paleturquoise": "טורקיז חיוור", + "color-peachpuff": "נשיפת אפרסק", "color-pink": "ורוד", "color-plum": "plum", "color-purple": "סגול", "color-red": "אדום", "color-saddlebrown": "saddlebrown", - "color-silver": "silver", + "color-silver": "כסף", "color-sky": "תכלת", "color-slateblue": "slateblue", - "color-white": "white", + "color-white": "לבן", "color-yellow": "צהוב", + "unset-color": "Unset", "comment": "לפרסם", "comment-placeholder": "כתיבת הערה", "comment-only": "תגובה בלבד", @@ -495,7 +496,7 @@ "OS_Totalmem": "סך כל הזיכרון (RAM)", "OS_Type": "סוג מערכת ההפעלה", "OS_Uptime": "זמן שעבר מאז האתחול האחרון", - "days": "days", + "days": "ימים", "hours": "שעות", "minutes": "דקות", "seconds": "שניות", @@ -516,7 +517,7 @@ "card-end-on": "מועד הסיום", "editCardReceivedDatePopup-title": "החלפת מועד הקבלה", "editCardEndDatePopup-title": "החלפת מועד הסיום", - "setCardColor-title": "Set color", + "setCardColor-title": "הגדרת צבע", "assigned-by": "הוקצה על ידי", "requested-by": "התבקש על ידי", "board-delete-notice": "מחיקה היא לצמיתות. כל הרשימות, הכרטיבים והפעולות שקשורים בלוח הזה ילכו לאיבוד.", @@ -595,7 +596,7 @@ "r-label": "תווית", "r-member": "חבר", "r-remove-all": "הסרת כל החברים מהכרטיס", - "r-set-color": "Set color to", + "r-set-color": "הגדרת צבע לכדי", "r-checklist": "רשימת משימות", "r-check-all": "לסמן הכול", "r-uncheck-all": "לבטל את הסימון", diff --git a/i18n/hi.i18n.json b/i18n/hi.i18n.json index ca7d502dc..d1d623d58 100644 --- a/i18n/hi.i18n.json +++ b/i18n/hi.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "पीला", + "unset-color": "Unset", "comment": "टिप्पणी", "comment-placeholder": "टिप्पणी लिखें", "comment-only": "केवल टिप्पणी करें", diff --git a/i18n/hu.i18n.json b/i18n/hu.i18n.json index 5a02ae64d..b960968cc 100644 --- a/i18n/hu.i18n.json +++ b/i18n/hu.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "sárga", + "unset-color": "Unset", "comment": "Megjegyzés", "comment-placeholder": "Megjegyzés írása", "comment-only": "Csak megjegyzés", diff --git a/i18n/hy.i18n.json b/i18n/hy.i18n.json index 601503e05..5c6639bfa 100644 --- a/i18n/hy.i18n.json +++ b/i18n/hy.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/id.i18n.json b/i18n/id.i18n.json index a9496d631..06d055736 100644 --- a/i18n/id.i18n.json +++ b/i18n/id.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "kuning", + "unset-color": "Unset", "comment": "Komentar", "comment-placeholder": "Write Comment", "comment-only": "Hanya komentar", diff --git a/i18n/ig.i18n.json b/i18n/ig.i18n.json index b55a7e00a..122f99baf 100644 --- a/i18n/ig.i18n.json +++ b/i18n/ig.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/it.i18n.json b/i18n/it.i18n.json index fed71957e..fabc94b16 100644 --- a/i18n/it.i18n.json +++ b/i18n/it.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "giallo", + "unset-color": "Unset", "comment": "Commento", "comment-placeholder": "Scrivi Commento", "comment-only": "Solo commenti", diff --git a/i18n/ja.i18n.json b/i18n/ja.i18n.json index 3a4828948..8b6d6a3a6 100644 --- a/i18n/ja.i18n.json +++ b/i18n/ja.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "黄", + "unset-color": "Unset", "comment": "コメント", "comment-placeholder": "コメントを書く", "comment-only": "コメントのみ", diff --git a/i18n/ka.i18n.json b/i18n/ka.i18n.json index c4fe85c86..44bc69599 100644 --- a/i18n/ka.i18n.json +++ b/i18n/ka.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "ყვითელი", + "unset-color": "Unset", "comment": "კომენტარი", "comment-placeholder": "დაწერეთ კომენტარი", "comment-only": "მხოლოდ კომენტარები", diff --git a/i18n/km.i18n.json b/i18n/km.i18n.json index 71566be7a..009d0ea42 100644 --- a/i18n/km.i18n.json +++ b/i18n/km.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/ko.i18n.json b/i18n/ko.i18n.json index 71dd10926..30ffb980f 100644 --- a/i18n/ko.i18n.json +++ b/i18n/ko.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "옐로우", + "unset-color": "Unset", "comment": "댓글", "comment-placeholder": "댓글 입력", "comment-only": "댓글만 입력 가능", diff --git a/i18n/lv.i18n.json b/i18n/lv.i18n.json index 7edcac0a3..778c84f52 100644 --- a/i18n/lv.i18n.json +++ b/i18n/lv.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/mn.i18n.json b/i18n/mn.i18n.json index 2309fd5da..656124f0b 100644 --- a/i18n/mn.i18n.json +++ b/i18n/mn.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/nb.i18n.json b/i18n/nb.i18n.json index a67d4cb24..edaf85f52 100644 --- a/i18n/nb.i18n.json +++ b/i18n/nb.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/nl.i18n.json b/i18n/nl.i18n.json index e7509c8ee..3df4f62a7 100644 --- a/i18n/nl.i18n.json +++ b/i18n/nl.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "Geel", + "unset-color": "Unset", "comment": "Reageer", "comment-placeholder": "Schrijf reactie", "comment-only": "Alleen reageren", diff --git a/i18n/pl.i18n.json b/i18n/pl.i18n.json index bdc28d328..534c62ad3 100644 --- a/i18n/pl.i18n.json +++ b/i18n/pl.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "żółty", + "unset-color": "Unset", "comment": "Komentarz", "comment-placeholder": "Dodaj komentarz", "comment-only": "Tylko komentowanie", diff --git a/i18n/pt-BR.i18n.json b/i18n/pt-BR.i18n.json index bfa3903c5..4def56491 100644 --- a/i18n/pt-BR.i18n.json +++ b/i18n/pt-BR.i18n.json @@ -169,29 +169,30 @@ "close-board-pop": "Você será capaz de restaurar o quadro clicando no botão “Arquivo-morto” a partir do cabeçalho do Início.", "color-black": "preto", "color-blue": "azul", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", + "color-crimson": "carmesim", + "color-darkgreen": "verde escuro", + "color-gold": "dourado", + "color-gray": "cinza", "color-green": "verde", - "color-indigo": "indigo", + "color-indigo": "azul", "color-lime": "verde limão", "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", + "color-mistyrose": "rosa claro", + "color-navy": "azul marinho", "color-orange": "laranja", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", + "color-paleturquoise": "azul ciano", + "color-peachpuff": "pêssego", "color-pink": "cor-de-rosa", - "color-plum": "plum", + "color-plum": "ameixa", "color-purple": "roxo", "color-red": "vermelho", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", + "color-saddlebrown": "marrom", + "color-silver": "prateado", "color-sky": "azul-celeste", - "color-slateblue": "slateblue", - "color-white": "white", + "color-slateblue": "azul ardósia", + "color-white": "branco", "color-yellow": "amarelo", + "unset-color": "Unset", "comment": "Comentário", "comment-placeholder": "Escrever Comentário", "comment-only": "Somente comentários", @@ -495,7 +496,7 @@ "OS_Totalmem": "Memória Total do SO", "OS_Type": "Tipo do SO", "OS_Uptime": "Disponibilidade do SO", - "days": "days", + "days": "dias", "hours": "horas", "minutes": "minutos", "seconds": "segundos", @@ -516,7 +517,7 @@ "card-end-on": "Termina em", "editCardReceivedDatePopup-title": "Modificar data de recebimento", "editCardEndDatePopup-title": "Mudar data de fim", - "setCardColor-title": "Set color", + "setCardColor-title": "Definir cor", "assigned-by": "Atribuído por", "requested-by": "Solicitado por", "board-delete-notice": "Excluir é permanente. Você perderá todas as listas, cartões e ações associados nesse quadro.", @@ -595,7 +596,7 @@ "r-label": "etiqueta", "r-member": "membro", "r-remove-all": "Remover todos os membros do cartão", - "r-set-color": "Set color to", + "r-set-color": "Definir cor para", "r-checklist": "lista de verificação", "r-check-all": "Marcar todos", "r-uncheck-all": "Desmarcar todos", diff --git a/i18n/pt.i18n.json b/i18n/pt.i18n.json index 4fc0889a7..4a960d003 100644 --- a/i18n/pt.i18n.json +++ b/i18n/pt.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comentário", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/ro.i18n.json b/i18n/ro.i18n.json index ad64b79ab..25fdc9373 100644 --- a/i18n/ro.i18n.json +++ b/i18n/ro.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/ru.i18n.json b/i18n/ru.i18n.json index 24e582d61..dd8114bab 100644 --- a/i18n/ru.i18n.json +++ b/i18n/ru.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "желтый", + "unset-color": "Unset", "comment": "Добавить комментарий", "comment-placeholder": "Написать комментарий", "comment-only": "Только комментирование", diff --git a/i18n/sr.i18n.json b/i18n/sr.i18n.json index f723c532d..fe8f43fc6 100644 --- a/i18n/sr.i18n.json +++ b/i18n/sr.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/sv.i18n.json b/i18n/sv.i18n.json index c3cb5dcc1..e134d9b1e 100644 --- a/i18n/sv.i18n.json +++ b/i18n/sv.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "gul", + "unset-color": "Unset", "comment": "Kommentera", "comment-placeholder": "Skriv kommentar", "comment-only": "Kommentera endast", diff --git a/i18n/sw.i18n.json b/i18n/sw.i18n.json index 426ed6341..9bc667e84 100644 --- a/i18n/sw.i18n.json +++ b/i18n/sw.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Changia", "comment-placeholder": "Andika changio", "comment-only": "Changia pekee", diff --git a/i18n/ta.i18n.json b/i18n/ta.i18n.json index c1847fc1e..159df52d0 100644 --- a/i18n/ta.i18n.json +++ b/i18n/ta.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/th.i18n.json b/i18n/th.i18n.json index f0c134721..6fe3d52fb 100644 --- a/i18n/th.i18n.json +++ b/i18n/th.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "เหลือง", + "unset-color": "Unset", "comment": "คอมเม็นต์", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/tr.i18n.json b/i18n/tr.i18n.json index 8d8acd4de..ba05145e5 100644 --- a/i18n/tr.i18n.json +++ b/i18n/tr.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "sarı", + "unset-color": "Unset", "comment": "Yorum", "comment-placeholder": "Yorum Yaz", "comment-only": "Sadece yorum", diff --git a/i18n/uk.i18n.json b/i18n/uk.i18n.json index 5b6317cbe..820524a39 100644 --- a/i18n/uk.i18n.json +++ b/i18n/uk.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "жовтий", + "unset-color": "Unset", "comment": "Коментар", "comment-placeholder": "Написати коментар", "comment-only": "Comment only", diff --git a/i18n/vi.i18n.json b/i18n/vi.i18n.json index ef4b6b8c9..e9b454c18 100644 --- a/i18n/vi.i18n.json +++ b/i18n/vi.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "yellow", + "unset-color": "Unset", "comment": "Comment", "comment-placeholder": "Write Comment", "comment-only": "Comment only", diff --git a/i18n/zh-CN.i18n.json b/i18n/zh-CN.i18n.json index 1bae6876f..8e0b87f93 100644 --- a/i18n/zh-CN.i18n.json +++ b/i18n/zh-CN.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "黄色", + "unset-color": "Unset", "comment": "评论", "comment-placeholder": "添加评论", "comment-only": "仅能评论", diff --git a/i18n/zh-TW.i18n.json b/i18n/zh-TW.i18n.json index 43eff7def..e578993ac 100644 --- a/i18n/zh-TW.i18n.json +++ b/i18n/zh-TW.i18n.json @@ -192,6 +192,7 @@ "color-slateblue": "slateblue", "color-white": "white", "color-yellow": "黃色", + "unset-color": "Unset", "comment": "留言", "comment-placeholder": "新增評論", "comment-only": "只可以發表評論", From ba9f0ca6720ff1bf2e775ae8b71ff82a23bb37e4 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Tue, 22 Jan 2019 15:32:40 +0100 Subject: [PATCH 26/29] Fix: Translate and add colors to IFTTT Rules dropdown." This fixes commit 44e4df2492b95226f1297e7f556d61b1afaab714. When the label has a name, not setting `translatedname` results in a blank item in the IFTTT label trigger. --- client/components/rules/triggers/cardTriggers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/components/rules/triggers/cardTriggers.js b/client/components/rules/triggers/cardTriggers.js index ca282fa7b..82b21d61f 100644 --- a/client/components/rules/triggers/cardTriggers.js +++ b/client/components/rules/triggers/cardTriggers.js @@ -8,6 +8,8 @@ BlazeComponent.extendComponent({ if (labels[i].name === '' || labels[i].name === undefined) { labels[i].name = labels[i].color; labels[i].translatedname = `${TAPi18n.__(`color-${ labels[i].color}`)}`; + } else { + labels[i].translatedname = labels[i].name; } } return labels; From 2b4df7e8c76bb3b48e32fd25105c1ee491472537 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 22 Jan 2019 17:26:48 +0200 Subject: [PATCH 27/29] Update changelog. --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88c018ab1..4c405c76b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,22 @@ # Upcoming Wekan release -This release adds the following new features: +This release adds the following new features with Apache I-CLA, thanks to bentiss: -- [OpenAPI and generating of REST API Docs](https://github.com/wekan/wekan/pull/1965). Thanks to bentiss. +- [Add per card color: Card / Hamburger menu / Set Color](https://github.com/wekan/wekan/pull/2116) with [color picker](https://github.com/wekan/wekan/pull/2117); +- [OpenAPI and generating of REST API Docs](https://github.com/wekan/wekan/pull/1965); +- [Allow to retrieve full export of board from the REST API](https://github.com/wekan/wekan/pull/2118) through generic authentication. + When the board is big, retrieving individual cards is heavy for both the server and the number of requests. + Allowing the API to directly call on export and then treat the data makes the whole process smoother. + +and adds the following new features with Apache I-CLA, thanks to xet7 and bentiss: + +- [Translate and add color names to IFTTT Rules dropdown](https://github.com/wekan/wekan/commit/44e4df2492b95226f1297e7f556d61b1afaab714), thanks to xet7. + [Fix to this feature blank item](https://github.com/wekan/wekan/pull/2119), thanks to bentiss. and adds these updates: - Update translations. Thanks to translators. +- Added missing translation for 'days'. Thanks to Chartman123. and fixes these typos; From 9baed4256aa2d65ee85a9fc9c9815d57d1da0d0f Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 22 Jan 2019 17:28:19 +0200 Subject: [PATCH 28/29] Update translations (he). --- i18n/he.i18n.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/he.i18n.json b/i18n/he.i18n.json index afa54c076..771d36ad2 100644 --- a/i18n/he.i18n.json +++ b/i18n/he.i18n.json @@ -183,7 +183,7 @@ "color-paleturquoise": "טורקיז חיוור", "color-peachpuff": "נשיפת אפרסק", "color-pink": "ורוד", - "color-plum": "plum", + "color-plum": "שזיף", "color-purple": "סגול", "color-red": "אדום", "color-saddlebrown": "saddlebrown", @@ -192,7 +192,7 @@ "color-slateblue": "slateblue", "color-white": "לבן", "color-yellow": "צהוב", - "unset-color": "Unset", + "unset-color": "בטל הגדרה", "comment": "לפרסם", "comment-placeholder": "כתיבת הערה", "comment-only": "תגובה בלבד", From 1b445ad789a41d97b7cf4e16af35f52ae0be694b Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Tue, 22 Jan 2019 17:31:57 +0200 Subject: [PATCH 29/29] v2.02 --- CHANGELOG.md | 2 +- Stackerfile.yml | 2 +- package.json | 2 +- sandstorm-pkgdef.capnp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c405c76b..12b328f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Upcoming Wekan release +# v2.02 2019-01-22 Wekan release This release adds the following new features with Apache I-CLA, thanks to bentiss: diff --git a/Stackerfile.yml b/Stackerfile.yml index df20fe6aa..68a8265d9 100644 --- a/Stackerfile.yml +++ b/Stackerfile.yml @@ -1,5 +1,5 @@ appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928 -appVersion: "v2.01.0" +appVersion: "v2.02.0" files: userUploads: - README.md diff --git a/package.json b/package.json index 2f6974327..cc3ebfe98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wekan", - "version": "v2.01.0", + "version": "v2.02.0", "description": "Open-Source kanban", "private": true, "scripts": { diff --git a/sandstorm-pkgdef.capnp b/sandstorm-pkgdef.capnp index 6b8b10c83..4b0c8ecec 100644 --- a/sandstorm-pkgdef.capnp +++ b/sandstorm-pkgdef.capnp @@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = ( appTitle = (defaultText = "Wekan"), # The name of the app as it is displayed to the user. - appVersion = 203, + appVersion = 204, # Increment this for every release. - appMarketingVersion = (defaultText = "2.01.0~2019-01-06"), + appMarketingVersion = (defaultText = "2.02.0~2019-01-22"), # Human-readable presentation of the app version. minUpgradableAppVersion = 0,