Compare commits

...
117 Commits
Author SHA1 Message Date
Lauri Ojansivu 2e93a975c4 v4.13 2020-06-09 22:41:46 +03:00
Lauri Ojansivu c4df69a05b Update ChangeLog. 2020-06-09 22:35:44 +03:00
Lauri Ojansivu a562ee4ba1 Update ChangeLog. 2020-06-09 22:34:18 +03:00
Lauri Ojansivu 781b384fe9 Update translations. 2020-06-09 22:29:18 +03:00
Lauri Ojansivu b6cc6c440d Update ChangeLog. 2020-06-09 22:27:34 +03:00
Lauri Ojansivu 20da91b774 Merge branch 'marc1006-issue_3164' 2020-06-09 22:24:00 +03:00
Lauri Ojansivu e530077fc5 Merge branch 'issue_3164' of https://github.com/marc1006/wekan into marc1006-issue_3164 2020-06-09 22:19:56 +03:00
Lauri Ojansivu 444fff47dc Update ChangeLog. 2020-06-09 22:16:00 +03:00
Marc Hartmayer 1aa8f502ae Fix condition whether a card is in list
This fixes the issues https://github.com/wekan/wekan/issues/3164,
https://github.com/wekan/wekan/issues/3162, and
https://github.com/wekan/wekan/issues/3163. While at it, remove now useless
comments.
2020-06-09 17:24:17 +02:00
Lauri Ojansivu 2a25318ba8 Merge pull request #3158 from jimmyjones2/patch-1
OpenShift template updates
2020-06-08 21:35:15 +03:00
Lauri Ojansivu 39a8524354 Merge pull request #3154 from marc1006/issue_2970
Copy the labels only if the target board is different
2020-06-08 21:34:56 +03:00
Lauri Ojansivu 573ca73b45 Use Docker Hub image, it did build faster this time. 2020-06-08 21:22:34 +03:00
Lauri Ojansivu 07d1a864d8 v4.12 2020-06-08 20:29:55 +03:00
Lauri Ojansivu ffcd380523 Update ChangeLog. 2020-06-08 20:24:40 +03:00
Lauri Ojansivu 55534b8b2e Fix typos. 2020-06-08 20:23:34 +03:00
Lauri Ojansivu ca23934bde Update ChangeLog. 2020-06-08 20:21:36 +03:00
Lauri Ojansivu 8a622ec7c3 Change to correct dependency version. 2020-06-08 19:20:27 +03:00
Lauri Ojansivu 39519d1cc9 1) Public board default view to Swimlane.
2) When changing Public board view (sets view cookie), also reload page so view is changed immediately.

Thanks to xet7 !
2020-06-08 19:12:17 +03:00
Lauri Ojansivu dcbf92b5b1 Update dependencies. 2020-06-08 17:55:43 +03:00
Lauri Ojansivu 7f6d500cbe Default view Swimlanes part 3: Change dropdown order to Swimlanes/Lists/Calendar.
Thanks to xet7 !
2020-06-08 17:53:03 +03:00
Lauri Ojansivu cb1e91fee8 Update minifier-css.
Thanks to xet7 !
2020-06-08 17:37:20 +03:00
Lauri Ojansivu 96494bacf5 Fix indent part 2.
Thanks to xet7 !
2020-06-08 17:35:25 +03:00
Lauri Ojansivu 99f68f36b0 Fix XSS.
Thanks to xet7 !
2020-06-08 17:34:45 +03:00
Lauri Ojansivu 61e682470c Default view Swimlanes part 2.
Thanks to xet7 !
2020-06-08 17:33:38 +03:00
Lauri Ojansivu 415e94d187 Fix indent.
Thanks to xet7 !
2020-06-08 17:32:17 +03:00
Lauri Ojansivu 6b22f96313 Use markdown in Swimlane titles.
Thanks to xet7 !
2020-06-08 17:30:26 +03:00
Lauri Ojansivu 8c3322f9a9 Change default view to Swimlanes.
Thanks to xet7 !
2020-06-08 17:28:53 +03:00
Lauri Ojansivu f935cf391e Update translations. 2020-06-08 17:26:00 +03:00
Marc Hartmayer fb44df9815 WIP: XSS fixes 2020-06-07 23:22:04 +02:00
Jimmy Jones e127895cf3 OpenShift template updates
* Remove status fields (this is created by Kubernetes at run time)
* The latest MongoDB by [default available with OpenShift is 3.6](https://github.com/openshift/origin/blob/master/examples/image-streams/image-streams-rhel7.json#L334)
* Change MongoDB service name to contain wekan to avoid potentially conflicting with other mongodb instances in the same project
2020-06-07 20:18:17 +01:00
Marc Hartmayer 1f85b25549 WIP: markdown 2020-06-06 11:26:56 +02:00
Marc Hartmayer df9851e2b7 Copy the labels only if the target board is different
This fixes the issues https://github.com/wekan/wekan/issues/2404 and
https://github.com/wekan/wekan/issues/2970 if the target board doesn't differ
from the source board.
2020-06-05 08:54:10 +02:00
Lauri Ojansivu 232bc746f4 Update ChangeLog. 2020-06-04 02:52:18 +03:00
Lauri Ojansivu ea448cd6c3 Update ChangeLog. 2020-06-04 02:49:38 +03:00
Lauri Ojansivu a7d12ddcaa v4.11 2020-06-04 01:41:17 +03:00
Lauri Ojansivu 420e9b23d2 Update ChangeLog. 2020-06-04 01:36:05 +03:00
Lauri Ojansivu cd49018306 Add fibers. 2020-06-04 01:34:49 +03:00
Lauri Ojansivu ddbf4f18ff Update translations. 2020-06-04 01:33:58 +03:00
Lauri Ojansivu 24104991b4 Update ChangeLog. 2020-06-04 01:31:53 +03:00
Lauri Ojansivu d9d451a206 Upgrade to Node v12.18.0
Thanks to Node developers and xet7 !
2020-06-04 01:25:35 +03:00
Lauri Ojansivu 1f07050979 Merge pull request #3149 from marc1006/issue_3148
Update `markedjs` package
2020-06-04 00:57:44 +03:00
Marc Hartmayer 399ddd2dab Update markedjs package
Update `markedjs` to commit
https://github.com/markedjs/marked/commit/7b3036f8c0440cfd003ce47dd6e1a92af0f5e822.
This fixes the issue https://github.com/wekan/wekan/issues/3148.
2020-06-03 23:10:25 +02:00
Lauri Ojansivu 2876ce633c Merge pull request #3146 from GitGramm/patch-1
Added dates & assignees to REST API calls #3145
2020-06-01 16:51:57 +03:00
GitGramm 333b9c811d Merge pull request #2 from GitGramm/GitGramm-patch-2
Added dates & assignees to REST API calls incl receivedAt
2020-06-01 15:45:20 +02:00
GitGramm 1df3d56f7c Added dates & assignees to REST API calls incl receivedAt
I have added the dates (received, start, due, end) and the assignees to the output of the REST API Calls.
2020-06-01 15:44:51 +02:00
GitGramm d2a7a82870 Added dates & assignees to REST API calls #3145
I have added the dates (stat, due, end) and the assignees to the output of the REST API Calls
2020-06-01 14:23:43 +02:00
Lauri Ojansivu 5e49006b6f Update ChangeLog. 2020-05-31 05:29:11 +03:00
Lauri Ojansivu f73ea218ee Add Calendar Month Event List view.
Thanks to xet7 !
2020-05-30 17:12:09 +03:00
Lauri Ojansivu 6ac3249272 v4.10 2020-05-30 08:41:48 +03:00
Lauri Ojansivu 784f960e8f Update ChangeLog. 2020-05-30 08:35:46 +03:00
Lauri Ojansivu 04db8d739e Update translations. 2020-05-30 08:29:32 +03:00
Lauri Ojansivu 7226afdc39 Remove fibers. 2020-05-30 08:25:12 +03:00
Lauri Ojansivu 446a81ddd0 Merge pull request #3140 from marc1006/2310
Fix Registration broken "Templates board id is required"
2020-05-30 08:22:47 +03:00
Marc Hartmayer 5fd167c182 REALLY, REALLY UGLY HACK 2020-05-29 23:42:19 +02:00
Marc Hartmayer 7cd57ee023 WIP 2020-05-29 22:29:42 +02:00
Lauri Ojansivu 07ab3b83b1 Update ChangeLog. 2020-05-29 21:40:53 +03:00
Lauri Ojansivu 3ade9d95a6 Upgrade to Node v12.17.0
Thanks to Node developers and xet7 !
2020-05-29 21:37:28 +03:00
Lauri Ojansivu 55c5f07026 Update ChangeLog. 2020-05-29 21:24:44 +03:00
Lauri Ojansivu b4ade0256b Merge pull request #3135 from marc1006/sendSMTPTestEmail
Fix email verification in `sendSMTPTestEmail`
2020-05-29 21:24:21 +03:00
Marc Hartmayer afcb8aa49b Fix email verification in sendSMTPTestEmail
Fixes: 6e088af3
2020-05-29 13:35:29 +02:00
Lauri Ojansivu a47ca598e7 Merge pull request #3131 from gvespignani70/feature/new-API-cardsByCustomField
Added an API to get the cards for a specific custom field value
2020-05-28 18:51:30 +03:00
Giacomo Vespignani c9a28db3ab Added an API to get the cards for a specific custom field value 2020-05-27 11:16:35 +02:00
Lauri Ojansivu 1cfb6eee4b v4.09 2020-05-27 02:12:42 +03:00
Lauri Ojansivu 6c68867efe Update translations. 2020-05-27 02:04:43 +03:00
Lauri Ojansivu 0ef84a0d5f Update ChangeLog. 2020-05-27 01:45:48 +03:00
Lauri Ojansivu e0f4758667 Merge pull request #3128 from brymut/fix-export-csv-vote
Fix vote export & export/import currency custom field to CSV/TSV
2020-05-27 01:43:08 +03:00
Bryan Mutai 8c149da9e9 Fix vote export to CSV/TSV & export currency custom field. 2020-05-27 01:23:31 +03:00
Lauri Ojansivu 18eafe2fec v4.08 2020-05-26 17:12:29 +03:00
Lauri Ojansivu 5ac636fdd4 Update translations. 2020-05-26 17:08:23 +03:00
Lauri Ojansivu 6db3474cdf Update ChangeLog. 2020-05-26 17:03:52 +03:00
Lauri Ojansivu 6a1bc167cf Add some changes to Modern theme.
Thanks to jeroenstoker and xet7 !

Related #3086
2020-05-26 17:00:46 +03:00
Lauri Ojansivu 191071d295 Update ChangeLog. 2020-05-26 12:59:28 +03:00
Lauri Ojansivu 3e4a9471ba Merge branch 'habenamare-currency-custom-field' 2020-05-26 12:54:14 +03:00
Lauri Ojansivu 9b68aa919a Update translations. 2020-05-26 12:53:43 +03:00
Haben Amare 8732e4b18f use Intl.NumberFormat to format currency 2020-05-25 22:05:06 +00:00
Haben Amare d26786a628 display formatted currency custom field on minicard 2020-05-25 22:02:51 +00:00
Haben Amare 54b9035eb2 add 'cardCustomField-currency' template 2020-05-25 22:02:51 +00:00
Haben Amare 3cf6ed916f add the 'currency' custom field type 2020-05-25 22:02:51 +00:00
Lauri Ojansivu 351d9d0c95 Fix typo that caused parse error. 2020-05-26 00:51:10 +03:00
Lauri Ojansivu c0923ed3cf v4.07 2020-05-26 00:38:29 +03:00
Lauri Ojansivu fc2eb2e935 Update ChangeLog. 2020-05-26 00:35:32 +03:00
Lauri Ojansivu 88844034fc Merge pull request #3122 from marc1006/default_value
Fix default value of `sort`
2020-05-26 00:29:33 +03:00
Marc Hartmayer 3d33ea911a Fix default value of sort
The default has to be a numeric value.
2020-05-25 23:26:07 +02:00
Lauri Ojansivu bb08e40ea5 Merge pull request #3121 from marc1006/api_gen
Fix Python API generation
2020-05-26 00:13:00 +03:00
Marc Hartmayer c4879de4b1 Fix Python API generation 2020-05-25 23:10:01 +02:00
Lauri Ojansivu fd0afd7b05 Merge pull request #3120 from marc1006/issue_3119
Fix move selection
2020-05-25 23:50:51 +03:00
Marc Hartmayer 5c02850bc1 Fix move selection
This fixes https://github.com/wekan/wekan/issues/3119.
2020-05-25 22:48:57 +02:00
Lauri Ojansivu 1f450df1af v4.06 2020-05-25 23:22:35 +03:00
Lauri Ojansivu e933890730 Update translations. 2020-05-25 23:19:25 +03:00
Lauri Ojansivu 8eafa1ac66 Fix Card export CSV, check for vote undefined.
Thanks to xet7 !
2020-05-25 22:59:52 +03:00
Lauri Ojansivu 858bb35612 v4.05 2020-05-25 21:46:08 +03:00
Lauri Ojansivu f9018fc3a8 Sorry marc1006, I had to revert deepcode.ai arrow function fixes because
Python API docs generator does not work all when code has arrow functions.

Thanks to xet7 !
2020-05-25 21:33:38 +03:00
Lauri Ojansivu 252c4b19f9 Update ChangeLog. 2020-05-25 18:04:51 +03:00
Lauri Ojansivu 05349a5deb Prettifier fixes. 2020-05-25 18:04:32 +03:00
Lauri Ojansivu d52affe658 Move In Progress ostrio-files changes to separate branch, and revert ostrio-files changes, so that:
- Export to CSV/TSV with custom fields works
- Attachments are not exported to disk
- It is possible to build arm64/s390x versions again.

Thanks to xet7 !

Related #3110
2020-05-25 17:54:51 +03:00
Lauri Ojansivu 23dcd084a4 Update translations. 2020-05-24 19:29:38 +03:00
Lauri Ojansivu a853189486 Update ChangeLog. 2020-05-24 19:10:29 +03:00
Lauri Ojansivu 82d0a3df85 Merge branch 'brymut-import-export-custom-fields' 2020-05-24 19:06:38 +03:00
Lauri Ojansivu ef189477dd Merge branch 'import-export-custom-fields' of https://github.com/brymut/wekan into
brymut-import-export-custom-fields
2020-05-24 18:59:46 +03:00
Lauri Ojansivu 4bcc795778 Update ChangeLog. 2020-05-24 18:57:43 +03:00
Lauri Ojansivu 01346152b5 Merge pull request #3112 from marc1006/deepcode
Some fixes suggested by deepcode.ai
2020-05-24 18:56:36 +03:00
Lauri Ojansivu 5b7b5b3e7e Update ChangeLog. 2020-05-24 18:51:24 +03:00
Lauri Ojansivu 2df29324e6 Merge branch 'marc1006-minor_fixes' 2020-05-24 18:51:12 +03:00
Bryan Mutai 51f52fee23 enable exporting and importing custom fields. 2020-05-24 17:48:49 +03:00
Marc Hartmayer 6e088af3d1 Fix email verification in sendSMTPTestEmail
Found by deepcode.ai.
2020-05-24 13:19:24 +02:00
Marc Hartmayer fc9f0d8392 Sort callback should return 0 if values are equal
Fixes:
"The callback [:381] provided to sort [:381] should return 0 if the compared values are equal."
2020-05-24 13:19:24 +02:00
Marc Hartmayer cfa2bbd3d4 Fix false positive found by deepcode.ai 2020-05-24 13:19:24 +02:00
Marc Hartmayer 52d633ca69 Use on instead of bind
Suggested by deepcode.ai.
2020-05-24 12:46:39 +02:00
Marc Hartmayer b2fee6a6c1 Use an arrow function inside forEach() instead of an anonymous function
Suggested by deepcode.ai.
2020-05-24 12:44:14 +02:00
Marc Hartmayer 66d35a1528 Fix minicard cover functionality
Otherwise, if `this.coverId` is undefined then `Attachments.findOne()` would
return any attachment.
2020-05-24 11:55:44 +02:00
Marc Hartmayer b7105d7b57 Fix error link not available
Fixes:

Exception in template helper: TypeError: Cannot read property 'link' of undefined
    at constructor.attachmentLink (http://192.168.178.2:8080/app/app.js?hash=c0f961362c1318d650abaafcaaa6f6a77c8d11c8:3935:29)
    at http://192.168.178.2:8080/packages/blaze.js?hash=a20deb597f76789e171a9ee2b2e37d73fbb7ecda:1566:16
    at http://192.168.178.2:8080/packages/peerlibrary_blaze-components.js?hash=4049f7e3116e3d9e865392b9546e70dc479b9add:395:66
    at Function.Template._withTemplateInstanceFunc (http://192.168.178.2:8080/packages/blaze.js?hash=a20deb597f76789e171a9ee2b2e37d73fbb7ecda:3575:14)
    at http://192.168.178.2:8080/packages/peerlibrary_blaze-components.js?hash=4049f7e3116e3d9e865392b9546e70dc479b9add:394:27
    at Spacebars.call (http://192.168.178.2:8080/packages/spacebars.js?hash=6f2be25813c793c0b363a6a91ebb029723f294ec:169:51)
    at Spacebars.mustacheImpl (http://192.168.178.2:8080/packages/spacebars.js?hash=6f2be25813c793c0b363a6a91ebb029723f294ec:106:25)
    at Object.Spacebars.mustache (http://192.168.178.2:8080/packages/spacebars.js?hash=6f2be25813c793c0b363a6a91ebb029723f294ec:110:39)
    at Blaze.View._render (http://192.168.178.2:8080/app/app.js?hash=c0f961362c1318d650abaafcaaa6f6a77c8d11c8:3328:42)
    at Blaze.View.doRender (http://192.168.178.2:8080/packages/blaze.js?hash=a20deb597f76789e171a9ee2b2e37d73fbb7ecda:1925:25)
2020-05-24 11:31:25 +02:00
Marc Hartmayer d5fbd50b76 Make scrollParentContainer() more robust as it's used in a timeout callback
Example exception:

Exception in setTimeout callback: TypeError: Cannot read property 'parentComponent' of null
at constructor.scrollParentContainer (cardDetails.js:77)
at cardDetails.js:190
at Meteor.EnvironmentVariable.EVp.withValue (meteor.js?hash=857dafb4b9dff17e29ed8498a22ea5b1a3d6b41d:1207)
at meteor.js?hash=857dafb4b9dff17e29ed8498a22ea5b1a3d6b41d:588
at meteor.js?hash=857dafb4b9dff17e29ed8498a22ea5b1a3d6b41d:1234

Probably there is a better fix for this.
2020-05-24 11:28:05 +02:00
Lauri Ojansivu 06515559a6 Update translations. 2020-05-24 09:12:01 +03:00
Lauri Ojansivu c61a126c8b Add default attachments store path /var/snap/wekan/common/uploads
where attachments will be stored.
2020-05-24 09:03:50 +03:00
Lauri Ojansivu b737adfcdf Try to fix Snap. 2020-05-24 07:24:44 +03:00
Lauri Ojansivu 3b44acd87c Update packages. 2020-05-24 06:54:48 +03:00
Lauri Ojansivu 6fad68b9b9 Try to fix Snap. 2020-05-24 05:43:50 +03:00
133 changed files with 9953 additions and 7295 deletions

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND=noninteractive
ENV \
DEBUG=false \
NODE_VERSION=12.16.3 \
NODE_VERSION=12.18.0 \
METEOR_RELEASE=1.10.2 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
+1 -1
View File
@@ -81,7 +81,7 @@ parts:
wekan:
source: .
plugin: nodejs
node-engine: 12.16.3
node-engine: 12.18.0
node-packages:
- node-gyp
- node-pre-gyp
+1 -1
View File
@@ -83,7 +83,7 @@ parts:
wekan:
source: .
plugin: nodejs
node-engine: 12.14.3
node-engine: 12.18.0
node-packages:
- node-gyp
- node-pre-gyp
+1 -1
View File
@@ -98,4 +98,4 @@ percolate:synced-cron
easylogic:summernote
cfs:filesystem
ostrio:cookies
ostrio:files
tmeasday:check-npm-versions
+4 -4
View File
@@ -101,7 +101,7 @@ meteorhacks:collection-utils@1.2.0
meteorhacks:picker@1.0.3
meteorhacks:subs-manager@1.6.4
meteorspark:util@0.2.0
minifier-css@1.5.0
minifier-css@1.5.1
minifier-js@2.6.0
minifiers@1.1.8-faster-rebuild.0
minimongo@1.6.0
@@ -109,7 +109,7 @@ mobile-status-bar@1.1.0
modern-browsers@0.1.5
modules@0.15.0
modules-runtime@0.12.0
momentjs:moment@2.25.2
momentjs:moment@2.26.0
mongo@1.10.0
mongo-decimal@0.1.1
mongo-dev-server@1.1.0
@@ -134,7 +134,6 @@ observe-sequence@1.0.16
ongoworks:speakingurl@1.1.0
ordered-dict@1.1.0
ostrio:cookies@2.6.0
ostrio:files@1.14.2
peerlibrary:assert@0.3.0
peerlibrary:base-component@0.16.0
peerlibrary:blaze-components@0.15.1
@@ -178,6 +177,7 @@ templating@1.3.2
templating-compiler@1.3.3
templating-runtime@1.3.2
templating-tools@1.1.2
tmeasday:check-npm-versions@0.3.2
tracker@1.2.0
twbs:bootstrap@3.3.6
ui@1.0.13
@@ -192,7 +192,7 @@ webapp-hashing@1.0.9
wekan-accounts-cas@0.1.0
wekan-accounts-oidc@1.0.10
wekan-ldap@0.0.2
wekan-markdown@1.0.7
wekan-markdown@1.0.9
wekan-oidc@1.0.12
wekan-scrollbar@3.1.3
yasaricli:slugify@0.0.7
+1 -1
View File
@@ -3,7 +3,7 @@ sudo: required
env:
TRAVIS_DOCKER_COMPOSE_VERSION: 1.24.0
TRAVIS_NODE_VERSION: 12.16.3
TRAVIS_NODE_VERSION: 12.18.0
TRAVIS_NPM_VERSION: latest
before_install:
+200
View File
@@ -1,5 +1,205 @@
# v4.13 2020-06-09 Wekan release
This release adds the following updates:
- [OpenShift template updates](https://github.com/wekan/wekan/pull/3158), Thanks to jimmyjones2:
1) Remove status fields (this is created by Kubernetes at run time)
2) The latest MongoDB by default available with OpenShift is 3.6
3) Change MongoDB service name to contain wekan to avoid potentially conflicting with other mongodb instances in the same project.
and fixes the following bugs:
- [Copy the labels only if the target board is different](https://github.com/wekan/wekan/pull/3154).
Thanks to marc1006.
- [Fix condition whether a card is in list](https://github.com/wekan/wekan/pull/3165).
Thanks to marc1006.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.12 2020-06-08 Wekan release
This release fixes the following CRITICAL SECURITY VULNERABILITIES:
- Fix XSS bug reported 2020-05-24 by [swsjona](https://twitter.com/swsjona):
[Part 1](https://github.com/wekan/wekan/commit/1f85b25549b50602380f1745f19e5fe44fe36d6f),
[Part 2](https://github.com/wekan/wekan/commit/fb44df981581354bf23a6928427ad2bf73c4550f),
[Part 3](https://github.com/wekan/wekan/commit/99f68f36b028d6c75acf2e5b83585b1acee65f97),
[Part 4](https://github.com/wekan/wekan/commit/8a622ec7c3043bf8f34399ef34563e6a9a19dcd8).
Logged in users could run javascript in input fields. This was partially fixed at v3.85,
but at some fields XSS was still possible. This affects at least Wekan versions v3.12-v4.12.
After this fix, Javascript in input fields is not executed.
Thanks to swsjona, marc1006 and xet7.
and adds the following new features:
- Change default view to Swimlanes:
[Part 1](https://github.com/wekan/wekan/commit/8c3322f9a93c321e8a2cc5cfcd4b1d6316a5fb7c),
[Part 2](https://github.com/wekan/wekan/commit/61e682470cdaef42cce2d74b41fb752cfc61848b),
[Part 3 Change dropdown order to Swimlanes/Lists/Calendar](https://github.com/wekan/wekan/commit/7f6d500cbec15496ae357b05b9df3f10e51ed1f1),
[Part 4.1. Public board default view to Swimlane. Part 4.2. When changing Public board
view (sets view cookie), also reload page so view is changed
immediately](https://github.com/wekan/wekan/commit/39519d1cc944c567837be0f88ab4a037e2144c61).
Thanks to xet7.
- [Use markdown in Swimlane titles](https://github.com/wekan/wekan/commit/6b22f96313354b45b851b93c25aa392bbe346bdb).
Thanks to xet7.
and adds the following updates:
- [Update minifier-css](https://github.com/wekan/wekan/commit/cb1e91fee83eaad1e926c288c0abfc1e4f2a8bd4).
Thanks to xet7.
and fixes the following bugs:
- Fix indent [Part1](https://github.com/wekan/wekan/commit/415e94d187ffcb9a4afaecc5c6960a50a87ca7eb),
[Part 2](https://github.com/wekan/wekan/commit/96494bacf550cde65598e6d59199517f311aa33d).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.11 2020-06-04 Wekan release
This release adds the following new platforms:
- [Using arm64 bundle on Raspberry Pi OS arm64 with MongoDB 4.2.x for RasPi3 and
RasPi4](https://github.com/wekan/wekan/wiki/Raspberry-Pi#raspberry-pi-os-arm64-with-mongodb-42x).
Thanks to Raspberry Pi OS devs, MongoDB devs and xet7.
- [s390x RHEL 8](https://github.com/wekan/wekan/wiki/s390x#rhel-8).
Thanks to IBM, Red Hat Linux, Linux Foundation and xet7.
and adds the following updates:
- [Upgrade to Node v12.18.0](https://github.com/wekan/wekan/commit/d9d451a206cabe7f6ca8ad5d35eb76443198e4c1).
Thanks to Node developers and xet7.
- [Update `markedjs` package](https://github.com/wekan/wekan/pull/3149).
Thanks to marc1006.
- [Add fibers](https://github.com/wekan/wekan/commit/cd49018306f826fff37b7024dfde9de05d88b620).
Thanks to xet7.
and adds the following new features:
- [Add Calendar Month Event List view](https://github.com/wekan/wekan/commit/f73ea218eefba3f0d6c642849dfede9e03052d25).
Thanks to xet7.
- [Added dates & assignees to REST API calls](https://github.com/wekan/wekan/pull/3146).
Thanks to GitGramm.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.10 2020-05-30 Wekan release
This release adds the following new features:
- [Added an API to get the cards for a specific custom field value](https://github.com/wekan/wekan/pulls/3131).
Thanks to gvespignani70.
and adds the following updates:
- [Upgrade to Node v12.17.0](https://github.com/wekan/wekan/commit/3ade9d95a69b269c345127e1755e1b539dc07263).
Thanks to Node developers and xet7.
and fixes the following bugs:
- [Fix email verification in `sendSMTPTestEmail`](https://github.com/wekan/wekan/pull/3135).
Thanks to marc1006.
- [Try to Fix Registration broken "Templates board id is required" with ugly hack. If it works, ugly becomes
beautiful](https://github.com/wekan/wekan/pull/3140).
Thanks to marc1006.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.09 2020-05-27 Wekan release
This release fixes the following bugs:
- [Fix vote export & export/import currency custom field to CSV/TSV](https://github.com/wekan/wekan/pull/3128).
Thanks to brymut.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.08 2020-05-26 Wekan release
This release adds the following new features:
- [Add the 'Currency' Custom Field type](https://github.com/wekan/wekan/pull/3123).
Thanks to habenamare.
and adds the following updates:
- [Add some changes to Modern theme](https://github.com/wekan/wekan/commit/6a1bc167cf10e75d61b3196db9eac2978d70ad8e).
Thanks to jeroenstoker and xet7.
and fixes the following bugs:
- [Fix typo that caused parse error](https://github.com/wekan/wekan/commit/351d9d0c9577c9d543d543bc12a51388b0141324).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.07 2020-05-26 Wekan release
This release fixes the following bugs:
- [Fix move selection](https://github.com/wekan/wekan/pull/3120).
Thanks to marc1006.
- [Fix Python API generation](https://github.com/wekan/wekan/pull/3121).
Thanks to marc1006.
- [Fix default value of `sort`](https://github.com/wekan/wekan/pull/3122).
Thanks to marc1006.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.06 2020-05-25 Wekan release
This release fixes the following bugs:
- [Fix Card export CSV, check for vote
undefined](https://github.com/wekan/wekan/commit/8eafa1ac66fdcf5fb5f0a95aa6cfee454ddad67f).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.05 2020-05-25 Wekan release
This release adds the following new features:
- [Import/Export Custom Fields in CSV/TSV](https://github.com/wekan/wekan/pull/3115).
Thanks to brymut.
and adds the following updates:
- [Update packages](https://github.com/wekan/wekan/commit/3b44acd87c35340bf9fe5d210f4402f1b1a1dfdf).
Thanks to xet7.
and fixes the following bugs:
- Try to fix Snap [1](https://github.com/wekan/wekan/commit/6fad68b9b9afd8de7074037d73eeac40f6a3f7c1), [2](https://github.com/wekan/wekan/commit/b737adfcdfc9b8084a7eb84420a89c014bbec1fb). Later reverted those like other ostrio-files changes too.
Thanks to xet7.
- [Add default attachments store path /var/snap/wekan/common/uploads where attachments will be
stored](https://github.com/wekan/wekan/commit/c61a126c8bcb25a1eda0203b89c990ae31de7a70).
Thanks to xet7.
- [Make scrollParentContainer() more robust as it's used in a timeout callback. Example exception: Exception in setTimeout callback: TypeError: Cannot read property 'parentComponent' of null. Probably there is a better fix for this](https://github.com/wekan/wekan/commit/d5fbd50b760b1d3b84b5b4e8af3a8ed7608e2918).
Thanks to marc1006.
- [Fix error link not available. Fixes: Exception in template helper: TypeError: Cannot read property 'link' of
undefined](https://github.com/wekan/wekan/commit/b7105d7b5712dcdbf9dadebfddaba7691810da5c).
Thanks to marc1006.
- [Fix minicard cover functionality. Otherwise, if `this.coverId` is undefined then `Attachments.findOne()` would return any
attachment](https://github.com/wekan/wekan/commit/66d35a15280795b76a81c3e59cebbd2a29e4dff8).
Thanks to marc1006.
- [Some fixes suggested by deepcode.ai](https://github.com/wekan/wekan/pull/3112).
Thanks to marc1006.
- [Sorry marc1006, I had to revert deepcode.ai arrow function fixes because Python API docs generator does not work all when code has arrow functions](https://github.com/wekan/wekan/commit/f9018fc3a87080d8d97c371e29a8f3f0a20ca932).
Thanks to xet7.
- [Move In Progress ostrio-files changes to separate branch, and revert ostrio-files changes, so that:
Export to CSV/TSV with custom fields works, Attachments are not exported to disk,
It is possible to build arm64/s390x versions
again](https://github.com/wekan/wekan/commit/d52affe65893f17bab59bb43aa9f5afbb54993d3).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.04 2020-05-24 Wekan release
Please use v4.05 or newer instead, that works better.
This release adds the following features:
- [Found Time Tracking GPLv3 software "Titra" with integration to Wekan](https://github.com/wekan/wekan/wiki/Time-Tracking).
+1 -1
View File
@@ -8,7 +8,7 @@ ARG DEBIAN_FRONTEND=noninteractive
ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates python3" \
DEBUG=false \
NODE_VERSION=v12.16.3 \
NODE_VERSION=v12.18.0 \
METEOR_RELEASE=1.10.2 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
+2 -2
View File
@@ -4,7 +4,7 @@ FROM amd64/alpine:3.7 AS builder
ENV QEMU_VERSION=v4.2.0-6 \
QEMU_ARCHITECTURE=aarch64 \
NODE_ARCHITECTURE=linux-arm64 \
NODE_VERSION=v12.16.3 \
NODE_VERSION=v12.18.0 \
WEKAN_VERSION=3.96 \
WEKAN_ARCHITECTURE=arm64
@@ -40,7 +40,7 @@ LABEL maintainer="wekan"
# Set the environment variables (defaults where required)
ENV QEMU_ARCHITECTURE=aarch64 \
NODE_ARCHITECTURE=linux-arm64 \
NODE_VERSION=v12.16.1 \
NODE_VERSION=v12.18.0 \
NODE_ENV=production \
NPM_VERSION=latest \
WITH_API=true \
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v4.04.0"
appVersion: "v4.13.0"
files:
userUploads:
- README.md
+28 -28
View File
@@ -34,38 +34,38 @@ template(name="activity")
//- board activity ------------------------------------------------------
if($eq mode 'board')
if($eq activity.activityType 'createBoard')
| {{_ 'activity-created' boardLabel}}.
| {{{_ 'activity-created' boardLabelLink}}}.
if($eq activity.activityType 'importBoard')
| {{{_ 'activity-imported-board' boardLabel sourceLink}}}.
| {{{_ 'activity-imported-board' boardLabelLink sourceLink}}}.
if($eq activity.activityType 'addBoardMember')
| {{{_ 'activity-added' memberLink boardLabel}}}.
| {{{_ 'activity-added' memberLink boardLabelLink}}}.
if($eq activity.activityType 'removeBoardMember')
| {{{_ 'activity-excluded' memberLink boardLabel}}}.
| {{{_ 'activity-excluded' memberLink boardLabelLink}}}.
//- card activity -------------------------------------------------------
if($eq activity.activityType 'createCard')
if($eq mode 'card')
| {{{_ 'activity-added' cardLabel activity.listName}}}.
| {{{_ 'activity-added' cardLabelLink (sanitize activity.listName)}}}.
else
| {{{_ 'activity-added' cardLabel boardLabel}}}.
| {{{_ 'activity-added' cardLabelLink boardLabelLink}}}.
if($eq activity.activityType 'importCard')
| {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}.
| {{{_ 'activity-imported' cardLink boardLabelLink sourceLink}}}.
if($eq activity.activityType 'moveCard')
| {{{_ 'activity-moved' cardLabel activity.oldList.title activity.list.title}}}.
| {{{_ 'activity-moved' cardLabelLink (sanitize activity.oldList.title) (sanitize activity.list.title)}}}.
if($eq activity.activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink activity.oldBoardName activity.boardName}}}.
| {{{_ 'activity-moved' cardLink (sanitize activity.oldBoardName) (sanitize activity.boardName)}}}.
if($eq activity.activityType 'archivedCard')
| {{{_ 'activity-archived' cardLink}}}.
if($eq activity.activityType 'restoredCard')
| {{{_ 'activity-sent' cardLink boardLabel}}}.
| {{{_ 'activity-sent' cardLink boardLabelLink}}}.
//- checklist activity --------------------------------------------------
if($eq activity.activityType 'addChecklist')
@@ -83,25 +83,25 @@ template(name="activity")
| {{{_ 'activity-checklist-removed' cardLink}}}.
if($eq activity.activityType 'completeChecklist')
| {{{_ 'activity-checklist-completed' activity.checklist.title cardLink}}}.
| {{{_ 'activity-checklist-completed' (sanitize activity.checklist.title) cardLink}}}.
if($eq activity.activityType 'uncompleteChecklist')
| {{{_ 'activity-checklist-uncompleted' activity.checklist.title cardLink}}}.
| {{{_ 'activity-checklist-uncompleted' (sanitize activity.checklist.title) cardLink}}}.
if($eq activity.activityType 'checkedItem')
| {{{_ 'activity-checked-item' checkItem activity.checklist.title cardLink}}}.
| {{{_ 'activity-checked-item' (sanitize checkItem) (sanitize activity.checklist.title) cardLink}}}.
if($eq activity.activityType 'uncheckedItem')
| {{{_ 'activity-unchecked-item' checkItem activity.checklist.title cardLink}}}.
| {{{_ 'activity-unchecked-item' (sanitize checkItem) (sanitize activity.checklist.title) cardLink}}}.
if($eq activity.activityType 'addChecklistItem')
| {{{_ 'activity-checklist-item-added' activity.checklist.title cardLink}}}.
| {{{_ 'activity-checklist-item-added' (sanitize activity.checklist.title) cardLink}}}.
.activity-checklist(href="{{ activity.card.absoluteUrl }}")
+viewer
= activity.checklistItem.title
if($eq activity.activityType 'removedChecklistItem')
| {{{_ 'activity-checklist-item-removed' activity.checklist.title cardLink}}}.
| {{{_ 'activity-checklist-item-removed' (sanitize activity.checklist.title) cardLink}}}.
//- comment activity ----------------------------------------------------
if($eq mode 'card')
@@ -143,31 +143,31 @@ template(name="activity")
| {{_ 'activity-customfield-created' customField}}.
if($eq activity.activityType 'setCustomField')
| {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}.
| {{{_ 'activity-set-customfield' (sanitize lastCustomField) (sanitize lastCustomFieldValue) cardLink}}}.
if($eq activity.activityType 'unsetCustomField')
| {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}.
| {{{_ 'activity-unset-customfield' (sanitize lastCustomField) cardLink}}}.
//- label activity ------------------------------------------------------
if($eq activity.activityType 'addedLabel')
| {{{_ 'activity-added-label' lastLabel cardLink}}}.
| {{{_ 'activity-added-label' (sanitize lastLabel) cardLink}}}.
if($eq activity.activityType 'removedLabel')
| {{{_ 'activity-removed-label' lastLabel cardLink}}}.
| {{{_ 'activity-removed-label' (sanitize lastLabel) cardLink}}}.
//- list activity -------------------------------------------------------
if($neq mode 'card')
if($eq activity.activityType 'createList')
| {{{_ 'activity-added' listLabel boardLabel}}}.
| {{{_ 'activity-added' (sanitize listLabel) boardLabelLink}}}.
if($eq activity.activityType 'importList')
| {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}.
| {{{_ 'activity-imported' (sanitize listLabel) boardLabelLink sourceLink}}}.
if($eq activity.activityType 'removeList')
| {{{_ 'activity-removed' activity.title boardLabel}}}.
| {{{_ 'activity-removed' (sanitize activity.title) boardLabelLink}}}.
if($eq activity.activityType 'archivedList')
| {{_ 'activity-archived' listLabel}}.
| {{_ 'activity-archived' (sanitize listLabel)}}.
//- member activity ----------------------------------------------------
if($eq activity.activityType 'joinMember')
@@ -185,15 +185,15 @@ template(name="activity")
//- swimlane activity --------------------------------------------------
if($neq mode 'card')
if($eq activity.activityType 'createSwimlane')
| {{{_ 'activity-added' activity.swimlane.title boardLabel}}}.
| {{_ 'activity-added' (sanitize activity.swimlane.title) boardLabelLink}}.
if($eq activity.activityType 'archivedSwimlane')
| {{_ 'activity-archived' activity.swimlane.title}}.
| {{_ 'activity-archived' (sanitize activity.swimlane.title)}}.
//- I don't understand this part ----------------------------------------
if(currentData.timeKey)
| {{{_ activity.activityType }}}
| {{_ activity.activityType }}
= ' '
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
if (currentData.timeOldValue)
@@ -203,6 +203,6 @@ template(name="activity")
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
= ' @'
else if(currentData.timeValue)
| {{{_ activity.activityType currentData.timeValue}}}
| {{_ activity.activityType currentData.timeValue}}
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
+20 -12
View File
@@ -1,3 +1,5 @@
import sanitizeXss from 'xss';
const activitiesPerPage = 20;
BlazeComponent.extendComponent({
@@ -57,7 +59,7 @@ BlazeComponent.extendComponent({
return checkItem && checkItem.title;
},
boardLabel() {
boardLabelLink() {
const data = this.currentData();
if (data.mode !== 'board') {
return createBoardLink(data.activity.board(), data.activity.listName);
@@ -65,10 +67,10 @@ BlazeComponent.extendComponent({
return TAPi18n.__('this-board');
},
cardLabel() {
cardLabelLink() {
const data = this.currentData();
if (data.mode !== 'card') {
return createCardLink(this.currentData().activity.card());
return createCardLink(data.activity.card());
}
return TAPi18n.__('this-card');
},
@@ -134,11 +136,11 @@ BlazeComponent.extendComponent({
{
href: source.url,
},
source.system,
sanitizeXss(source.system),
),
);
} else {
return source.system;
return sanitizeXss(source.system);
}
}
return null;
@@ -152,21 +154,20 @@ BlazeComponent.extendComponent({
attachmentLink() {
const attachment = this.currentData().activity.attachment();
const link = attachment.link('original', '/');
// trying to display url before file is stored generates js errors
return (
(attachment &&
link &&
attachment.url({ download: true }) &&
Blaze.toHTML(
HTML.A(
{
href: link,
href: attachment.url({ download: true }),
target: '_blank',
},
attachment.name,
sanitizeXss(attachment.name()),
),
)) ||
this.currentData().activity.attachmentName
sanitizeXss(this.currentData().activity.attachmentName)
);
},
@@ -203,7 +204,14 @@ BlazeComponent.extendComponent({
},
}).register('activity');
Template.activity.helpers({
sanitize(value) {
return sanitizeXss(value);
},
});
function createCardLink(card) {
if (!card) return '';
return (
card &&
Blaze.toHTML(
@@ -212,7 +220,7 @@ function createCardLink(card) {
href: card.absoluteUrl(),
class: 'action-card',
},
card.title,
sanitizeXss(card.title),
),
)
);
@@ -229,7 +237,7 @@ function createBoardLink(board, list) {
href: board.absoluteUrl(),
class: 'action-board',
},
text,
sanitizeXss(text),
),
)
);
+1 -1
View File
@@ -323,7 +323,7 @@ BlazeComponent.extendComponent({
header: {
left: 'title today prev,next',
center:
'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,timelineMonth timelineYear',
'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,listMonth',
right: '',
},
// height: 'parent', nope, doesn't work as the parent might be small
+32 -1
View File
@@ -679,7 +679,7 @@ setBoardClear(color1,color2)
color: #333
.pop-over
box-shadow: 0 4px 20px rgba(0,0,0,.1)
box-shadow: 0 4px 20px rgba(0,0,0,.2)
border: 0
border-radius: 5px
@@ -687,6 +687,17 @@ setBoardClear(color1,color2)
padding: 10px
border-bottom: 0
border-radius: 5px 5px 0 0
background:#eee
.pop-over .header .header-title
font-family: Poppins
font-size:16px
color:#333
.pop-over .header .close-btn
font-size:20px
top:6px
right:8px
.pop-over .content-container .content
padding: 5px 20px 20px
@@ -708,3 +719,23 @@ setBoardClear(color1,color2)
.sidebar .sidebar-content
padding: 30px
/* Notifications */
.board-color-modern section#notifications-drawer
border-radius:5px
.board-color-modern section#notifications-drawer .header
padding: 18px 16px
border-bottom: 0
border-radius: 5px 5px 0 0
background: #eee
.board-color-modern section#notifications-drawer .header h5
font-family: Poppins
font-weight: bold
.board-color-modern section#notifications-drawer .header .close
font-size: 20px
top: 14px
section#notifications-drawer .header .toggle-read
top: 18px
+10 -10
View File
@@ -99,13 +99,13 @@ template(name="boardHeaderBar")
a.board-header-btn.js-toggle-board-view(
title="{{_ 'board-view'}}")
i.fa.fa-caret-down
if $eq boardView 'board-view-lists'
i.fa.fa-trello
if $eq boardView 'board-view-swimlanes'
i.fa.fa-th-large
if $eq boardView 'board-view-lists'
i.fa.fa-trello
if $eq boardView 'board-view-cal'
i.fa.fa-calendar
span {{#if boardView}}{{_ boardView}}{{else}}{{_ 'board-view-lists'}}{{/if}}
span {{#if boardView}}{{_ boardView}}{{else}}{{_ 'board-view-swimlanes'}}{{/if}}
if canModifyBoard
a.board-header-btn.js-multiselection-activate(
@@ -172,13 +172,6 @@ template(name="boardChangeWatchPopup")
template(name="boardChangeViewPopup")
ul.pop-over-list
li
with "board-view-lists"
a.js-open-lists-view
i.fa.fa-trello.colorful
| {{_ 'board-view-lists'}}
if $eq Utils.boardView "board-view-lists"
i.fa.fa-check
li
with "board-view-swimlanes"
a.js-open-swimlanes-view
@@ -186,6 +179,13 @@ template(name="boardChangeViewPopup")
| {{_ 'board-view-swimlanes'}}
if $eq Utils.boardView "board-view-swimlanes"
i.fa.fa-check
li
with "board-view-lists"
a.js-open-lists-view
i.fa.fa-trello.colorful
| {{_ 'board-view-lists'}}
if $eq Utils.boardView "board-view-lists"
i.fa.fa-check
li
with "board-view-cal"
a.js-open-cal-view
+3 -10
View File
@@ -18,19 +18,12 @@ template(name="attachmentDeletePopup")
p {{_ "attachment-delete-pop"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="uploadingPopup")
.uploading-info
span.upload-percentage {{progress}}%
.upload-progress-frame
.upload-progress-bar(style="width: {{progress}}%;")
span.upload-size {{fileSize}}
template(name="attachmentsGalery")
.attachments-galery
each attachments
.attachment-item
a.attachment-thumbnail.swipebox(href="{{url}}" download="{{name}}" title="{{name}}")
if isUploaded
a.attachment-thumbnail.swipebox(href="{{url}}" title="{{name}}")
if isUploaded
if isImage
img.attachment-thumbnail-img(src="{{url}}")
else
@@ -40,7 +33,7 @@ template(name="attachmentsGalery")
p.attachment-details
= name
span.attachment-details-actions
a.js-download(href="{{url download=true}}" download="{{name}}")
a.js-download(href="{{url download=true}}")
i.fa.fa-download
| {{_ 'download'}}
if currentUser.isBoardMember
+17 -86
View File
@@ -13,10 +13,10 @@ Template.attachmentsGalery.events({
event.stopPropagation();
},
'click .js-add-cover'() {
Cards.findOne(this.meta.cardId).setCover(this._id);
Cards.findOne(this.cardId).setCover(this._id);
},
'click .js-remove-cover'() {
Cards.findOne(this.meta.cardId).unsetCover();
Cards.findOne(this.cardId).unsetCover();
},
'click .js-preview-image'(event) {
Popup.open('previewAttachedImage').call(this, event);
@@ -45,63 +45,22 @@ Template.attachmentsGalery.events({
},
});
Template.attachmentsGalery.helpers({
url() {
return Attachments.link(this, 'original', '/');
},
isUploaded() {
return !this.meta.uploading;
},
isImage() {
return !!this.isImage;
},
});
Template.previewAttachedImagePopup.events({
'click .js-large-image-clicked'() {
Popup.close();
},
});
Template.previewAttachedImagePopup.helpers({
url() {
return Attachments.link(this, 'original', '/');
}
});
// For uploading popup
let uploadFileSize = new ReactiveVar('');
let uploadProgress = new ReactiveVar(0);
Template.cardAttachmentsPopup.events({
'change .js-attach-file'(event, instance) {
'change .js-attach-file'(event) {
const card = this;
const callbacks = {
onBeforeUpload: (err, fileData) => {
Popup.open('uploading')(this.clickEvent);
uploadFileSize.set('...');
uploadProgress.set(0);
return true;
},
onUploaded: (err, attachment) => {
if (attachment && attachment._id && attachment.isImage) {
card.setCover(attachment._id);
}
Popup.close();
},
onStart: (error, fileData) => {
uploadFileSize.set(formatBytes(fileData.size));
},
onError: (err, fileObj) => {
console.log('Error!', err);
},
onProgress: (progress, fileData) => {
uploadProgress.set(progress);
}
};
const processFile = f => {
Utils.processUploadedAttachment(card, f, callbacks);
Utils.processUploadedAttachment(card, f, attachment => {
if (attachment && attachment._id && attachment.isImage()) {
card.setCover(attachment._id);
}
Popup.close();
});
};
FS.Utility.eachFile(event, f => {
@@ -141,22 +100,12 @@ Template.cardAttachmentsPopup.events({
});
},
'click .js-computer-upload'(event, templateInstance) {
this.clickEvent = event;
templateInstance.find('.js-attach-file').click();
event.preventDefault();
},
'click .js-upload-clipboard-image': Popup.open('previewClipboardImage'),
});
Template.uploadingPopup.helpers({
fileSize: () => {
return uploadFileSize.get();
},
progress: () => {
return uploadProgress.get();
}
});
const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
let pastedResults = null;
@@ -200,26 +149,20 @@ Template.previewClipboardImagePopup.events({
if (results && results.file) {
window.oPasted = pastedResults;
const card = this;
const settings = {
file: results.file,
streams: 'dynamic',
chunkSize: 'dynamic',
};
const file = new FS.File(results.file);
if (!results.name) {
// if no filename, it's from clipboard. then we give it a name, with ext name from MIME type
if (typeof results.file.type === 'string') {
settings.fileName =
new Date().getTime() + results.file.type.replace('.+/', '');
file.name(results.file.type.replace('image/', 'clipboard.'));
}
}
settings.meta = {};
settings.meta.updatedAt = new Date().getTime();
settings.meta.boardId = card.boardId;
settings.meta.cardId = card._id;
settings.meta.userId = Meteor.userId();
const attachment = Attachments.insert(settings);
file.updatedAt(new Date());
file.boardId = card.boardId;
file.cardId = card._id;
file.userId = Meteor.userId();
const attachment = Attachments.insert(file);
if (attachment && attachment._id && attachment.isImage) {
if (attachment && attachment._id && attachment.isImage()) {
card.setCover(attachment._id);
}
@@ -229,15 +172,3 @@ Template.previewClipboardImagePopup.events({
}
},
});
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
-11
View File
@@ -64,17 +64,6 @@
border: 1px solid black
box-shadow: 0 1px 2px rgba(0,0,0,.2)
.uploading-info
.upload-progress-frame
background-color: grey;
border: 1px solid;
height: 22px;
.upload-progress-bar
background-color: blue;
height: 20px;
padding: 1px;
@media screen and (max-width: 800px)
.attachments-galery
flex-direction
@@ -53,6 +53,23 @@ template(name="cardCustomField-number")
if value
= value
template(name="cardCustomField-currency")
if canModifyCard
+inlinedForm(classNames="js-card-customfield-currency")
input(type="text" value=data.value)
.edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
else
a.js-open-inlined-form
if value
= formattedValue
else
| {{_ 'edit'}}
else
if value
= formattedValue
template(name="cardCustomField-date")
if canModifyCard
a.js-edit-date(title="{{showTitle}}" class="{{classes}}")
@@ -80,6 +80,36 @@ CardCustomField.register('cardCustomField');
}
}.register('cardCustomField-number'));
// cardCustomField-currency
(class extends CardCustomField {
onCreated() {
super.onCreated();
this.currencyCode = this.data().definition.settings.currencyCode;
}
formattedValue() {
const locale = TAPi18n.getLanguage();
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: this.currencyCode,
}).format(this.data().value);
}
events() {
return [
{
'submit .js-card-customfield-currency'(event) {
event.preventDefault();
const value = Number(this.find('input').value, 10);
this.card.setCustomField(this.customFieldId, value);
},
},
];
}
}.register('cardCustomField-currency'));
// cardCustomField-date
(class extends CardCustomField {
onCreated() {
+8 -1
View File
@@ -74,11 +74,18 @@ BlazeComponent.extendComponent({
scrollParentContainer() {
const cardPanelWidth = 510;
const bodyBoardComponent = this.parentComponent().parentComponent();
const parentComponent = this.parentComponent();
// TODO sometimes parentComponent is not available, maybe because it's not
// yet created?!
if (!parentComponent) return;
const bodyBoardComponent = parentComponent.parentComponent();
//On Mobile View Parent is Board, Not Board Body. I cant see how this funciton should work then.
if (bodyBoardComponent === null) return;
const $cardView = this.$(this.firstNode());
const $cardContainer = bodyBoardComponent.$('.js-swimlanes');
// TODO sometimes cardContainer is not available, maybe because it's not yet
// created?!
if (!$cardContainer) return;
const cardContainerScroll = $cardContainer.scrollLeft();
const cardContainerWidth = $cardContainer.width();
+7 -3
View File
@@ -11,7 +11,7 @@ template(name="minicard")
.handle
.fa.fa-arrows
if cover
.minicard-cover(style="background-image: url('{{coverUrl}}');")
.minicard-cover(style="background-image: url('{{cover.url}}');")
if labels
.minicard-labels
each labels
@@ -74,8 +74,12 @@ template(name="minicard")
+viewer
= definition.name
.minicard-custom-field-item
+viewer
= trueValue
if $eq definition.type "currency"
+viewer
= formattedCurrencyCustomFieldValue(definition)
else
+viewer
= trueValue
if getAssignees
.minicard-assignees.js-minicard-assignees
+14 -3
View File
@@ -9,6 +9,20 @@ BlazeComponent.extendComponent({
return 'minicard';
},
formattedCurrencyCustomFieldValue(definition) {
const customField = this.data()
.customFieldsWD()
.find(f => f._id === definition._id);
const customFieldTrueValue =
customField && customField.trueValue ? customField.trueValue : '';
const locale = TAPi18n.getLanguage();
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: definition.settings.currencyCode,
}).format(customFieldTrueValue);
},
events() {
return [
{
@@ -52,7 +66,4 @@ Template.minicard.helpers({
return false;
}
},
coverUrl() {
return Attachments.findOne(this.coverId).link('original', '/');
},
});
+26 -24
View File
@@ -152,31 +152,33 @@ Template.editor.onRendered(() => {
const processData = function(fileObj) {
Utils.processUploadedAttachment(
currentCard,
fileObj,
{ onUploaded:
attachment => {
if (attachment && attachment._id && attachment.isImage) {
attachment.one('uploaded', function() {
const maxTry = 3;
const checkItvl = 500;
let retry = 0;
const checkUrl = function() {
// even though uploaded event fired, attachment.url() is still null somehow //TODO
const url = Attachments.link(attachment, 'original', '/');
if (url) {
insertImage(
`${location.protocol}//${location.host}${url}`,
);
} else {
retry++;
if (retry < maxTry) {
setTimeout(checkUrl, checkItvl);
}
fileObj,
attachment => {
if (
attachment &&
attachment._id &&
attachment.isImage()
) {
attachment.one('uploaded', function() {
const maxTry = 3;
const checkItvl = 500;
let retry = 0;
const checkUrl = function() {
// even though uploaded event fired, attachment.url() is still null somehow //TODO
const url = attachment.url();
if (url) {
insertImage(
`${location.protocol}//${location.host}${url}`,
);
} else {
retry++;
if (retry < maxTry) {
setTimeout(checkUrl, checkItvl);
}
};
checkUrl();
});
}
}
};
checkUrl();
});
}
},
);
@@ -75,7 +75,7 @@ template(name="cardActions")
button.trigger-button.trigger-button-color.js-show-color-palette(
id="color-action"
class="card-details-{{cardColorButton}}")
| {{{_ cardColorButtonText }}}
| {{_ cardColorButtonText }}
div.trigger-button.js-set-color-action.js-goto-rules
i.fa.fa-plus
@@ -164,6 +164,7 @@ BlazeComponent.extendComponent({
const boardId = Session.get('currentBoard');
const actionId = Actions.insert({
actionType: 'removeMember',
// deepcode ignore NoHardcodedCredentials: it's no credential
username: '*',
boardId,
desc,
+1 -1
View File
@@ -467,7 +467,7 @@ Template.exportBoard.events({
'click .html-export-board': async event => {
event.preventDefault();
await ExportHtml(Popup)();
}
},
});
Template.labelsWidget.events({
@@ -33,6 +33,17 @@ template(name="createCustomFieldPopup")
option(value=value selected="selected") {{name}}
else
option(value=value) {{name}}
div.js-field-settings.js-field-settings-currency(class="{{#if isTypeNotSelected 'currency'}}hide{{/if}}")
label
| {{_ 'custom-field-currency-option'}}
select.js-field-currency
each getCurrencyCodes
if selected
option(value=value selected="selected") {{name}}
else
option(value=value) {{name}}
div.js-field-settings.js-field-settings-dropdown(class="{{#if isTypeNotSelected 'dropdown'}}hide{{/if}}")
label
| {{_ 'custom-field-dropdown-options'}}
@@ -16,12 +16,62 @@ BlazeComponent.extendComponent({
}).register('customFieldsSidebar');
const CreateCustomFieldPopup = BlazeComponent.extendComponent({
_types: ['text', 'number', 'date', 'dropdown'],
_types: ['text', 'number', 'date', 'dropdown', 'currency'],
_currencyList: [
{
name: 'US Dollar',
code: 'USD',
},
{
name: 'Euro',
code: 'EUR',
},
{
name: 'Yen',
code: 'JPY',
},
{
name: 'Pound Sterling',
code: 'GBP',
},
{
name: 'Australian Dollar',
code: 'AUD',
},
{
name: 'Canadian Dollar',
code: 'CAD',
},
{
name: 'Swiss Franc',
code: 'CHF',
},
{
name: 'Yuan Renminbi',
code: 'CNY',
},
{
name: 'Hong Kong Dollar',
code: 'HKD',
},
{
name: 'New Zealand Dollar',
code: 'NZD',
},
],
onCreated() {
this.type = new ReactiveVar(
this.data().type ? this.data().type : this._types[0],
);
this.currencyCode = new ReactiveVar(
this.data().settings && this.data().settings.currencyCode
? this.data().settings.currencyCode
: this._currencyList[0].code,
);
this.dropdownItems = new ReactiveVar(
this.data().settings && this.data().settings.dropdownItems
? this.data().settings.dropdownItems
@@ -44,6 +94,18 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
return this.type.get() !== type;
},
getCurrencyCodes() {
const currentCode = this.currencyCode.get();
return this._currencyList.map(({ name, code }) => {
return {
name: `${code} - ${name}`,
value: code,
selected: code === currentCode,
};
});
},
getDropdownItems() {
const items = this.dropdownItems.get();
Array.from(this.findAll('.js-field-settings-dropdown input')).forEach(
@@ -62,6 +124,11 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
getSettings() {
const settings = {};
switch (this.type.get()) {
case 'currency': {
const currencyCode = this.currencyCode.get();
settings.currencyCode = currencyCode;
break;
}
case 'dropdown': {
const dropdownItems = this.getDropdownItems().filter(
item => !!item.name.trim(),
@@ -80,6 +147,10 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
const value = evt.target.value;
this.type.set(value);
},
'change .js-field-currency'(evt) {
const value = evt.target.value;
this.currencyCode.set(value);
},
'keydown .js-dropdown-item.last'(evt) {
if (evt.target.value.trim() && evt.keyCode === 13) {
const items = this.getDropdownItems();
+3 -2
View File
@@ -157,8 +157,9 @@ Template.disambiguateMultiMemberPopup.events({
});
Template.moveSelectionPopup.events({
'click .js-select-list'() {
mutateSelectedCards('move', this._id);
'click .js-select-list'(event) {
// Move the minicard to the end of the target list
mutateSelectedCards('moveToEndOfList', { listId: this._id });
EscapeActions.executeUpTo('multiselection');
},
});
@@ -11,7 +11,8 @@ template(name="swimlaneHeader")
template(name="swimlaneFixedHeader")
.swimlane-header(
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
= title
+viewer
= title
.swimlane-header-menu
unless currentUser.isCommentOnly
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon
+1 -1
View File
@@ -2,7 +2,7 @@ template(name="swimlane")
.swimlane
+swimlaneHeader
unless collapseSwimlane
.swimlane.js-lists.js-swimlane
.swimlane.js-lists.js-swimlane(id="swimlane-{{_id}}")
if isMiniScreen
if currentListIsInThisSwimlane _id
+list(currentList)
+1 -1
View File
@@ -23,8 +23,8 @@ function currentCardIsInThisList(listId, swimlaneId) {
currentCard.listId === listId &&
currentCard.swimlaneId === swimlaneId
);
// Default view: board-view-lists
else return currentCard && currentCard.listId === listId;
// https://github.com/wekan/wekan/issues/1623
// https://github.com/ChronikEwok/wekan/commit/cad9b20451bb6149bfb527a99b5001873b06c3de
// TODO: In public board, if you would like to switch between List/Swimlane view, you could
+2 -2
View File
@@ -39,8 +39,8 @@
};
return this.each(function() {
const element = this;
$(element).bind('dragenter dragover dragleave', stopFn);
return $(element).bind('drop', function(event) {
$(element).on('dragenter dragover dragleave', stopFn);
return $(element).on('drop', function(event) {
stopFn(event);
const files = event.dataTransfer.files;
for (let i = 0; i < files.length; i++) {
+38 -31
View File
@@ -1,8 +1,8 @@
const JSZip = require('jszip');
window.ExportHtml = (Popup) => {
window.ExportHtml = Popup => {
const saveAs = function(blob, filename) {
let dl = document.createElement('a');
const dl = document.createElement('a');
dl.href = window.URL.createObjectURL(blob);
dl.onclick = event => document.body.removeChild(event.target);
dl.style.display = 'none';
@@ -12,21 +12,22 @@ window.ExportHtml = (Popup) => {
dl.click();
};
const asyncForEach = async function (array, callback) {
const asyncForEach = async function(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
};
const getPageHtmlString = () => {
return `<!doctype html>${
window.document.querySelector('html').outerHTML
}`;
return `<!doctype html>${window.document.querySelector('html').outerHTML}`;
};
const removeAnchors = htmlString => {
const replaceOpenAnchor = htmlString.replace(new RegExp('<a ', 'gim'), '<span ');
return replaceOpenAnchor.replace(new RegExp('<\/a', 'gim'), '</span');
const replaceOpenAnchor = htmlString.replace(
new RegExp('<a ', 'gim'),
'<span ',
);
return replaceOpenAnchor.replace(new RegExp('</a', 'gim'), '</span');
};
const ensureSidebarRemoved = () => {
@@ -83,9 +84,8 @@ window.ExportHtml = (Popup) => {
elem.src = elem.src;
});
Array.from(document.querySelectorAll('.is-editable')).forEach(elem => {
elem.classList.remove('is-editable')
})
elem.classList.remove('is-editable');
});
};
const getBoardSlug = () => {
@@ -104,7 +104,8 @@ window.ExportHtml = (Popup) => {
const responseBody = await response.text();
const finalResponse = responseBody.replace(
new RegExp('packages\/[^\/]+\/upstream\/', 'gim'), '../'
new RegExp('packages/[^/]+/upstream/', 'gim'),
'../',
);
const filename = elem.href
@@ -138,30 +139,33 @@ window.ExportHtml = (Popup) => {
};
const removeCssUrlSurround = url => {
const working = url || "";
const working = url || '';
return working
.split("url(")
.join("")
.split("\")")
.join("")
.split("\"")
.join("")
.split('url(')
.join('')
.split('")')
.join('')
.split('"')
.join('')
.split("')")
.join("")
.join('')
.split("'")
.join("")
.split(")")
.join("");
.join('')
.split(')')
.join('');
};
const getCardCovers = () => {
return Array.from(document.querySelectorAll('.minicard-cover'))
.filter(elem => elem.style['background-image'])
}
return Array.from(document.querySelectorAll('.minicard-cover')).filter(
elem => elem.style['background-image'],
);
};
const downloadCardCovers = async (elements, zip, boardSlug) => {
await asyncForEach(elements, async elem => {
const response = await fetch(removeCssUrlSurround(elem.style['background-image']));
const response = await fetch(
removeCssUrlSurround(elem.style['background-image']),
);
const responseBody = await response.blob();
const filename = removeCssUrlSurround(elem.style['background-image'])
.split('/')
@@ -179,9 +183,12 @@ window.ExportHtml = (Popup) => {
const addBoardHTMLToZip = (boardSlug, zip) => {
ensureSidebarRemoved();
const htmlOutputPath = `${boardSlug}/index.html`;
zip.file(htmlOutputPath, new Blob([
removeAnchors(getPageHtmlString())
], { type: 'application/html' }));
zip.file(
htmlOutputPath,
new Blob([removeAnchors(getPageHtmlString())], {
type: 'application/html',
}),
);
};
return async () => {
@@ -202,5 +209,5 @@ window.ExportHtml = (Popup) => {
const content = await zip.generateAsync({ type: 'blob' });
saveAs(content, `${boardSlug}.zip`);
window.location.reload();
}
};
};
+1 -1
View File
@@ -35,7 +35,7 @@
options = $.extend({}, defaults, options);
return this.each(function() {
const element = this;
return $(element).bind('paste', function(event) {
return $(element).on('paste', function(event) {
const types = event.clipboardData.types;
const items = event.clipboardData.items;
for (let i = 0; i < types.length; i++) {
+35 -35
View File
@@ -6,12 +6,18 @@ Utils = {
currentUser = Meteor.user();
if (currentUser) {
Meteor.user().setBoardView(view);
} else if (view === 'board-view-lists') {
cookies.set('boardView', 'board-view-lists'); //true
} else if (view === 'board-view-swimlanes') {
cookies.set('boardView', 'board-view-swimlanes'); //true
location.reload();
} else if (view === 'board-view-lists') {
cookies.set('boardView', 'board-view-lists'); //true
location.reload();
} else if (view === 'board-view-cal') {
cookies.set('boardView', 'board-view-cal'); //true
location.reload();
} else {
cookies.set('boardView', 'board-view-swimlanes'); //true
location.reload();
}
},
@@ -24,14 +30,16 @@ Utils = {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).boardView;
} else if (cookies.get('boardView') === 'board-view-lists') {
return 'board-view-lists';
} else if (cookies.get('boardView') === 'board-view-swimlanes') {
return 'board-view-swimlanes';
} else if (cookies.get('boardView') === 'board-view-lists') {
return 'board-view-lists';
} else if (cookies.get('boardView') === 'board-view-cal') {
return 'board-view-cal';
} else {
return false;
cookies.set('boardView', 'board-view-swimlanes'); //true
location.reload();
return 'board-view-swimlanes';
}
},
@@ -61,38 +69,30 @@ Utils = {
},
MAX_IMAGE_PIXEL: Meteor.settings.public.MAX_IMAGE_PIXEL,
COMPRESS_RATIO: Meteor.settings.public.IMAGE_COMPRESS_RATIO,
processUploadedAttachment(card, fileObj, callbacks) {
if (!card) {
return onUploaded();
}
let settings = {
file: fileObj,
streams: 'dynamic',
chunkSize: 'dynamic',
};
settings.meta = {
uploading: true
};
if (card.isLinkedCard()) {
settings.meta.boardId = Cards.findOne(card.linkedId).boardId;
settings.meta.cardId = card.linkedId;
} else {
settings.meta.boardId = card.boardId;
settings.meta.swimlaneId = card.swimlaneId;
settings.meta.listId = card.listId;
settings.meta.cardId = card._id;
}
settings.meta.userId = Meteor.userId();
if (typeof callbacks === 'function') {
settings.onEnd = callbacks;
} else {
for (const key in callbacks) {
if (key.substring(0, 2) === 'on') {
settings[key] = callbacks[key];
}
processUploadedAttachment(card, fileObj, callback) {
const next = attachment => {
if (typeof callback === 'function') {
callback(attachment);
}
};
if (!card) {
return next();
}
Attachments.insert(settings);
const file = new FS.File(fileObj);
if (card.isLinkedCard()) {
file.boardId = Cards.findOne(card.linkedId).boardId;
file.cardId = card.linkedId;
} else {
file.boardId = card.boardId;
file.swimlaneId = card.swimlaneId;
file.listId = card.listId;
file.cardId = card._id;
}
file.userId = Meteor.userId();
if (file.original) {
file.original.name = fileObj.name;
}
return next(Attachments.insert(file));
},
shrinkImage(options) {
// shrink image to certain size
+2 -2
View File
@@ -115,11 +115,11 @@ services:
# NOTE: Quay is currently not updated, use Docker Hub image below c)
# a) For Wekan Meteor 1.8.x version at master branch,
# using https://quay.io/wekan/wekan automatic builds
image: quay.io/wekan/wekan
#image: quay.io/wekan/wekan
# b) Using specific Meteor 1.6.x version tag:
# image: quay.io/wekan/wekan:v1.95
# c) Using Docker Hub automatic builds https://hub.docker.com/r/wekanteam/wekan
#image: wekanteam/wekan
image: wekanteam/wekan
# image: wekanteam/wekan:v2.99
#-------------------------------------------------------------------------------------
container_name: wekan-app
+2
View File
@@ -256,6 +256,8 @@
"current": "الحالي",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "تاريخ",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "сегашен",
"custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.",
"custom-field-checkbox": "Чекбокс",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Дата",
"custom-field-dropdown": "Падащо меню",
"custom-field-dropdown-none": "(няма)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Data",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "Aktuální",
"custom-field-delete-pop": "Nelze vrátit zpět. Toto odebere toto vlastní pole ze všech karet a zničí jeho historii.",
"custom-field-checkbox": "Checkbox",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Datum",
"custom-field-dropdown": "Rozbalovací seznam",
"custom-field-dropdown-none": "(prázdné)",
+2
View File
@@ -256,6 +256,8 @@
"current": "nuværende",
"custom-field-delete-pop": "Du kan ikke fortryde handlingen. Dette vil fjerne dette brugerdefinerede felt fra alle kort og tilintetgøre dens historik.",
"custom-field-checkbox": "Afkrydsningsfelt",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Dato",
"custom-field-dropdown": "Rullegardinliste",
"custom-field-dropdown-none": "(ingen)",
+2
View File
@@ -256,6 +256,8 @@
"current": "aktuell",
"custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.",
"custom-field-checkbox": "Kontrollkästchen",
"custom-field-currency": "Währung",
"custom-field-currency-option": "Währungszeichen",
"custom-field-date": "Datum",
"custom-field-dropdown": "Dropdownliste",
"custom-field-dropdown-none": "(keiner)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Ημερομηνία",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Dato",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Fecha",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(ninguno)",
+2
View File
@@ -256,6 +256,8 @@
"current": "actual",
"custom-field-delete-pop": "Se eliminará este campo personalizado de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.",
"custom-field-checkbox": "Casilla de verificación",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Fecha",
"custom-field-dropdown": "Lista desplegable",
"custom-field-dropdown-none": "(nada)",
+2
View File
@@ -256,6 +256,8 @@
"current": "actual",
"custom-field-delete-pop": "Se eliminará este campo personalizado de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.",
"custom-field-checkbox": "Casilla de verificación",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Fecha",
"custom-field-dropdown": "Lista desplegable",
"custom-field-dropdown-none": "(nada)",
+2
View File
@@ -256,6 +256,8 @@
"current": "unekoa",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Data",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "جاری",
"custom-field-delete-pop": "این اقدام فیلدشخصی را بهمراه تمامی تاریخچه آن از کارت ها پاک می کند و برگشت پذیر نمی باشد",
"custom-field-checkbox": "جعبه انتخابی",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "تاریخ",
"custom-field-dropdown": "لیست افتادنی",
"custom-field-dropdown-none": "(هیچ)",
+2
View File
@@ -256,6 +256,8 @@
"current": "nykyinen",
"custom-field-delete-pop": "Tätä ei voi peruuttaa. Tämä poistaa tämän mukautetun kentän kaikista korteista ja poistaa sen historian.",
"custom-field-checkbox": "Valintaruutu",
"custom-field-currency": "Valuutta",
"custom-field-currency-option": "Valuutta koodi",
"custom-field-date": "Päivämäärä",
"custom-field-dropdown": "Pudotusvalikko",
"custom-field-dropdown-none": "(ei mitään)",
+2
View File
@@ -256,6 +256,8 @@
"current": "actuel",
"custom-field-delete-pop": "Cette action n'est pas réversible. Elle supprimera ce champ personnalisé de toutes les cartes et détruira son historique.",
"custom-field-checkbox": "Case à cocher",
"custom-field-currency": "Devise",
"custom-field-currency-option": "Code devise",
"custom-field-date": "Date",
"custom-field-dropdown": "Liste de choix",
"custom-field-dropdown-none": "(aucun)",
+2
View File
@@ -256,6 +256,8 @@
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Data",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "נוכחי",
"custom-field-delete-pop": "אין אפשרות לבטל את הפעולה. הפעולה תסיר את השדה שהותאם אישית מכל הכרטיסים ותשמיד את ההיסטוריה שלו.",
"custom-field-checkbox": "תיבת סימון",
"custom-field-currency": "מטבע",
"custom-field-currency-option": "קוד מטבע",
"custom-field-date": "תאריך",
"custom-field-dropdown": "רשימה נגללת",
"custom-field-dropdown-none": "(ללא)",
+52 -50
View File
@@ -17,14 +17,14 @@
"act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-editComment": "कार्ड पर संपादित टिप्पणी __card__: __comment__ सूची में __list__ स्विमलेन में __swimlane__ बोर्ड पर __board__",
"act-deleteComment": "कार्ड पर हटाई गई टिप्पणी __card__: __comment__ सूची में __list__ स्विमलेन में __swimlane__ बोर्ड पर __board__",
"act-createBoard": "created board __board__",
"act-createSwimlane": "created swimlane __swimlane__ to board __board__",
"act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
"act-createCustomField": "created custom field __customField__ at board __board__",
"act-deleteCustomField": "deleted custom field __customField__ at board __board__",
"act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-createCustomField": "बोर्ड पर कस्टम फ़ील्ड __customField__ __board__",
"act-deleteCustomField": "बोर्ड पर कस्टम फ़ील्ड __customField__ हटा दिया गया __board__",
"act-setCustomField": "कस्टम फ़ील्ड __customField__ संपादित करें: __customFieldValue__ कार्ड पर __card__ सूची में __list__ स्विमलेन __swimlane__ पर बोर्ड __board__",
"act-createList": "added list __list__ to board __board__",
"act-addBoardMember": "added member __member__ to board __board__",
"act-archivedBoard": "Board __board__ moved to Archive",
@@ -64,7 +64,7 @@
"activity-unchecked-item": "अचिह्नित %s अंदर में चिह्नांकन-सूची %s of %s",
"activity-checklist-added": "संकलित चिह्नांकन-सूची तक %s",
"activity-checklist-removed": "हटा दिया एक चिह्नांकन-सूची से %s",
"activity-checklist-completed": "completed checklist %s of %s",
"activity-checklist-completed": "पूरी जाँच सूची%sकी %s",
"activity-checklist-uncompleted": "अपूर्ण चिह्नांकन-सूची %s of %s",
"activity-checklist-item-added": "संकलित चिह्नांकन-सूची विषय तक '%s' अंदर में %s",
"activity-checklist-item-removed": "हटा दिया एक चिह्नांकन-सूची विषय से '%s' अंदर में %s",
@@ -73,8 +73,8 @@
"activity-unchecked-item-card": "अचिह्नित %s अंदर में चिह्नांकन-सूची %s",
"activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"activity-checklist-uncompleted-card": "अपूर्ण चिह्नांकन-सूची %s",
"activity-editComment": "edited comment %s",
"activity-deleteComment": "deleted comment %s",
"activity-editComment": "संपादित टिप्पणी",
"activity-deleteComment": "टिप्पणी हटा दी गई",
"add-attachment": "संलग्न करें",
"add-board": "बोर्ड जोड़ें",
"add-card": "कार्ड जोड़ें",
@@ -137,7 +137,7 @@
"board-view": "बोर्ड दृष्टिकोण",
"board-view-cal": "तिथि-पत्र",
"board-view-swimlanes": "तैरना",
"board-view-collapse": "Collapse",
"board-view-collapse": "संक्षिप्त करें",
"board-view-lists": "सूचियाँ",
"bucket-example": "उदाहरण के लिए “बाल्टी सूची” की तरह",
"cancel": "रद्द करें",
@@ -161,18 +161,18 @@
"cardAttachmentsPopup-title": "से अनुलग्न करें",
"cardCustomField-datePopup-title": "तारीख बदलें",
"cardCustomFieldsPopup-title": "संपादित करें प्रचलन क्षेत्र",
"cardStartVotingPopup-title": "Start a vote",
"positiveVoteMembersPopup-title": "Proponents",
"negativeVoteMembersPopup-title": "Opponents",
"card-edit-voting": "Edit voting",
"editVoteEndDatePopup-title": "Change vote end date",
"allowNonBoardMembers": "Allow all logged in users",
"vote-question": "Voting question",
"vote-public": "Show who voted what",
"vote-for-it": "for it",
"vote-against": "against",
"deleteVotePopup-title": "Delete vote?",
"vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.",
"cardStartVotingPopup-title": "वोट शुरू करें",
"positiveVoteMembersPopup-title": "समर्थकों का",
"negativeVoteMembersPopup-title": "प्रतिद्वंद्वी",
"card-edit-voting": "मतदान संपादित करें",
"editVoteEndDatePopup-title": "मतदान समाप्ति की तारीख बदलें",
"allowNonBoardMembers": "सभी लॉग इन उपयोगकर्ताओं को अनुमति दें",
"vote-question": "मतदान का सवाल",
"vote-public": "दिखाएँ कि किसने क्या मतदान किया",
"vote-for-it": "इसके लिए",
"vote-against": "के प्रतिकूल",
"deleteVotePopup-title": "मतदान हटाएं?",
"vote-delete-pop": "हटाना स्थायी है। आप इस मतदान से जुड़े सभी कार्यों को खो देंगे।",
"cardDeletePopup-title": "मिटाएँ कार्ड?",
"cardDetailsActionsPopup-title": "कार्ड क्रियाएँ",
"cardLabelsPopup-title": "नामपत्र",
@@ -235,8 +235,8 @@
"comment-only-desc": "केवल कार्ड पर टिप्पणी कर सकते हैं।",
"no-comments": "कोई टिप्पणी नहीं",
"no-comments-desc": "टिप्पणियां और गतिविधियां नहीं देख पा रहे हैं।",
"worker": "Worker",
"worker-desc": "Can only move cards, assign itself to card and comment.",
"worker": "कामगार",
"worker-desc": "केवल कार्ड ले जा सकते हैं, खुद को कार्ड और टिप्पणी करने के लिए असाइन करें।",
"computer": "संगणक",
"confirm-subtask-delete-dialog": "क्या आप वाकई उपकार्य हटाना चाहते हैं?",
"confirm-checklist-delete-dialog": "क्या आप वाकई जांचसूची हटाना चाहते हैं?",
@@ -256,6 +256,8 @@
"current": "वर्तमान",
"custom-field-delete-pop": "कोई पूर्ववत् नहीं है । यह सभी कार्ड से इस कस्टम क्षेत्र को हटा दें और इसके इतिहास को नष्ट कर देगा ।",
"custom-field-checkbox": "निशानबक्से",
"custom-field-currency": "मुद्रा",
"custom-field-currency-option": "मुद्रा संहिता",
"custom-field-date": "दिनांक",
"custom-field-dropdown": "ड्रॉपडाउन सूची",
"custom-field-dropdown-none": "(कोई नहीं)",
@@ -309,7 +311,7 @@
"error-board-notAMember": "You need तक be एक सदस्य of यह बोर्ड तक do that",
"error-json-malformed": "Your text is not valid JSON",
"error-json-schema": "आपके JSON डेटा में सही प्रारूप में सही जानकारी शामिल नहीं है",
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
"error-csv-schema": "आपके CSV (कोमा सेपरेटेड वैल्यूज़) / TSV (टैब सेपरेटेड वैल्यूज़) में सही फॉर्मेट में उचित जानकारी शामिल नहीं है",
"error-list-doesNotExist": "यह सूची does not exist",
"error-user-doesNotExist": "यह user does not exist",
"error-user-notAllowSelf": "You can not invite yourself",
@@ -317,30 +319,30 @@
"error-username-taken": "यह username is already taken",
"error-email-taken": "Email has already been taken",
"export-board": "Export बोर्ड",
"export-board-json": "Export board to JSON",
"export-board-csv": "Export board to CSV",
"export-board-tsv": "Export board to TSV",
"export-board-html": "Export board to HTML",
"export-board-json": "JSON को निर्यात बोर्ड",
"export-board-csv": "निर्यात बोर्ड को सी.एस.वी.",
"export-board-tsv": "टी.एस.वी. को निर्यात बोर्ड",
"export-board-html": "HTML को निर्यात बोर्ड",
"exportBoardPopup-title": "Export बोर्ड",
"sort": "Sort",
"sort-desc": "Click to Sort List",
"list-sort-by": "Sort the List By:",
"list-label-modifiedAt": "Last Access Time",
"list-label-title": "Name of the List",
"list-label-sort": "Your Manual Order",
"list-label-short-modifiedAt": "(L)",
"list-label-short-title": "(N)",
"list-label-short-sort": "(M)",
"sort": "भांति",
"sort-desc": "क्रमबद्ध सूची पर क्लिक करें",
"list-sort-by": "सूची क्रमबद्ध करें:",
"list-label-modifiedAt": "अंतिम पहुंच समय",
"list-label-title": "सूची का नाम",
"list-label-sort": "आपका नियमावलीआदेश",
"list-label-short-modifiedAt": "(बड़ा)",
"list-label-short-title": "(साधारण)",
"list-label-short-sort": "(मध्यम)",
"filter": "Filter",
"filter-cards": "Filter Cards or Lists",
"list-filter-label": "Filter List by Title",
"filter-cards": "निस्पंदन पत्ते या सूची",
"list-filter-label": "शीर्षक द्वारा निस्पंदन सूची",
"filter-clear": "Clear filter",
"filter-no-label": "No label",
"filter-no-member": "No सदस्य",
"filter-no-assignee": "No assignee",
"filter-no-assignee": "कोई अभिहस्तांकिती नहीं",
"filter-no-custom-fields": "No प्रचलन क्षेत्र",
"filter-show-archive": "Show archived lists",
"filter-hide-empty": "Hide empty lists",
"filter-show-archive": "संग्रहित सूची दिखाएं",
"filter-hide-empty": "खाली सूची छिपाएं",
"filter-on": "Filter is on",
"filter-on-desc": "You are filtering कार्ड इस पर बोर्ड. Click here तक संपादित करें filter.",
"filter-to-selection": "Filter तक selection",
@@ -357,16 +359,16 @@
"import-board-c": "Import बोर्ड",
"import-board-title-trello": "Import बोर्ड से Trello",
"import-board-title-wekan": "Import board from previous export",
"import-board-title-csv": "Import board from CSV/TSV",
"import-board-title-csv": "CSV / TSV से आयात बोर्ड",
"from-trello": "From Trello",
"from-wekan": "From previous export",
"from-csv": "From CSV/TSV",
"from-csv": "CSV / TSV से",
"import-board-instruction-trello": "In your Trello बोर्ड, go तक 'Menu', then 'More', 'Print और Export', 'Export JSON', और copy the resulting text.",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .",
"import-board-instruction-csv": "अपने कोमा सेपरेटेड वैल्यू (CSV) / ​​टैब सेपरेटेड वैल्यू (TSV) में पेस्ट करें।",
"import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
"import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.",
"import-json-placeholder": "Paste your valid JSON data here",
"import-csv-placeholder": "Paste your valid CSV/TSV data here",
"import-csv-placeholder": "अपना वैध CSV / TSV सामग्री यहां पेस्ट करें",
"import-map-members": "Map सदस्य",
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
"import-show-user-mapping": "Re आलोकन सदस्य mapping",
@@ -399,7 +401,7 @@
"swimlaneActionPopup-title": "तैरन Actions",
"swimlaneAddPopup-title": "Add a Swimlane below",
"listImportCardPopup-title": "Import एक Trello कार्ड",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listImportCardsTsvPopup-title": "एक्सेल CSV / TSV आयात करें",
"listMorePopup-title": "More",
"link-list": "Link तक यह list",
"list-delete-pop": "All actions हो जाएगा हटा दिया से the activity feed और you won't be able तक recover the list. There is no undo.",
@@ -462,7 +464,7 @@
"save": "Save",
"search": "Search",
"rules": "Rules",
"search-cards": "Search from card/list titles, descriptions and custom fields on this board",
"search-cards": "इस बोर्ड पर कार्ड / सूची शीर्षक, विवरण और आदत क्षेत्र से खोजें",
"search-example": "Text तक search for?",
"select-color": "Select Color",
"set-wip-limit-value": "Set एक limit for the maximum number of tasks अंदर में यह list",
@@ -546,8 +548,8 @@
"email-smtp-test-text": "You have successfully प्रेषित an email",
"error-invitation-code-not-exist": "Invitation code doesn't exist",
"error-notAuthorized": "You are not authorized तक आलोकन यह page.",
"webhook-title": "Webhook Name",
"webhook-token": "Token (Optional for Authentication)",
"webhook-title": "वेबहुक नाम",
"webhook-token": "टोकन (प्रमाणीकरण के लिए वैकल्पिक)",
"outgoing-webhooks": "Outgoing Webhooks",
"bidirectional-webhooks": "Two-Way Webhooks",
"outgoingWebhooksPopup-title": "Outgoing Webhooks",
+2
View File
@@ -256,6 +256,8 @@
"current": "jelenlegi",
"custom-field-delete-pop": "Nincs visszavonás. Ez el fogja távolítani az egyéni mezőt az összes kártyáról, és megsemmisíti az előzményeit.",
"custom-field-checkbox": "Jelölőnégyzet",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Dátum",
"custom-field-dropdown": "Legördülő lista",
"custom-field-dropdown-none": "(nincs)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "sekarang",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Tanggal",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "corrente",
"custom-field-delete-pop": "Non potrai tornare indietro. Questa azione rimuoverà questo campo personalizzato da tutte le schede ed eliminerà ogni sua traccia.",
"custom-field-checkbox": "Casella di scelta",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Data",
"custom-field-dropdown": "Lista a discesa",
"custom-field-dropdown-none": "(niente)",
+11 -9
View File
@@ -166,7 +166,7 @@
"negativeVoteMembersPopup-title": "反対者",
"card-edit-voting": "投票を編集",
"editVoteEndDatePopup-title": "投票終了日の変更",
"allowNonBoardMembers": "Allow all logged in users",
"allowNonBoardMembers": "ログイン中のすべてのユーザーに許可",
"vote-question": "投票の質問事項",
"vote-public": "誰が何に投票したか表示",
"vote-for-it": "賛成",
@@ -256,6 +256,8 @@
"current": "現在",
"custom-field-delete-pop": "この操作は取り消しできません。このカスタムフィールドはすべてのカードから外され履歴からも見えなくなります。",
"custom-field-checkbox": "チェックボックス",
"custom-field-currency": "通貨",
"custom-field-currency-option": "通貨コード",
"custom-field-date": "日付",
"custom-field-dropdown": "ドロップダウンリスト",
"custom-field-dropdown-none": "(none)",
@@ -360,9 +362,9 @@
"import-board-title-csv": "ボードをCSV/TSVからインポート",
"from-trello": "Trelloから",
"from-wekan": "以前のエクスポートから",
"from-csv": "From CSV/TSV",
"from-csv": "CSV/TSV から",
"import-board-instruction-trello": "Trelloボードの、 'Menu' → 'More' → 'Print and Export' → 'Export JSON'を選択し、テキストをコピーしてください。",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .",
"import-board-instruction-csv": "カンマ区切り(CSV) / タブ区切り(TSV) ファイルに貼り付けてください。",
"import-board-instruction-wekan": "ボードの、 'メニュー' → 'ボードのエクスポート'を選択し、ダウンロードされたファイルの中のテキストをコピーしてください。",
"import-board-instruction-about-errors": "ボードのインポート中にエラーが発生した場合、インポートがまだ進行中のまま、全てのボードページに表示されている場合があります。",
"import-json-placeholder": "JSONデータをここに貼り付けする",
@@ -639,7 +641,7 @@
"r-when-a-card": "カード:",
"r-is": "が",
"r-is-moved": "が移動された時",
"r-added-to": "Added to",
"r-added-to": "次に追加された時",
"r-removed-from": "次から削除された時",
"r-the-board": "ボード:",
"r-list": "リスト:",
@@ -798,13 +800,13 @@
"friday": "金曜",
"saturday": "土曜",
"sunday": "日曜",
"status": "Status",
"status": "ステータス",
"swimlane": "スイムレーン",
"owner": "オーナー",
"last-modified-at": "Last modified at",
"last-activity": "Last activity",
"last-modified-at": "最終更新",
"last-activity": "最終アクティビティ",
"voting": "投票",
"archived": "アーカイブ",
"delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has",
"delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list"
"delete-linked-card-before-this-card": "カード内にある、リンクされているカードを削除しなければ、このカードは削除できません",
"delete-linked-cards-before-this-list": "リスト内にある、他のカードを参照しているカードを削除しなければ、このリストは削除できません"
}
+2
View File
@@ -256,6 +256,8 @@
"current": "მიმდინარე",
"custom-field-delete-pop": "ქმედება გამოიწვევს მომხმარებლის ველის წაშლას ყველა ბარათიდან და გაანადგურებს მის ისტორიას, რის შემდეგაც შეუძლებელი იქნება მისი უკან დაბრუნება. ",
"custom-field-checkbox": "მოსანიშნი გრაფა",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "თარიღი",
"custom-field-dropdown": "ჩამოსაშლელი სია",
"custom-field-dropdown-none": "(ცარიელი)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "경향",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "날짜",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "сегашен",
"custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.",
"custom-field-checkbox": "Чекбокс",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Дата",
"custom-field-dropdown": "Падащо меню",
"custom-field-dropdown-none": "(няма)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Dato",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "Huidige",
"custom-field-delete-pop": "Er is geen herstelmogelijkheid. Deze actie zal dit maatwerkveld van alle kaarten verwijderen en de bijbehorende historie wissen.",
"custom-field-checkbox": "Checkbox",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Datum",
"custom-field-dropdown": "Dropdown Lijst",
"custom-field-dropdown-none": "(geen)",
+2
View File
@@ -256,6 +256,8 @@
"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": "Casa de croiar",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Data",
"custom-field-dropdown": "Tièra de causidas",
"custom-field-dropdown-none": "(pas res)",
+2
View File
@@ -256,6 +256,8 @@
"current": "obecny",
"custom-field-delete-pop": "Nie ma możliwości wycofania tej operacji. To usunie te niestandardowe pole ze wszystkich kart oraz usunie ich całą historię.",
"custom-field-checkbox": "Pole wyboru",
"custom-field-currency": "Waluta",
"custom-field-currency-option": "Kod waluty",
"custom-field-date": "Data",
"custom-field-dropdown": "Lista rozwijana",
"custom-field-dropdown-none": "(puste)",
+4 -2
View File
@@ -256,6 +256,8 @@
"current": "atual",
"custom-field-delete-pop": "Não existe desfazer. Isso irá excluir o campo customizado de todos os cartões e destruir seu histórico",
"custom-field-checkbox": "Caixa de seleção",
"custom-field-currency": "Moeda",
"custom-field-currency-option": "Código de moeda",
"custom-field-date": "Data",
"custom-field-dropdown": "Lista suspensa",
"custom-field-dropdown-none": "(nada)",
@@ -805,6 +807,6 @@
"last-activity": "Última atividade",
"voting": "Votação",
"archived": "Arquivado",
"delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has",
"delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list"
"delete-linked-card-before-this-card": "Você não pode excluir este cartão antes de excluir primeiro o cartão vinculado que possui",
"delete-linked-cards-before-this-list": "Você não pode excluir esta lista antes de excluir primeiro os cartões vinculados que estão apontando para os cartões nesta lista"
}
+37 -35
View File
@@ -137,7 +137,7 @@
"board-view": "Visão do Quadro",
"board-view-cal": "Calendário",
"board-view-swimlanes": "Pistas",
"board-view-collapse": "Collapse",
"board-view-collapse": "Colapsar ",
"board-view-lists": "Listas",
"bucket-example": "\"Lista de Desejos\", por exemplo",
"cancel": "Cancelar",
@@ -164,14 +164,14 @@
"cardStartVotingPopup-title": "Start a vote",
"positiveVoteMembersPopup-title": "Proponents",
"negativeVoteMembersPopup-title": "Opponents",
"card-edit-voting": "Edit voting",
"card-edit-voting": "Alterar voto",
"editVoteEndDatePopup-title": "Change vote end date",
"allowNonBoardMembers": "Allow all logged in users",
"vote-question": "Voting question",
"vote-public": "Show who voted what",
"vote-for-it": "for it",
"vote-against": "against",
"deleteVotePopup-title": "Delete vote?",
"deleteVotePopup-title": "Apagar voto?",
"vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.",
"cardDeletePopup-title": "Apagar Cartão?",
"cardDetailsActionsPopup-title": "Acções do Cartão",
@@ -256,6 +256,8 @@
"current": "actual",
"custom-field-delete-pop": "Não existe desfazer. Isto irá remover este campo personalizado de todos os cartões e destruir o seu histórico",
"custom-field-checkbox": "Caixa de selecção",
"custom-field-currency": "Moeda",
"custom-field-currency-option": "Código de Moeda",
"custom-field-date": "Data",
"custom-field-dropdown": "Lista Suspensa",
"custom-field-dropdown-none": "(nada)",
@@ -322,18 +324,18 @@
"export-board-tsv": "Export board to TSV",
"export-board-html": "Export board to HTML",
"exportBoardPopup-title": "Exportar quadro",
"sort": "Sort",
"sort-desc": "Click to Sort List",
"list-sort-by": "Sort the List By:",
"sort": "Ordenar",
"sort-desc": "Clique para ordenar a Lista",
"list-sort-by": "Ordenar a Lista por:",
"list-label-modifiedAt": "Last Access Time",
"list-label-title": "Name of the List",
"list-label-title": "Nome da Lista",
"list-label-sort": "Your Manual Order",
"list-label-short-modifiedAt": "(L)",
"list-label-short-title": "(N)",
"list-label-short-sort": "(M)",
"filter": "Filtrar",
"filter-cards": "Filter Cards or Lists",
"list-filter-label": "Filter List by Title",
"filter-cards": "Filtrar Cartões ou Listas",
"list-filter-label": "Filtrar Lista por Título",
"filter-clear": "Limpar filtro",
"filter-no-label": "Sem etiquetas",
"filter-no-member": "Sem membros",
@@ -360,13 +362,13 @@
"import-board-title-csv": "Import board from CSV/TSV",
"from-trello": "Do Trello",
"from-wekan": "A partir de exportação prévia",
"from-csv": "From CSV/TSV",
"from-csv": "De CSV/TSV",
"import-board-instruction-trello": "No seu quadro do Trello, vá em 'Menu', depois em 'Mais', 'Imprimir e Exportar', 'Exportar JSON', e copie o texto resultante.",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .",
"import-board-instruction-wekan": "No seu quadro vá para 'Menu', depois 'Exportar quadro' e copie o texto no ficheiro descarregado.",
"import-board-instruction-about-errors": "Se receber erros ao importar o quadro, às vezes a importação ainda funciona e o quadro está na página Todos os Quadros.",
"import-json-placeholder": "Cole seus dados JSON válidos aqui",
"import-csv-placeholder": "Paste your valid CSV/TSV data here",
"import-csv-placeholder": "Copie aqui os seus dados CSV/TSV",
"import-map-members": "Mapear membros",
"import-members-map": "O seu quadro importado possui alguns membros. Por favor, mapeie os membros que deseja importar para seus utilizadores",
"import-show-user-mapping": "Rever mapeamento dos membros",
@@ -399,7 +401,7 @@
"swimlaneActionPopup-title": "Acções de Pista",
"swimlaneAddPopup-title": "Adicionar uma Pista abaixo",
"listImportCardPopup-title": "Importe um cartão do Trello",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listImportCardsTsvPopup-title": "Importar Excel CSV/TSV",
"listMorePopup-title": "Mais",
"link-list": "Ligar a esta lista",
"list-delete-pop": "Todas as acções serão removidas do feed de actividade e não poderá recuperar a lista. Não há como desfazer.",
@@ -639,7 +641,7 @@
"r-when-a-card": "Quando um cartão",
"r-is": "é",
"r-is-moved": "é movido",
"r-added-to": "Added to",
"r-added-to": "Adicionado a",
"r-removed-from": "Removido de",
"r-the-board": "o quadro",
"r-list": "lista",
@@ -687,7 +689,7 @@
"r-of-checklist": "da lista de verificação",
"r-send-email": "Enviar um e-mail",
"r-to": "para",
"r-of": "of",
"r-of": "de",
"r-subject": "assunto",
"r-rule-details": "Detalhes da regra",
"r-d-move-to-top-gen": "Mover cartão para o topo da sua lista",
@@ -778,33 +780,33 @@
"show-desktop-drag-handles": "Show desktop drag handles",
"assignee": "Assignee",
"cardAssigneesPopup-title": "Assignee",
"addmore-detail": "Add a more detailed description",
"addmore-detail": "Adicionar uma descrição mais detalhada",
"show-on-card": "Show on Card",
"new": "New",
"editUserPopup-title": "Edit User",
"newUserPopup-title": "New User",
"notifications": "Notifications",
"view-all": "View All",
"filter-by-unread": "Filter by Unread",
"mark-all-as-read": "Mark all as read",
"remove-all-read": "Remove all read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename",
"new": "Novo",
"editUserPopup-title": "Editar Utilizador",
"newUserPopup-title": "Novo Utilizador",
"notifications": "Notificações",
"view-all": "Ver Todos",
"filter-by-unread": "Filtrar por não lidos",
"mark-all-as-read": "Marcar todos como lidos",
"remove-all-read": "Apagar todos os lidos",
"allow-rename": "Permitir Renomear",
"allowRenamePopup-title": "Permitir Renomear",
"start-day-of-week": "Set day of the week start",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "Sunday",
"monday": "Segunda-feira",
"tuesday": "Terça-feira",
"wednesday": "Quarta-feira",
"thursday": "Quinta-feira",
"friday": "Sexta-feira",
"saturday": "Sábado",
"sunday": "Domingo",
"status": "Status",
"swimlane": "Swimlane",
"owner": "Owner",
"last-modified-at": "Last modified at",
"last-activity": "Last activity",
"last-modified-at": "Modificado pela última vez em",
"last-activity": "Última actividade",
"voting": "Voting",
"archived": "Archived",
"archived": "Arquivado",
"delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has",
"delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list"
}
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "текущий",
"custom-field-delete-pop": "Отменить нельзя. Это удалит настраиваемое поле со всех карт и уничтожит его историю.",
"custom-field-checkbox": "Галочка",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Дата",
"custom-field-dropdown": "Выпадающий список",
"custom-field-dropdown-none": "(нет)",
+2
View File
@@ -256,6 +256,8 @@
"current": "trenutno",
"custom-field-delete-pop": "Razveljavitve ni. To bo odstranilo to poljubno polje iz vseh kartic in izbrisalo njegovo zgodovino.",
"custom-field-checkbox": "Potrditveno polje",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Datum",
"custom-field-dropdown": "Spustni seznam",
"custom-field-dropdown-none": "(nobeno)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Datum",
"custom-field-dropdown": "Padajuća lista",
"custom-field-dropdown-none": "(ništa)",
+2
View File
@@ -256,6 +256,8 @@
"current": "aktuell",
"custom-field-delete-pop": "Det går inte att ångra. Detta tar bort det här anpassade fältet från alla kort och förstör dess historia.",
"custom-field-checkbox": "Kryssruta",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Datum",
"custom-field-dropdown": "Rullgardingsmeny",
"custom-field-dropdown-none": "(inga)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "நாள் ",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "ปัจจุบัน",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "วันที่",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "mevcut",
"custom-field-delete-pop": "Bunun geri dönüşü yoktur. Bu özel alan tüm kartlardan kaldırılıp tarihçesi yokedilecektir.",
"custom-field-checkbox": "İşaret kutusu",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Tarih",
"custom-field-dropdown": "Açılır liste",
"custom-field-dropdown-none": "(hiçbiri)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+27 -25
View File
@@ -164,15 +164,15 @@
"cardStartVotingPopup-title": "建立投票",
"positiveVoteMembersPopup-title": "支持",
"negativeVoteMembersPopup-title": "反对",
"card-edit-voting": "Edit voting",
"editVoteEndDatePopup-title": "Change vote end date",
"allowNonBoardMembers": "Allow all logged in users",
"card-edit-voting": "编辑投票",
"editVoteEndDatePopup-title": "修改投票截止日期",
"allowNonBoardMembers": "允许所有已登入账户",
"vote-question": "投票题目",
"vote-public": "查看投票结果",
"vote-for-it": "同意",
"vote-against": "反对",
"deleteVotePopup-title": "Delete vote?",
"vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.",
"deleteVotePopup-title": "删除投票?",
"vote-delete-pop": "永久删除。此操作会将所有数据删除掉。",
"cardDeletePopup-title": "彻底删除卡片?",
"cardDetailsActionsPopup-title": "卡片操作",
"cardLabelsPopup-title": "标签",
@@ -256,6 +256,8 @@
"current": "当前",
"custom-field-delete-pop": "没有撤销,此动作将从所有卡片中移除自定义字段并销毁历史。",
"custom-field-checkbox": "选择框",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "日期",
"custom-field-dropdown": "下拉列表",
"custom-field-dropdown-none": "(无)",
@@ -309,7 +311,7 @@
"error-board-notAMember": "需要成为看板成员才能执行此操作",
"error-json-malformed": "文本不是合法的 JSON",
"error-json-schema": "JSON 数据没有用正确的格式包含合适的信息",
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
"error-csv-schema": "CSV(逗号分隔值)/TSV(制表符分隔值)数据格式的不正确",
"error-list-doesNotExist": "不存在此列表",
"error-user-doesNotExist": "该用户不存在",
"error-user-notAllowSelf": "无法邀请自己",
@@ -317,10 +319,10 @@
"error-username-taken": "此用户名已存在",
"error-email-taken": "此EMail已存在",
"export-board": "导出看板",
"export-board-json": "Export board to JSON",
"export-board-csv": "Export board to CSV",
"export-board-tsv": "Export board to TSV",
"export-board-html": "Export board to HTML",
"export-board-json": "看板导出为JSON",
"export-board-csv": "看板导出为CSV",
"export-board-tsv": "看板导出为TSV",
"export-board-html": "看板导出为HTML",
"exportBoardPopup-title": "导出看板",
"sort": "排序",
"sort-desc": "点此来将列表排序",
@@ -357,16 +359,16 @@
"import-board-c": "导入看板",
"import-board-title-trello": "从Trello导入看板",
"import-board-title-wekan": "从以前的导出数据导入看板",
"import-board-title-csv": "Import board from CSV/TSV",
"import-board-title-csv": "导入CSV/TSV看板",
"from-trello": "自 Trello",
"from-wekan": "自以前的导出",
"from-csv": "From CSV/TSV",
"from-csv": "从CSV/TSV",
"import-board-instruction-trello": "在你的Trello看板中,点击“菜单”,然后选择“更多”,“打印与导出”,“导出为 JSON” 并拷贝结果文本",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .",
"import-board-instruction-csv": "粘贴逗号分隔值(CSV)/制表符分隔值(TSV)。",
"import-board-instruction-wekan": "在您的看板,点击“菜单”,然后“导出看板”,复制下载文件中的文本。",
"import-board-instruction-about-errors": "如果在导入看板时出现错误,导入工作可能仍然在进行中,并且看板已经出现在全部看板页。",
"import-json-placeholder": "粘贴您有效的 JSON 数据至此",
"import-csv-placeholder": "Paste your valid CSV/TSV data here",
"import-csv-placeholder": "在此处粘贴有效的CSV/TSV数据",
"import-map-members": "映射成员",
"import-members-map": "您导入的看板有一些成员,请映射这些成员到您导入的用户。",
"import-show-user-mapping": "核对成员映射",
@@ -399,7 +401,7 @@
"swimlaneActionPopup-title": "泳道图操作",
"swimlaneAddPopup-title": "在下面添加一个泳道",
"listImportCardPopup-title": "导入 Trello 卡片",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listImportCardsTsvPopup-title": "导入Excel CSV/TSV",
"listMorePopup-title": "更多",
"link-list": "关联到这个列表",
"list-delete-pop": "所有活动将被从活动动态中删除并且你无法恢复他们,此操作无法撤销。",
@@ -639,7 +641,7 @@
"r-when-a-card": "当一张卡片",
"r-is": "是",
"r-is-moved": "已经移动",
"r-added-to": "Added to",
"r-added-to": "添加到",
"r-removed-from": "已移除",
"r-the-board": "该看板",
"r-list": "列表",
@@ -798,13 +800,13 @@
"friday": "周五",
"saturday": "周六",
"sunday": "周日",
"status": "Status",
"swimlane": "Swimlane",
"owner": "Owner",
"last-modified-at": "Last modified at",
"last-activity": "Last activity",
"voting": "Voting",
"archived": "Archived",
"delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has",
"delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list"
"status": "状态",
"swimlane": "泳道",
"owner": "所有者",
"last-modified-at": "上次修改时间",
"last-activity": "上次活动",
"voting": "投票",
"archived": "存档",
"delete-linked-card-before-this-card": "在你首次删除卡片前你无法删除此选项卡片",
"delete-linked-cards-before-this-list": "在首先删除指向此列表中的卡的链接卡之前,不能删除此列表"
}
+2
View File
@@ -256,6 +256,8 @@
"current": "current",
"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-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "Date",
"custom-field-dropdown": "Dropdown List",
"custom-field-dropdown-none": "(none)",
+2
View File
@@ -256,6 +256,8 @@
"current": "目前",
"custom-field-delete-pop": "此操作將會從所有卡片中移除自訂欄位以及銷毀歷史紀錄,並且無法撤消。",
"custom-field-checkbox": "複選框",
"custom-field-currency": "Currency",
"custom-field-currency-option": "Currency Code",
"custom-field-date": "日期",
"custom-field-dropdown": "下拉式選單",
"custom-field-dropdown-none": "(無)",
+1 -1
View File
@@ -217,7 +217,7 @@ if (Meteor.isServer) {
}
if (activity.attachmentId) {
const attachment = activity.attachment();
params.attachment = attachment.name;
params.attachment = attachment.original.name;
params.attachmentId = attachment._id;
}
if (activity.checklistId) {
+239 -103
View File
@@ -1,127 +1,263 @@
import { FilesCollection } from 'meteor/ostrio:files';
const fs = require('fs');
const localFSStore = process.env.ATTACHMENTS_STORE_PATH;
const storeName = 'attachments';
const defaultStoreOptions = {
beforeWrite: fileObj => {
if (!fileObj.isImage()) {
return {
type: 'application/octet-stream',
};
}
return {};
},
};
let store;
if (localFSStore) {
// have to reinvent methods from FS.Store.GridFS and FS.Store.FileSystem
const fs = Npm.require('fs');
const path = Npm.require('path');
const mongodb = Npm.require('mongodb');
const Grid = Npm.require('gridfs-stream');
// calulate the absolute path here, because FS.Store.FileSystem didn't expose the aboslutepath or FS.Store didn't expose api calls :(
let pathname = localFSStore;
/*eslint camelcase: ["error", {allow: ["__meteor_bootstrap__"]}] */
const collectionName = 'attachments2';
if (!pathname && __meteor_bootstrap__ && __meteor_bootstrap__.serverDir) {
pathname = path.join(
__meteor_bootstrap__.serverDir,
`../../../cfs/files/${storeName}`,
);
}
Attachments = new FilesCollection({
storagePath: storagePath(),
debug: false,
// allowClientCode: true,
collectionName: 'attachments2',
onAfterUpload: onAttachmentUploaded,
onBeforeRemove: onAttachmentRemoving
if (!pathname)
throw new Error('FS.Store.FileSystem unable to determine path');
// Check if we have '~/foo/bar'
if (pathname.split(path.sep)[0] === '~') {
const homepath =
process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
if (homepath) {
pathname = pathname.replace('~', homepath);
} else {
throw new Error('FS.Store.FileSystem unable to resolve "~" in path');
}
}
// Set absolute path
const absolutePath = path.resolve(pathname);
const _FStore = new FS.Store.FileSystem(storeName, {
path: localFSStore,
...defaultStoreOptions,
});
const GStore = {
fileKey(fileObj) {
const key = {
_id: null,
filename: null,
};
// If we're passed a fileObj, we retrieve the _id and filename from it.
if (fileObj) {
const info = fileObj._getInfo(storeName, {
updateFileRecordFirst: false,
});
key._id = info.key || null;
key.filename =
info.name ||
fileObj.name({ updateFileRecordFirst: false }) ||
`${fileObj.collectionName}-${fileObj._id}`;
}
// If key._id is null at this point, createWriteStream will let GridFS generate a new ID
return key;
},
db: undefined,
mongoOptions: { useNewUrlParser: true },
mongoUrl: process.env.MONGO_URL,
init() {
this._init(err => {
this.inited = !err;
});
},
_init(callback) {
const self = this;
mongodb.MongoClient.connect(self.mongoUrl, self.mongoOptions, function(
err,
db,
) {
if (err) {
return callback(err);
}
self.db = db;
return callback(null);
});
return;
},
createReadStream(fileKey, options) {
const self = this;
if (!self.inited) {
self.init();
return undefined;
}
options = options || {};
// Init GridFS
const gfs = new Grid(self.db, mongodb);
// Set the default streamning settings
const settings = {
_id: new mongodb.ObjectID(fileKey._id),
root: `cfs_gridfs.${storeName}`,
};
// Check if this should be a partial read
if (
typeof options.start !== 'undefined' &&
typeof options.end !== 'undefined'
) {
// Add partial info
settings.range = {
startPos: options.start,
endPos: options.end,
};
}
return gfs.createReadStream(settings);
},
};
GStore.init();
const CRS = 'createReadStream';
const _CRS = `_${CRS}`;
const FStore = _FStore._transform;
FStore[_CRS] = FStore[CRS].bind(FStore);
FStore[CRS] = function(fileObj, options) {
let stream;
try {
const localFile = path.join(
absolutePath,
FStore.storage.fileKey(fileObj),
);
const state = fs.statSync(localFile);
if (state) {
stream = FStore[_CRS](fileObj, options);
}
} catch (e) {
// file is not there, try GridFS ?
stream = undefined;
}
if (stream) return stream;
else {
try {
const stream = GStore[CRS](GStore.fileKey(fileObj), options);
return stream;
} catch (e) {
return undefined;
}
}
}.bind(FStore);
store = _FStore;
} else {
store = new FS.Store.GridFS(localFSStore ? `G${storeName}` : storeName, {
// XXX Add a new store for cover thumbnails so we don't load big images in
// the general board view
// If the uploaded document is not an image we need to enforce browser
// download instead of execution. This is particularly important for HTML
// files that the browser will just execute if we don't serve them with the
// appropriate `application/octet-stream` MIME header which can lead to user
// data leaks. I imagine other formats (like PDF) can also be attack vectors.
// See https://github.com/wekan/wekan/issues/99
// XXX Should we use `beforeWrite` option of CollectionFS instead of
// collection-hooks?
// We should use `beforeWrite`.
...defaultStoreOptions,
});
}
Attachments = new FS.Collection('attachments', {
stores: [store],
});
if (Meteor.isServer) {
Meteor.startup(() => {
Attachments.collection._ensureIndex({ cardId: 1 });
Attachments.files._ensureIndex({ cardId: 1 });
});
// TODO: Permission related
Attachments.allow({
insert() {
return false;
insert(userId, doc) {
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
},
update() {
return true;
update(userId, doc) {
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
},
remove() {
return true;
}
});
Meteor.methods({
cloneAttachment(file, overrides) {
check(file, Object);
check(overrides, Match.Maybe(Object));
const path = file.path;
const opts = {
fileName: file.name,
type: file.type,
meta: file.meta,
userId: file.userId
};
for (let key in overrides) {
if (key === 'meta') {
for (let metaKey in overrides.meta) {
opts.meta[metaKey] = overrides.meta[metaKey];
}
} else {
opts[key] = overrides[key];
}
remove(userId, doc) {
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
},
// We authorize the attachment download either:
// - if the board is public, everyone (even unconnected) can download it
// - if the board is private, only board members can download it
download(userId, doc) {
const board = Boards.findOne(doc.boardId);
if (board.isPublic()) {
return true;
} else {
return board.hasMember(userId);
}
const buffer = fs.readFileSync(path);
Attachments.write(buffer, opts, (err, fileRef) => {
if (err) {
console.log('Error when cloning record', err);
}
},
fetch: ['boardId'],
});
}
// XXX Enforce a schema for the Attachments CollectionFS
if (Meteor.isServer) {
Attachments.files.after.insert((userId, doc) => {
// If the attachment doesn't have a source field
// or its source is different than import
if (!doc.source || doc.source !== 'import') {
// Add activity about adding the attachment
Activities.insert({
userId,
type: 'card',
activityType: 'addAttachment',
attachmentId: doc._id,
// this preserves the name so that notifications can be meaningful after
// this file is removed
attachmentName: doc.original.name,
boardId: doc.boardId,
cardId: doc.cardId,
listId: doc.listId,
swimlaneId: doc.swimlaneId,
});
return true;
} else {
// Don't add activity about adding the attachment as the activity
// be imported and delete source field
Attachments.update(
{
_id: doc._id,
},
{
$unset: {
source: '',
},
},
);
}
});
Meteor.publish(collectionName, function() {
return Attachments.find().cursor;
});
} else {
Meteor.subscribe(collectionName);
}
function storagePath(defaultPath) {
const storePath = process.env.ATTACHMENTS_STORE_PATH;
return storePath ? storePath : defaultPath;
}
function onAttachmentUploaded(fileRef) {
Attachments.update({_id:fileRef._id}, {$set: {"meta.uploading": false}});
if (!fileRef.meta.source || fileRef.meta.source !== 'import') {
// Add activity about adding the attachment
Attachments.files.before.remove((userId, doc) => {
Activities.insert({
userId: fileRef.userId,
userId,
type: 'card',
activityType: 'addAttachment',
attachmentId: fileRef._id,
activityType: 'deleteAttachment',
attachmentId: doc._id,
// this preserves the name so that notifications can be meaningful after
// this file is removed
attachmentName: fileRef.name,
boardId: fileRef.meta.boardId,
cardId: fileRef.meta.cardId,
listId: fileRef.meta.listId,
swimlaneId: fileRef.meta.swimlaneId,
// this file is removed
attachmentName: doc.original.name,
boardId: doc.boardId,
cardId: doc.cardId,
listId: doc.listId,
swimlaneId: doc.swimlaneId,
});
} else {
// Don't add activity about adding the attachment as the activity
// be imported and delete source field
Attachments.collection.update(
{
_id: fileRef._id,
},
{
$unset: {
'meta.source': '',
},
},
);
}
}
function onAttachmentRemoving(cursor) {
const file = cursor.get()[0];
const meta = file.meta;
Activities.insert({
userId: this.userId,
type: 'card',
activityType: 'deleteAttachment',
attachmentId: file._id,
// this preserves the name so that notifications can be meaningful after
// this file is removed
attachmentName: file.name,
boardId: meta.boardId,
cardId: meta.cardId,
listId: meta.listId,
swimlaneId: meta.swimlaneId,
});
return true;
}
export default Attachments;
+168 -65
View File
@@ -278,7 +278,7 @@ Cards.attachSchema(
*/
type: Number,
decimal: true,
defaultValue: '',
defaultValue: 0,
},
subtaskSort: {
/**
@@ -368,30 +368,36 @@ Cards.allow({
Cards.helpers({
copy(boardId, swimlaneId, listId) {
const oldBoard = Boards.findOne(this.boardId);
const oldBoardLabels = oldBoard.labels;
// Get old label names
const oldCardLabels = _.pluck(
_.filter(oldBoardLabels, label => {
return _.contains(this.labelIds, label._id);
}),
'name',
);
const newBoard = Boards.findOne(boardId);
const newBoardLabels = newBoard.labels;
const newCardLabels = _.pluck(
_.filter(newBoardLabels, label => {
return _.contains(oldCardLabels, label.name);
}),
'_id',
);
const oldId = this._id;
const oldCard = Cards.findOne(oldId);
// Copy Custom Fields
if (oldBoard._id !== boardId) {
// we must only copy the labels and custom fields if the target board
// differs from the source board
if (this.boardId !== boardId) {
const oldBoard = Boards.findOne(this.boardId);
const oldBoardLabels = oldBoard.labels;
// Get old label names
const oldCardLabels = _.pluck(
_.filter(oldBoardLabels, label => {
return _.contains(this.labelIds, label._id);
}),
'name',
);
const newBoard = Boards.findOne(boardId);
const newBoardLabels = newBoard.labels;
const newCardLabels = _.pluck(
_.filter(newBoardLabels, label => {
return _.contains(oldCardLabels, label.name);
}),
'_id',
);
// now set the new label ids
delete this.labelIds;
this.labelIds = newCardLabels;
// Copy Custom Fields
CustomFields.find({
_id: {
$in: oldCard.customFields.map(cf => {
@@ -404,22 +410,16 @@ Cards.helpers({
}
delete this._id;
delete this.labelIds;
this.labelIds = newCardLabels;
this.boardId = boardId;
this.swimlaneId = swimlaneId;
this.listId = listId;
const _id = Cards.insert(this);
// Copy attachments
oldCard.attachments().forEach((file) => {
Meteor.call('cloneAttachment', file,
{
meta: {
cardId: _id
}
}
);
oldCard.attachments().forEach(att => {
att.cardId = _id;
delete att._id;
return Attachments.insert(att);
});
// copy checklists
@@ -522,23 +522,23 @@ Cards.helpers({
attachments() {
if (this.isLinkedCard()) {
return Attachments.find(
{ 'meta.cardId': this.linkedId },
{ cardId: this.linkedId },
{ sort: { uploadedAt: -1 } },
);
} else {
let ret = Attachments.find(
{ 'meta.cardId': this._id },
return Attachments.find(
{ cardId: this._id },
{ sort: { uploadedAt: -1 } },
);
return ret;
}
},
cover() {
if (!this.coverId) return false;
const cover = Attachments.findOne(this.coverId);
// if we return a cover before it is fully stored, we will get errors when we try to display it
// todo XXX we could return a default "upload pending" image in the meantime?
return cover && cover.link();
return cover && cover.url() && cover;
},
checklists() {
@@ -1259,9 +1259,83 @@ Cards.mutations({
};
},
moveToEndOfList({ listId } = {}) {
let swimlaneId = this.swimlaneId;
const boardId = this.boardId;
let sortIndex = 0;
// This should never happen, but there was a bug that was fixed in commit
// ea0239538a68e225c867411a4f3e0d27c158383.
if (!swimlaneId) {
const board = Boards.findOne(boardId);
swimlaneId = board.getDefaultSwimline()._id;
}
// Move the minicard to the end of the target list
let parentElementDom = $(`#swimlane-${this.swimlaneId}`).get(0);
if (!parentElementDom) parentElementDom = $(':root');
const lastCardDom = $(parentElementDom)
.find(`#js-list-${listId} .js-minicard:last`)
.get(0);
if (lastCardDom) sortIndex = Utils.calculateIndex(lastCardDom, null).base;
return this.moveOptionalArgs({
boardId,
swimlaneId,
listId,
sort: sortIndex,
});
},
moveOptionalArgs({ boardId, swimlaneId, listId, sort } = {}) {
boardId = boardId || this.boardId;
swimlaneId = swimlaneId || this.swimlaneId;
// This should never happen, but there was a bug that was fixed in commit
// ea0239538a68e225c867411a4f3e0d27c158383.
if (!swimlaneId) {
const board = Boards.findOne(boardId);
swimlaneId = board.getDefaultSwimline()._id;
}
listId = listId || this.listId;
if (sort === undefined || sort === null) sort = this.sort;
return this.move(boardId, swimlaneId, listId, sort);
},
move(boardId, swimlaneId, listId, sort) {
// Copy Custom Fields
const mutatedFields = {
boardId,
swimlaneId,
listId,
sort,
};
// we must only copy the labels and custom fields if the target board
// differs from the source board
if (this.boardId !== boardId) {
// Get label names
const oldBoard = Boards.findOne(this.boardId);
const oldBoardLabels = oldBoard.labels;
const oldCardLabels = _.pluck(
_.filter(oldBoardLabels, label => {
return _.contains(this.labelIds, label._id);
}),
'name',
);
const newBoard = Boards.findOne(boardId);
const newBoardLabels = newBoard.labels;
const newCardLabelIds = _.pluck(
_.filter(newBoardLabels, label => {
return label.name && _.contains(oldCardLabels, label.name);
}),
'_id',
);
Object.assign(mutatedFields, {
labelIds: newCardLabelIds,
});
// Copy custom fields
CustomFields.find({
_id: {
$in: this.customFields.map(cf => {
@@ -1273,33 +1347,6 @@ Cards.mutations({
});
}
// Get label names
const oldBoard = Boards.findOne(this.boardId);
const oldBoardLabels = oldBoard.labels;
const oldCardLabels = _.pluck(
_.filter(oldBoardLabels, label => {
return _.contains(this.labelIds, label._id);
}),
'name',
);
const newBoard = Boards.findOne(boardId);
const newBoardLabels = newBoard.labels;
const newCardLabelIds = _.pluck(
_.filter(newBoardLabels, label => {
return label.name && _.contains(oldCardLabels, label.name);
}),
'_id',
);
const mutatedFields = {
boardId,
swimlaneId,
listId,
sort,
labelIds: newCardLabelIds,
};
Cards.update(this._id, {
$set: mutatedFields,
});
@@ -2130,6 +2177,11 @@ if (Meteor.isServer) {
title: doc.title,
description: doc.description,
listId: doc.listId,
receivedAt: doc.receivedAt,
startAt: doc.startAt,
dueAt: doc.dueAt,
endAt: doc.endAt,
assignees: doc.assignees,
};
}),
});
@@ -2166,6 +2218,11 @@ if (Meteor.isServer) {
_id: doc._id,
title: doc.title,
description: doc.description,
receivedAt: doc.receivedAt,
startAt: doc.startAt,
dueAt: doc.dueAt,
endAt: doc.endAt,
assignees: doc.assignees,
};
}),
});
@@ -2644,6 +2701,52 @@ if (Meteor.isServer) {
});
},
);
/**
* @operation get_cards_by_custom_field
* @summary Get all Cards that matchs a value of a specific custom field
*
* @param {string} boardId the board ID
* @param {string} customFieldId the list ID
* @param {string} customFieldValue the value to look for
* @return_type [{_id: string,
* title: string,
* description: string,
* listId: string
* swinlaneId: string}]
*/
JsonRoutes.add(
'GET',
'/api/boards/:boardId/cardsByCustomField/:customFieldId/:customFieldValue',
function(req, res) {
const paramBoardId = req.params.boardId;
const paramCustomFieldId = req.params.customFieldId;
const paramCustomFieldValue = req.params.customFieldValue;
Authentication.checkBoardAccess(req.userId, paramBoardId);
JsonRoutes.sendResult(res, {
code: 200,
data: Cards.find({
boardId: paramBoardId,
customFields: {
$elemMatch: {
_id: paramCustomFieldId,
value: paramCustomFieldValue,
},
},
archived: false,
}).map(function(doc) {
return {
_id: doc._id,
title: doc.title,
description: doc.description,
listId: doc.listId,
swinlaneId: doc.swinlaneId,
};
}),
});
},
);
}
export default Cards;
+3 -1
View File
@@ -70,7 +70,9 @@ Checklists.helpers({
this._id = null;
this.cardId = newCardId;
const newChecklistId = Checklists.insert(this);
ChecklistItems.find({ checklistId: oldChecklistId }).forEach(item => {
ChecklistItems.find({ checklistId: oldChecklistId }).forEach(function(
item,
) {
item._id = null;
item.checklistId = newChecklistId;
item.cardId = newCardId;
+97 -16
View File
@@ -50,6 +50,7 @@ export class CsvCreator {
*/
mapHeadertoCardFieldIndex(headerRow) {
const index = {};
index.customFields = [];
for (let i = 0; i < headerRow.length; i++) {
switch (headerRow[i].trim().toLowerCase()) {
case 'title':
@@ -98,9 +99,61 @@ export class CsvCreator {
index.modifiedAt = i;
break;
}
if (headerRow[i].toLowerCase().startsWith('customfield')) {
if (headerRow[i].split('-')[2] === 'dropdown') {
index.customFields.push({
name: headerRow[i].split('-')[1],
type: headerRow[i].split('-')[2],
options: headerRow[i].split('-')[3].split('/'),
position: i,
});
} else if (headerRow[i].split('-')[2] === 'currency') {
index.customFields.push({
name: headerRow[i].split('-')[1],
type: headerRow[i].split('-')[2],
currencyCode: headerRow[i].split('-')[3],
position: i,
});
} else {
index.customFields.push({
name: headerRow[i].split('-')[1],
type: headerRow[i].split('-')[2],
position: i,
});
}
}
}
this.fieldIndex = index;
}
createCustomFields(boardId) {
this.fieldIndex.customFields.forEach(customField => {
let settings = {};
if (customField.type === 'dropdown') {
settings = {
dropdownItems: customField.options.map(option => {
return { _id: Random.id(6), name: option };
}),
};
} else if (customField.type === 'currency') {
settings = {
currencyCode: customField.currencyCode,
};
} else {
settings = {};
}
const id = CustomFields.direct.insert({
name: customField.name,
type: customField.type,
settings,
showOnCard: false,
automaticallyOnCard: false,
showLabelOnMiniCard: false,
boardIds: [boardId],
});
customField.id = id;
customField.settings = settings;
});
}
createBoard(csvData) {
const boardToCreate = {
@@ -228,9 +281,10 @@ export class CsvCreator {
const cardToCreate = {
archived: false,
boardId,
createdAt: csvData[i][this.fieldIndex.createdAt]
? this._now(new Date(csvData[i][this.fieldIndex.createdAt]))
: null,
createdAt:
csvData[i][this.fieldIndex.createdAt] !== ' ' || ''
? this._now(new Date(csvData[i][this.fieldIndex.createdAt]))
: null,
dateLastActivity: this._now(),
description: csvData[i][this.fieldIndex.description],
listId: this.lists[csvData[i][this.fieldIndex.stage]],
@@ -238,20 +292,24 @@ export class CsvCreator {
sort: -1,
title: csvData[i][this.fieldIndex.title],
userId: this._user(),
startAt: csvData[i][this.fieldIndex.startAt]
? this._now(new Date(csvData[i][this.fieldIndex.startAt]))
: null,
dueAt: csvData[i][this.fieldIndex.dueAt]
? this._now(new Date(csvData[i][this.fieldIndex.dueAt]))
: null,
endAt: csvData[i][this.fieldIndex.endAt]
? this._now(new Date(csvData[i][this.fieldIndex.endAt]))
: null,
startAt:
csvData[i][this.fieldIndex.startAt] !== ' ' || ''
? this._now(new Date(csvData[i][this.fieldIndex.startAt]))
: null,
dueAt:
csvData[i][this.fieldIndex.dueAt] !== ' ' || ''
? this._now(new Date(csvData[i][this.fieldIndex.dueAt]))
: null,
endAt:
csvData[i][this.fieldIndex.endAt] !== ' ' || ''
? this._now(new Date(csvData[i][this.fieldIndex.endAt]))
: null,
spentTime: null,
labelIds: [],
modifiedAt: csvData[i][this.fieldIndex.modifiedAt]
? this._now(new Date(csvData[i][this.fieldIndex.modifiedAt]))
: null,
modifiedAt:
csvData[i][this.fieldIndex.modifiedAt] !== ' ' || ''
? this._now(new Date(csvData[i][this.fieldIndex.modifiedAt]))
: null,
};
// add the labels
if (csvData[i][this.fieldIndex.labels]) {
@@ -290,7 +348,29 @@ export class CsvCreator {
cardToCreate.members = wekanMembers;
}
}
Cards.direct.insert(cardToCreate);
// add the custom fields
if (this.fieldIndex.customFields.length > 0) {
const customFields = [];
this.fieldIndex.customFields.forEach(customField => {
if (csvData[i][customField.position] !== ' ') {
if (customField.type === 'dropdown') {
customFields.push({
_id: customField.id,
value: customField.settings.dropdownItems.find(
({ name }) => name === csvData[i][customField.position],
)._id,
});
} else {
customFields.push({
_id: customField.id,
value: csvData[i][customField.position],
});
}
}
cardToCreate.customFields = customFields;
});
Cards.direct.insert(cardToCreate);
}
}
}
@@ -307,6 +387,7 @@ export class CsvCreator {
const boardId = this.createBoard(board);
this.createLists(board, boardId);
this.createSwimlanes(boardId);
this.createCustomFields(boardId);
this.createCards(board, boardId);
return boardId;
}
+5 -1
View File
@@ -22,7 +22,7 @@ CustomFields.attachSchema(
* type of the custom field
*/
type: String,
allowedValues: ['text', 'number', 'date', 'dropdown'],
allowedValues: ['text', 'number', 'date', 'dropdown', 'currency'],
},
settings: {
/**
@@ -30,6 +30,10 @@ CustomFields.attachSchema(
*/
type: Object,
},
'settings.currencyCode': {
type: String,
optional: true,
},
'settings.dropdownItems': {
/**
* list of drop down items objects
+51 -197
View File
@@ -1,3 +1,4 @@
import { Exporter } from './exporter';
/* global JsonRoutes */
if (Meteor.isServer) {
// todo XXX once we have a real API in place, move that route there
@@ -7,10 +8,10 @@ if (Meteor.isServer) {
// on the client instead of copy/pasting the route path manually between the
// client and the server.
/**
* @operation export
* @operation exportJson
* @tag Boards
*
* @summary This route is used to export the board.
* @summary This route is used to export the board to a json file format.
*
* @description If user is already logged-in, pass loginToken as param
* "authToken": '/api/boards/:boardId/export?authToken=:token'
@@ -46,199 +47,52 @@ if (Meteor.isServer) {
JsonRoutes.sendResult(res, 403);
}
});
}
// exporter maybe is broken since Gridfs introduced, add fs and path
export class Exporter {
constructor(boardId) {
this._boardId = boardId;
}
build() {
const fs = Npm.require('fs');
const os = Npm.require('os');
const path = Npm.require('path');
const byBoard = { boardId: this._boardId };
const byBoardNoLinked = {
boardId: this._boardId,
linkedId: { $in: ['', null] },
};
// we do not want to retrieve boardId in related elements
const noBoardId = {
fields: {
boardId: 0,
},
};
const result = {
_format: 'wekan-board-1.0.0',
};
_.extend(
result,
Boards.findOne(this._boardId, {
fields: {
stars: 0,
},
}),
);
result.lists = Lists.find(byBoard, noBoardId).fetch();
result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch();
result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch();
result.customFields = CustomFields.find(
{ boardIds: { $in: [this.boardId] } },
{ fields: { boardId: 0 } },
).fetch();
result.comments = CardComments.find(byBoard, noBoardId).fetch();
result.activities = Activities.find(byBoard, noBoardId).fetch();
result.rules = Rules.find(byBoard, noBoardId).fetch();
result.checklists = [];
result.checklistItems = [];
result.subtaskItems = [];
result.triggers = [];
result.actions = [];
result.cards.forEach(card => {
result.checklists.push(
...Checklists.find({
cardId: card._id,
}).fetch(),
);
result.checklistItems.push(
...ChecklistItems.find({
cardId: card._id,
}).fetch(),
);
result.subtaskItems.push(
...Cards.find({
parentId: card._id,
}).fetch(),
);
});
result.rules.forEach(rule => {
result.triggers.push(
...Triggers.find(
{
_id: rule.triggerId,
},
noBoardId,
).fetch(),
);
result.actions.push(
...Actions.find(
{
_id: rule.actionId,
},
noBoardId,
).fetch(),
);
});
// [Old] for attachments we only export IDs and absolute url to original doc
// [New] Encode attachment to base64
const getBase64Data = function(doc, callback) {
let buffer = Buffer.allocUnsafe(0);
buffer.fill(0);
// callback has the form function (err, res) {}
const tmpFile = path.join(
os.tmpdir(),
`tmpexport${process.pid}${Math.random()}`,
);
const tmpWriteable = fs.createWriteStream(tmpFile);
const readStream = fs.createReadStream(doc.path);
readStream.on('data', function(chunk) {
buffer = Buffer.concat([buffer, chunk]);
});
readStream.on('error', function(err) {
callback(null, null);
});
readStream.on('end', function() {
// done
fs.unlink(tmpFile, () => {
//ignored
});
callback(null, buffer.toString('base64'));
});
readStream.pipe(tmpWriteable);
};
const getBase64DataSync = Meteor.wrapAsync(getBase64Data);
result.attachments = Attachments.find({ 'meta.boardId': byBoard.boardId })
.fetch()
.map(attachment => {
let filebase64 = null;
filebase64 = getBase64DataSync(attachment);
return {
_id: attachment._id,
cardId: attachment.meta.cardId,
//url: FlowRouter.url(attachment.url()),
file: filebase64,
name: attachment.name,
type: attachment.type,
};
});
// we also have to export some user data - as the other elements only
// include id but we have to be careful:
// 1- only exports users that are linked somehow to that board
// 2- do not export any sensitive information
const users = {};
result.members.forEach(member => {
users[member.userId] = true;
});
result.lists.forEach(list => {
users[list.userId] = true;
});
result.cards.forEach(card => {
users[card.userId] = true;
if (card.members) {
card.members.forEach(memberId => {
users[memberId] = true;
});
}
});
result.comments.forEach(comment => {
users[comment.userId] = true;
});
result.activities.forEach(activity => {
users[activity.userId] = true;
});
result.checklists.forEach(checklist => {
users[checklist.userId] = true;
});
const byUserIds = {
_id: {
$in: Object.getOwnPropertyNames(users),
},
};
// we use whitelist to be sure we do not expose inadvertently
// some secret fields that gets added to User later.
const userFields = {
fields: {
_id: 1,
username: 1,
'profile.fullname': 1,
'profile.initials': 1,
'profile.avatarUrl': 1,
},
};
result.users = Users.find(byUserIds, userFields)
.fetch()
.map(user => {
// user avatar is stored as a relative url, we export absolute
if ((user.profile || {}).avatarUrl) {
user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl);
}
return user;
});
return result;
}
canExport(user) {
const board = Boards.findOne(this._boardId);
return board && board.isVisibleBy(user);
}
/**
* @operation exportCSV/TSV
* @tag Boards
*
* @summary This route is used to export the board to a CSV or TSV file format.
*
* @description If user is already logged-in, pass loginToken as param
*
* 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
* @param {string} delimiter delimiter to use while building export. Default is comma ','
*/
Picker.route('/api/boards/:boardId/export/csv', function(params, req, res) {
const boardId = params.boardId;
let user = null;
const loginToken = params.query.authToken;
if (loginToken) {
const hashToken = Accounts._hashLoginToken(loginToken);
user = Meteor.users.findOne({
'services.resume.loginTokens.hashedToken': hashToken,
});
} else if (!Meteor.settings.public.sandstorm) {
Authentication.checkUserId(req.userId);
user = Users.findOne({
_id: req.userId,
isAdmin: true,
});
}
const exporter = new Exporter(boardId);
if (exporter.canExport(user)) {
body = params.query.delimiter
? exporter.buildCsv(params.query.delimiter)
: exporter.buildCsv();
res.writeHead(200, {
'Content-Length': body.length,
'Content-Type': params.query.delimiter ? 'text/csv' : 'text/tsv',
});
res.write(body);
res.end();
} else {
res.writeHead(403);
res.end('Permission Error');
}
});
}
+71 -17
View File
@@ -37,8 +37,8 @@ export class Exporter {
result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch();
result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch();
result.customFields = CustomFields.find(
{ boardIds: { $in: [this.boardId] } },
{ fields: { boardId: 0 } },
{ boardIds: this._boardId },
{ fields: { boardIds: 0 } },
).fetch();
result.comments = CardComments.find(byBoard, noBoardId).fetch();
result.activities = Activities.find(byBoard, noBoardId).fetch();
@@ -214,7 +214,32 @@ export class Exporter {
'Vote',
'Archived',
);
const customFieldMap = {};
let i = 0;
result.customFields.forEach(customField => {
customFieldMap[customField._id] = {
position: i,
type: customField.type,
};
if (customField.type === 'dropdown') {
let options = '';
customField.settings.dropdownItems.forEach(item => {
options = options === '' ? item.name : `${`${options}/${item.name}`}`;
});
columnHeaders.push(
`CustomField-${customField.name}-${customField.type}-${options}`,
);
} else if (customField.type === 'currency') {
columnHeaders.push(
`CustomField-${customField.name}-${customField.type}-${customField.settings.currencyCode}`,
);
} else {
columnHeaders.push(
`CustomField-${customField.name}-${customField.type}`,
);
}
i++;
});
/* TODO: Try to get translations working.
These currently only bring English translations.
TAPi18n.__('title'),
@@ -290,23 +315,17 @@ export class Exporter {
labels = `${labels + label.name}-${label.color} `;
});
currentRow.push(labels.trim());
currentRow.push(card.startAt ? moment(card.startAt).format('LLLL') : ' ');
currentRow.push(card.dueAt ? moment(card.dueAt).format('LLLL') : ' ');
currentRow.push(card.endAt ? moment(card.endAt).format('LLLL') : ' ');
currentRow.push(card.startAt ? moment(card.startAt).format() : ' ');
currentRow.push(card.dueAt ? moment(card.dueAt).format() : ' ');
currentRow.push(card.endAt ? moment(card.endAt).format() : ' ');
currentRow.push(card.isOvertime ? 'true' : 'false');
currentRow.push(card.spentTime);
currentRow.push(card.createdAt ? moment(card.createdAt).format() : ' ');
currentRow.push(card.modifiedAt ? moment(card.modifiedAt).format() : ' ');
currentRow.push(
card.createdAt ? moment(card.createdAt).format('LLLL') : ' ',
card.dateLastActivity ? moment(card.dateLastActivity).format() : ' ',
);
currentRow.push(
card.modifiedAt ? moment(card.modifiedAt).format('LLLL') : ' ',
);
currentRow.push(
card.dateLastActivity
? moment(card.dateLastActivity).format('LLLL')
: ' ',
);
if (card.vote.question) {
if (card.vote && card.vote.question !== '') {
let positiveVoters = '';
let negativeVoters = '';
card.vote.positive.forEach(userId => {
@@ -331,10 +350,45 @@ export class Exporter {
currentRow.push(' ');
}
currentRow.push(card.archived ? 'true' : 'false');
//Custom fields
const customFieldValuesToPush = new Array(result.customFields.length);
card.customFields.forEach(field => {
if (field.value !== null) {
if (customFieldMap[field._id].type === 'date') {
customFieldValuesToPush[
customFieldMap[field._id].position
] = moment(field.value).format();
} else if (customFieldMap[field._id].type === 'dropdown') {
const dropdownOptions = result.customFields.find(
({ _id }) => _id === field._id,
).settings.dropdownItems;
const fieldValue = dropdownOptions.find(
({ _id }) => _id === field.value,
).name;
customFieldValuesToPush[
customFieldMap[field._id].position
] = fieldValue;
} else {
customFieldValuesToPush[customFieldMap[field._id].position] =
field.value;
}
}
});
for (
let valueIndex = 0;
valueIndex < customFieldValuesToPush.length;
valueIndex++
) {
if (!(valueIndex in customFieldValuesToPush)) {
currentRow.push(' ');
} else {
currentRow.push(customFieldValuesToPush[valueIndex]);
}
}
stringifier.write(currentRow);
});
stringifier.end();
return cardRows;
return cardRows[0];
}
canExport(user) {
+1 -1
View File
@@ -259,7 +259,7 @@ if (Meteor.isServer) {
throw new Meteor.Error('invalid-user');
}
const user = Meteor.user();
if (!user.emails && !user.emails[0] && user.emails[0].address) {
if (!user.emails || !user.emails[0] || !user.emails[0].address) {
throw new Meteor.Error('email-invalid');
}
this.unblock();
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff. Show more