Compare commits

...
318 Commits
Author SHA1 Message Date
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
Lauri Ojansivu b05f8e5251 Update translations. 2020-05-24 04:24:04 +03:00
Lauri Ojansivu 31c2afb072 Merge branch 'feature-meteor-files' 2020-05-24 04:18:52 +03:00
Lauri Ojansivu 863f0fc5db v4.04 2020-05-24 03:46:20 +03:00
Lauri Ojansivu 055b528532 Merge branch 'lib-change' of https://github.com/PDIS/wekan into
PDIS-lib-change
2020-05-24 03:13:53 +03:00
Lauri Ojansivu fda392e662 Update translations. 2020-05-24 02:45:09 +03:00
Lauri Ojansivu e5f9d0a4dd Update ChangeLog. 2020-05-24 02:37:24 +03:00
Lauri Ojansivu 3f16bf6054 Merge pull request #3109 from marc1006/issue_3107
Set '*' as default value for swimlane and list name in card move action
2020-05-24 02:13:55 +03:00
Lauri Ojansivu 6774b9e905 Update ChangeLog. 2020-05-24 02:11:23 +03:00
Lauri Ojansivu 00a38b36a4 Merge pull request #3108 from marc1006/issue_2757
Change the swimlaneid of a card only if a new target swimlaneid is se…
2020-05-24 01:56:58 +03:00
Lauri Ojansivu de365ad62a Merge branch 'master' of https://github.com/helioguardabaxo/wekan 2020-05-24 01:46:12 +03:00
Lauri Ojansivu fe631ff75b Update ChangeLog. 2020-05-24 01:16:42 +03:00
Lauri Ojansivu eed649fbe6 Merge branch 'Lewiscowles1986-feature-export-to-html-wip' 2020-05-24 01:13:07 +03:00
Lauri Ojansivu 8ddbc622db Merge branch 'feature-export-to-html-wip' of
https://github.com/Lewiscowles1986/wekan into
Lewiscowles1986-feature-export-to-html-wip
2020-05-24 00:55:32 +03:00
Marc Hartmayer 24b21d23ba Set '*' as default value for swimlane and list name in card move action
This is the same default as `Utils.getTriggerActionDesc` has. This commit fixes
https://github.com/wekan/wekan/issues/3107.
2020-05-23 19:37:23 +02:00
Marc Hartmayer 64fa02cdf5 Change the swimlaneid of a card only if a new target swimlaneid is selected
Fixes the issue https://github.com/wekan/wekan/issues/2757. While at it, fix the
same issue also for multi selection.
2020-05-23 17:35:22 +02:00
helioguardabaxo 3b50b2c59f Added Modern theme with Poppins font 2020-05-22 18:38:50 -03:00
Romulus Urakagi Tsai 921460db40 Fix export attachments (not tested) 2020-05-22 14:59:56 +08:00
Romulus Urakagi Tsai 4156073b1a Fix translation error 2020-05-22 14:53:10 +08:00
Romulus Urakagi Tsai af7c74b2d3 Merge branch 'lib-change' of https://github.com/PDIS/wekan into lib-change 2020-05-20 15:12:55 +08:00
Romulus Urakagi Tsai 4064f3f406 Fix migrated attachment not readable bug
Remove reduandant files
2020-05-20 15:11:22 +08:00
Lauri Ojansivu 94e47401cb Update ChangeLog. 2020-05-20 02:30:46 +03:00
Lewis Cowles 5ef83ab236 Export Board to Zip file
* Extracts Card covers
* Labels
* Re-works some CSS & HTML
* Produces deployable assets (minus WebFonts)
2020-05-18 11:06:18 +01:00
Lauri Ojansivu c22eaa26cd Update translations. 2020-05-17 03:19:30 +03:00
Lauri Ojansivu 060b9acd86 Update ChangeLog. 2020-05-17 01:08:50 +03:00
Lauri Ojansivu 213b7b4819 Merge branch 'helioguardabaxo-master' 2020-05-17 00:52:21 +03:00
helioguardabaxo 7a576f9ae4 Added natural theme 2020-05-16 18:07:38 -03:00
Lauri Ojansivu 62f06a792f v4.03 2020-05-16 03:42:57 +03:00
Lauri Ojansivu 1ac11d92ba Try to fix Sandstorm Export menu.
Thanks to xet7.
2020-05-15 20:37:30 -04:00
Lauri Ojansivu 0c299e3225 Update ChangeLog. 2020-05-16 03:33:17 +03:00
Lauri Ojansivu bcc3a39c06 Merge branch 'CidKramer-master' 2020-05-16 03:28:11 +03:00
Lauri Ojansivu 13dffca055 Merge branch 'master' of https://github.com/CidKramer/wekan into CidKramer-master 2020-05-16 03:25:06 +03:00
Lauri Ojansivu 68ee88a041 Update ChangeLog. 2020-05-16 03:15:39 +03:00
Lauri Ojansivu 3718dd4d45 Fix lint. 2020-05-16 03:15:14 +03:00
Lauri Ojansivu 506a747fce Update translations. 2020-05-16 03:12:07 +03:00
Lauri Ojansivu 98e255ccec Fix Can't Scroll on All Boards on mobile phone. Added drag handles.
Thanks to xet7 !

Fixes #3096
2020-05-16 03:08:05 +03:00
Lauri Ojansivu 4879df0244 v4.02 2020-05-15 20:20:29 +03:00
Lauri Ojansivu 3bdeaaf552 Update translations. 2020-05-15 20:08:34 +03:00
CidKramer 45dea737bf Added clearblue theme 2020-05-15 14:17:37 +02:00
蔡仲明 (Romulus Urakagi Tsai) b80396f627 Purge unneeded require 2020-05-14 17:36:57 +08:00
Romulus Tsai 蔡仲明 0735981366 Merge branch 'master' into lib-change 2020-05-14 16:47:05 +08:00
Romulus Tsai 蔡仲明 5d8cca40d2 Merge branch 'master' of https://github.com/wekan/wekan 2020-05-14 16:45:21 +08:00
Romulus Urakagi Tsai 09ce3e464f Fix typo 2020-05-14 16:43:59 +08:00
Romulus Urakagi Tsai 4c5a2fbd1f Card clone OK 2020-05-14 14:55:54 +08:00
Lauri Ojansivu 9c6cd51ca7 Merge branch 'marc1006-fixes' 2020-05-14 01:06:46 +03:00
Lauri Ojansivu ea74a34d72 Add popup and changelog for linked card fixes. 2020-05-14 01:04:52 +03:00
Lauri Ojansivu 28cffc4328 Update translations. 2020-05-14 01:04:21 +03:00
Lauri Ojansivu 0a12319002 Merge branch 'fixes' of https://github.com/marc1006/wekan into marc1006-fixes 2020-05-14 00:22:22 +03:00
Lauri Ojansivu 2f33e3a76b Update ChangeLog. 2020-05-13 23:52:30 +03:00
Lauri Ojansivu 96507e6777 Add Spanish (Chile).
Thanks to isos.

Update translations.
2020-05-13 23:48:53 +03:00
Lauri Ojansivu e6d6b5322e Update translations. 2020-05-13 23:22:32 +03:00
Lauri Ojansivu b8d8081ded Update ChangeLog. 2020-05-13 22:06:39 +03:00
Lauri Ojansivu 0e428233f2 Merge pull request #3088 from krupupakku/master
Swimlanes ID missing in new boards
2020-05-13 22:02:24 +03:00
Lauri Ojansivu fdbc1b5c22 Update translations. 2020-05-13 19:37:37 +03:00
mvolo17 ea0239538a Swimlanes ID missing in new boards
when creating a new card in a new board there were and error on console. Result: card was not created. adding this parenthesis it works now.
Just for info. without this change if you want to create a card you need to change view to swimlines and go back to list view
2020-05-13 12:44:40 +02:00
Lauri Ojansivu aa084b7a7e Merge branch 'brymut-add-more-import-export-options' 2020-05-13 05:27:28 +03:00
Lauri Ojansivu 8a2509007c Fix syntax. Maybe sometime later think about translations.
Thanks to xet7 !
2020-05-13 05:25:04 +03:00
Lauri Ojansivu d0208b2112 Update translations. 2020-05-13 04:25:22 +03:00
Lauri Ojansivu 851bfa53e6 Merge branch 'add-more-import-export-options' of https://github.com/brymut/wekan into brymut-add-more-import-export-options 2020-05-13 03:28:02 +03:00
Lauri Ojansivu ba01ebe05d Update ChangeLog. 2020-05-13 03:20:32 +03:00
Lauri Ojansivu e6a2ffc592 Merge branch 'master' of github.com:wekan/wekan 2020-05-13 03:17:11 +03:00
Lauri Ojansivu f90ca00669 Update translations. 2020-05-13 03:16:58 +03:00
Lauri Ojansivu bbb65ae4e2 Merge pull request #3084 from wackazong/master-patch-1
Create card does not allow an empty member list
2020-05-13 03:16:37 +03:00
Lauri Ojansivu eece32d51b Merge branch 'NicoP-S-master' 2020-05-13 03:07:05 +03:00
Lauri Ojansivu 1865bdbee9 Update translations and fix syntax. 2020-05-13 03:06:04 +03:00
Lauri Ojansivu 85f254da38 Merge branch 'master' of https://github.com/NicoP-S/wekan into NicoP-S-master 2020-05-13 02:57:36 +03:00
Lauri Ojansivu 1fe7394d05 Update ChangeLog. 2020-05-13 02:52:33 +03:00
Lauri Ojansivu 6b9a7a3fc1 Merge branch 'krupupakku-master' 2020-05-13 02:51:04 +03:00
Lauri Ojansivu 8f33517142 Merge branch 'master' of https://github.com/krupupakku/wekan into krupupakku-master 2020-05-13 02:46:57 +03:00
Lauri Ojansivu 0a51ea753f Update ChangeLog. 2020-05-13 02:45:10 +03:00
Lauri Ojansivu 235bedab00 Merge branch 'helioguardabaxo-master' 2020-05-13 02:43:16 +03:00
Lauri Ojansivu 564e1919af Merge branch 'master' of https://github.com/helioguardabaxo/wekan into helioguardabaxo-master 2020-05-13 02:39:38 +03:00
Lauri Ojansivu 1c9dd7233e Update translations. 2020-05-12 21:31:42 +03:00
Lauri Ojansivu 6e7a58b204 Update ChangeLog. 2020-05-12 21:26:19 +03:00
Lauri Ojansivu 75bdd33fda Update dependencies.
Thanks to xet7 !
2020-05-12 21:21:22 +03:00
Lauri Ojansivu 5f915ef966 Add options to rebuild-wekan.sh to run Meteor in development mode
where after file change it rebuilds.

Thanks to xet7 !
2020-05-12 19:05:51 +03:00
Bryan Mutai a570c4a861 add: export board/cards/lists to CSV/TSV 2020-05-10 23:58:15 +03:00
wackazong a797abaa36 Create card does not allow an empty member list
When I create a card via the API I always have the authorId in members, even if I pass an empty string as member list. Workaround: I can empty the member list by passing an empty string in a PUT request. This pull request proposes to not add the authorId to the member list when creating a card and the member list is empty.
2020-05-08 18:55:37 +02:00
Marco Volo 2bb5a31fa4 avatar-image fix 2020-05-08 15:25:31 +02:00
Romulus Urakagi Tsai 012ca39a8d Attachment activities merging done 2020-05-08 11:50:43 +08:00
Romulus Tsai 蔡仲明 c3458855bd Merge branch 'master' into lib-change 2020-05-08 10:13:11 +08:00
Romulus Tsai 蔡仲明 cfcc73724f Merge branch 'master' of https://github.com/wekan/wekan 2020-05-08 09:35:11 +08:00
Romulus Urakagi Tsai 4448488767 Done attachments activities operating 2020-05-08 09:32:19 +08:00
Bryan Mutai 1742bcd9b1 add: import board/cards/lists using CSV/TSV 2020-05-07 01:31:59 +03:00
Romulus Urakagi Tsai 7dc0bbd7b2 Set correct storage location 2020-05-06 11:15:01 +08:00
Romulus Urakagi Tsai 269698ba78 Attachments download 2020-05-05 14:18:10 +08:00
Romulus Urakagi Tsai 0a1bfd37b3 Migrating attachments 2020-05-05 14:08:36 +08:00
Nico ec03bbe260 API add boards to json where user is member of 2020-05-03 01:29:28 +02:00
Nico 3cc0a93e0e Card vote options in new fork 2020-05-03 00:33:15 +02:00
helioguardabaxo 079867ff37 Add white-space:normal to copy-to-clipboard button in card details 2020-05-02 14:48:49 -03:00
Lauri Ojansivu 533bc045d0 Update translations. 2020-04-30 07:26:13 +03:00
Lauri Ojansivu b5773232d5 Remove extra file. 2020-04-30 07:09:58 +03:00
Lauri Ojansivu 3042d2ff9a Update ChangeLog. 2020-04-30 07:03:43 +03:00
Lauri Ojansivu 8d5adc0464 Install Wekan to mobile homescreen icon and use fullscreen PWA.
Docs at https://github.com/wekan/wekan/wiki/PWA

Thanks to xet7 !

Closes #2879
2020-04-30 06:54:48 +03:00
Marc Hartmayer 9cba640120 Fix typo 2020-04-30 01:59:57 +02:00
Marc Hartmayer b740381a72 Refuse to delete a card as long as there is link to it
This fixes https://github.com/wekan/wekan/issues/2785.
2020-04-30 01:59:44 +02:00
Marc Hartmayer 2691f033cb Fix creation of card links
Without this fix, orphaned card links are created and therefore this leads to
problems as described in https://github.com/wekan/wekan/issues/2785.
2020-04-30 01:59:04 +02:00
Lauri Ojansivu c7d4a90d5c Update ChangeLog. 2020-04-29 18:33:42 +03:00
Lauri Ojansivu 92dd6bbba2 Update ChangeLog. 2020-04-29 16:34:51 +03:00
Lauri Ojansivu e42adcaed0 Merge pull request #3066 from marc1006/shortcuts
Fix shortcuts list and support card shortcuts when hovering a card
2020-04-29 16:32:59 +03:00
Marc Hartmayer 301d96f392 Support card shortcuts when hovering a card 2020-04-29 15:17:43 +02:00
Marc Hartmayer 1c488cb8a7 Fix shortcuts mapping in the shortcuts list
Shorcuts are case-sensitive therefore let's fix the keys in the shortcuts list.
2020-04-29 15:14:56 +02:00
Lauri Ojansivu 9e33d8cbab Update ChangeLog 2020-04-28 23:56:51 +03:00
Lauri Ojansivu 4c3907bd41 Merge pull request #3061 from marc1006/fix
Fix getStartDayOfWeek once again :)
2020-04-28 23:53:24 +03:00
Marc Hartmayer 153d729544 Fix getStartDayOfWeek function
In case profile.startDayOfWeek is 0 it's evaluated to false and 1 is returned.
Let's fix this by differentiating between undefined and an actual value.

Fixes: 9ae20a3f51
2020-04-28 20:56:11 +02:00
Lauri Ojansivu 3d5abd60cc v4.01 2020-04-28 19:02:42 +03:00
Lauri Ojansivu 1d89e96dd1 Upgrade to Node v12.16.3
Thanks to Node developers and xet7 !
2020-04-28 18:58:01 +03:00
Lauri Ojansivu bc3696b64a Update translations. 2020-04-28 15:22:12 +03:00
Lauri Ojansivu f803520c76 Update ChangeLog. 2020-04-28 15:17:09 +03:00
Lauri Ojansivu 6afc9259f0 Smaller height for Add Board button.
Thanks to xet7 !
2020-04-28 15:14:10 +03:00
Lauri Ojansivu 47df8d6737 Update translations. 2020-04-28 03:57:28 +03:00
Lauri Ojansivu d2dfd9c3b9 Update ChangeLog. 2020-04-28 03:53:43 +03:00
Lauri Ojansivu 63f501c574 Merge pull request #3056 from marc1006/header_height_fix
Fix list header height when cards count is shown
2020-04-28 03:51:18 +03:00
Lauri Ojansivu 6aa0aa50dc Update ChangeLog. 2020-04-28 03:38:27 +03:00
Lauri Ojansivu 9ae20a3f51 Fix Cards and Users docs not generated because of syntax error and new Javascript syntax.
Wekan uses wekan/releases/generate-docs*.sh Python code to generate OpenAPI docs,
it did not show any errors while generating docs, only left out parts of API docs.

Thanks to pvcon13 and xet7 !
2020-04-28 03:24:18 +03:00
Lauri Ojansivu 280e66947e Fix Docker builds.
Thanks to xet7 !
2020-04-27 16:14:02 +03:00
Marc Hartmayer 9b9e3c9e0c Fix list header height when cards count is shown 2020-04-27 14:22:26 +02:00
Lauri Ojansivu 1696bd688e v4.00 2020-04-27 03:35:38 +03:00
Lauri Ojansivu 63ad79a784 Merge pull request #3052 from marc1006/360px
Make sure that the board header buttons fit into one line
2020-04-27 03:29:27 +03:00
Marc Hartmayer f7a0d15db7 Make sure that the board header buttons fit into one line
...even for devices with 360px width resolution.
2020-04-27 02:21:48 +02:00
Lauri Ojansivu 6a7cb809f4 v3.99 2020-04-27 03:20:36 +03:00
Lauri Ojansivu 3244c3c32b Update translations. 2020-04-27 03:12:24 +03:00
Lauri Ojansivu 09666585e2 Revert part 2, related https://github.com/wekan/wekan/pull/3048 2020-04-27 02:56:11 +03:00
Lauri Ojansivu ee106d1cb4 Revert In Progress additional vote features. Translations are not removed.
Thanks to xet7 !

Related https://github.com/wekan/wekan/pull/3048
2020-04-27 02:54:40 +03:00
Lauri Ojansivu 8ef435c83e Update translations related to vote feature. Code variables not changed.
Thanks to xet7 !

Related https://github.com/wekan/wekan/pull/3048
2020-04-27 02:37:33 +03:00
Lauri Ojansivu 3a74b88294 Update ChangeLog. 2020-04-27 02:27:28 +03:00
Lauri Ojansivu c5079096b5 Merge branch 'master' of github.com:wekan/wekan 2020-04-27 02:23:42 +03:00
Lauri Ojansivu 927e43ab3f Merge pull request #3051 from marc1006/3049
Reactivate the touch event fix for the boards list
2020-04-27 02:23:00 +03:00
Marc Hartmayer eddcb2260b Reactivate the touch event fix for the boards list
This fixes https://github.com/wekan/wekan/issues/3049.
2020-04-27 01:16:13 +02:00
Lauri Ojansivu ff73aa4566 Merge branch 'NicoP-S-master' 2020-04-26 17:18:41 +03:00
Nico de993a079d Remove old code 2020-04-26 02:44:14 +02:00
Nico 7bb0aa7488 Additional vote features 2020-04-26 02:41:26 +02:00
Nico fe7398faef Put checkbox and label in same row 2020-04-26 02:37:18 +02:00
NicoP-S 8e709c1b79 Merge pull request #6 from wekan/master
Update
2020-04-26 00:37:30 +02:00
Lauri Ojansivu 3908fe0ab8 v3.98 2020-04-25 16:44:45 +03:00
Lauri Ojansivu 5d2ff89e9b Update ChangeLog. 2020-04-25 15:26:46 +03:00
Lauri Ojansivu 7f38bc7b95 Merge pull request #3046 from marc1006/3041
Partially fixes https://github.com/wekan/wekan/issues/3041
2020-04-25 15:21:50 +03:00
Marc Hartmayer ed0c64a270 Allow words in title/description to be able to break and wrap onto the next line 2020-04-25 13:34:30 +02:00
Marc Hartmayer e63e7b1131 Allow variable height for board list items
This fixes https://github.com/wekan/wekan/issues/3041.
2020-04-25 13:34:30 +02:00
Lauri Ojansivu 3f7d9fd42c Update ChangeLog. 2020-04-25 14:23:29 +03:00
Lauri Ojansivu 867344f070 Update translations. 2020-04-25 14:19:26 +03:00
Lauri Ojansivu a84195179e Merge pull request #3045 from marc1006/mobile_followup
Follow-up for https://github.com/wekan/wekan/pull/3040
2020-04-25 14:15:58 +03:00
Marc Hartmayer f1b18d79cd Don't interpret dragging an element as a click
Remove `enableClickOnTouch` as this behavior is not intuitive.
2020-04-25 09:49:41 +02:00
Marc Hartmayer 6d1cdebfe2 Make it compatible with newer and older versions of jQuery sortable
While at it, fix comments and prettify it.
2020-04-25 09:49:10 +02:00
Lauri Ojansivu 7cf8487f57 Merge branch 'master' of github.com:wekan/wekan 2020-04-25 04:16:45 +03:00
Lauri Ojansivu 5403027c9d Update ChangeLog. 2020-04-25 04:16:29 +03:00
Lauri Ojansivu daf397a1ba Merge pull request #3044 from VictorioBerra/patch-1
Fix comment typo in docker-compose.yml
2020-04-25 04:11:07 +03:00
Lauri Ojansivu 6104c1711f Update translations. 2020-04-25 03:58:48 +03:00
Lauri Ojansivu db41e41d90 Update ChangeLog. 2020-04-25 03:53:16 +03:00
Lauri Ojansivu 1235363465 Fix Wekan not load at all in Firefox v.68 for Android.
Thanks to xet7 !

Closes #3021
2020-04-25 03:47:44 +03:00
Lauri Ojansivu 7263d7497f Merge branch 'marc1006-mobile' 2020-04-25 01:41:37 +03:00
Lauri Ojansivu 666186fee8 Merge branch 'mobile' of https://github.com/marc1006/wekan into marc1006-mobile 2020-04-25 01:41:04 +03:00
Victorio Berra 27d1b5acca Fix comment typo in docker-compose.yml
webbroser -> web browser.
2020-04-24 09:07:32 -05:00
Lauri Ojansivu 5eb5ca5752 Update translations. 2020-04-23 18:38:57 +03:00
Lauri Ojansivu 16d371b814 Update ChangeLog. 2020-04-23 14:21:06 +03:00
Lauri Ojansivu df9b4db49d Merge branch 'marc1006-one_apply' 2020-04-23 14:17:57 +03:00
Lauri Ojansivu 2e8e58004d Merge branch 'one_apply' of https://github.com/marc1006/wekan into marc1006-one_apply 2020-04-23 14:17:35 +03:00
Lauri Ojansivu a83aa6162b Update ChangeLog. 2020-04-23 14:16:27 +03:00
Lauri Ojansivu 07c915ccf0 Merge pull request #3038 from marc1006/fix
Fix getStartDayOfWeek function
2020-04-23 14:14:09 +03:00
Marc Hartmayer 405f176bbb Fix getStartDayOfWeek function
In case profile.startDayOfWeek is 0 it's evaluated to false and 1 is returned.
Let's fix this by differentiating between undefined and an actual value.
2020-04-23 10:40:53 +02:00
Marc Hartmayer 6476503137 Fix drag-and-drop and scrolling on mobile devices
Use drag handles on "miniscreens" whenever useful, this is especially useful on
mobile device. This should hopefully fix https://github.com/wekan/wekan/issues/2947.
While at it, simplify the condition

 Utils.isMiniScreen() ||
   (!Utils.isMiniScreen() && showDesktopDragHandles)

 to

  Utils.isMiniScreen() || showDesktopDragHandle
2020-04-23 03:02:03 +02:00
Marc Hartmayer 981ed546f1 Newer versions of jQuery sortable use uiSortable key
Newer versions of jQuery sortable use `uiSortable` as key to store the data.
Let's adapt the code. While at it, refactor the code.
2020-04-23 02:55:33 +02:00
Marc Hartmayer c1287248a6 Mobile device layout fix 2020-04-23 02:55:24 +02:00
Marc Hartmayer 52f884f2b1 Use only one 'Apply' button for applying the user settings 2020-04-23 00:20:16 +02:00
Lauri Ojansivu 4550e10fbe Revert Snap MongoDB changes.
Thanks to xet7 !
2020-04-22 22:43:20 +03:00
Lauri Ojansivu 2efe974195 Update ChangeLog. 2020-04-22 21:50:35 +03:00
Lauri Ojansivu 6b808dd74b Try to upgrade MongoDB version in Snap.
Thanks to xet7 !
2020-04-22 21:48:46 +03:00
Lauri Ojansivu 8a917e0efc Update ChangeLog. 2020-04-22 21:37:33 +03:00
Lauri Ojansivu d1f98d0c47 Upgrade to Meteor 1.10.2
Thanks to Meteor developers and xet7 !
2020-04-22 21:35:13 +03:00
Lauri Ojansivu 323de77a83 Update ChangeLog. 2020-04-22 21:29:53 +03:00
Lauri Ojansivu 738e7f9ebc Merge branch 'boeserwolf-feature-add-babel' 2020-04-22 21:26:06 +03:00
Lauri Ojansivu b5b4af73a9 Merge branch 'feature-add-babel' of https://github.com/boeserwolf/wekan into boeserwolf-feature-add-babel 2020-04-22 21:25:49 +03:00
Lauri Ojansivu ab1014b7d8 Update ChangeLog. 2020-04-22 21:24:13 +03:00
Lauri Ojansivu 12964aa742 Update translations. 2020-04-22 21:16:28 +03:00
Lauri Ojansivu e4b1106acd Update ChangeLog. 2020-04-22 21:07:49 +03:00
Lauri Ojansivu d0524532a4 Merge branch 'master' of github.com:wekan/wekan 2020-04-22 21:05:58 +03:00
Lauri Ojansivu 7de18eccea - Set Snap MongoDB compatibility to 4.2 according to Meteor ChangeLog.
Thanks to xet7 !
2020-04-22 21:05:34 +03:00
Lauri Ojansivu f9747bc908 - Set Snap MongoDB compatibility to 4.2.5 according to Meteor ChangeLog.
Thanks to xet7 !
2020-04-22 21:03:47 +03:00
Lauri Ojansivu 6fd827792a Update ChangeLog. 2020-04-22 21:03:01 +03:00
Lauri Ojansivu 9e95c06415 Fix lint errors in lint error fix.
Thanks to xet7 !
2020-04-22 21:00:31 +03:00
Lauri Ojansivu e7603298d7 Merge branch 'marc1006-week_start' 2020-04-22 20:36:28 +03:00
Marc Hartmayer 8e14459cff Implement option to change the first day of week in user settings
Implements #2535.
2020-04-22 19:16:05 +02:00
Marc Hartmayer 3ac5dba243 Set first day of the week to Monday 2020-04-22 19:12:34 +02:00
Lauri Ojansivu 9969c89b1c Update ChangeLog. 2020-04-22 20:09:07 +03:00
Lauri Ojansivu 5a5b19d4fc Merge pull request #3031 from marc1006/style_issues
Multiple lint issue fixes
2020-04-22 20:06:50 +03:00
Lauri Ojansivu 4d48fa4d95 Update ChangeLog. 2020-04-22 16:43:47 +03:00
boeserwolf 57545f66d2 Add babel to build chain and linter
Enables fancy Javascript language features like optional chaining,
for developer happiness.
2020-04-22 15:16:30 +02:00
Lauri Ojansivu 8586c843a4 Merge pull request #3030 from boeserwolf/master
Pre-fill the title of checklists (Trello-style)
2020-04-22 16:08:06 +03:00
Marc Hartmayer b2acc3ba45 Multiple lint issue fixes
Found by using the command `meteor npm run lint:eslint:fix`.
2020-04-21 18:15:29 +02:00
boeserwolf 9f0273aa44 Pre-fill the title of checklists (Trello-style) 2020-04-21 00:58:15 +03:00
Lauri Ojansivu 3407b0e65b Add links to FAQ. 2020-04-20 16:22:29 +03:00
Lauri Ojansivu 6cd92ab2c6 v3.97 2020-04-19 17:05:41 +03:00
Lauri Ojansivu e92ebe5151 Update ChangeLog. 2020-04-19 16:52:06 +03:00
Lauri Ojansivu 8cb838c0de Merge branch 'boeserwolf-feature-sortable-boards' 2020-04-19 16:31:12 +03:00
Lauri Ojansivu 6cae14d6e9 Merge branch 'feature-sortable-boards' of https://github.com/boeserwolf/wekan into boeserwolf-feature-sortable-boards 2020-04-19 16:30:27 +03:00
boeserwolf b42d8346cd Insert new boards at last position 2020-04-19 15:53:13 +03:00
boeserwolf 1a065ff351 Refactor variable names 2020-04-19 15:52:43 +03:00
boeserwolf 3565ff2700 Export sort field 2020-04-19 15:52:06 +03:00
boeserwolf e354715a9d Remove sorting by _id 2020-04-19 15:51:29 +03:00
Lauri Ojansivu e7002f417b Update translations. 2020-04-19 14:00:24 +03:00
boeserwolf ef5f38f431 Make boards sortable 2020-04-19 13:49:10 +03:00
boeserwolf b3efa71d13 Add move function to boards mutations 2020-04-19 13:49:10 +03:00
boeserwolf 10fcc19b7f Add sortDefault helper for sorting boards 2020-04-19 13:49:07 +03:00
Lauri Ojansivu 948f05fecb Update ChangeLog. 2020-04-19 12:23:29 +03:00
Lauri Ojansivu 927bb83813 Merge pull request #3025 from boeserwolf/master
Make linked card clickable
2020-04-19 12:14:48 +03:00
boeserwolf 9f396e9038 Add a migration to add a sort field to the boards model 2020-04-19 10:51:58 +03:00
boeserwolf 2400c91013 Add sort field to boards model 2020-04-19 10:48:44 +03:00
boeserwolf f583645744 Make linked card clickable 2020-04-19 10:33:41 +03:00
Lauri Ojansivu 42b3cff99f Update ChangeLog. 2020-04-17 22:14:09 +03:00
Lauri Ojansivu 960fe5163b Remove old warning from Sandstorm import board data loss, because bug has been already fixed.
Thanks to aputsiaq and xet7 !
2020-04-17 21:58:19 +03:00
Lauri Ojansivu 41665c079d Update ChangeLog. 2020-04-17 21:48:06 +03:00
Lauri Ojansivu 8a0defc988 Merge pull request #3024 from marc1006/card_layout_fix
Improve card layout on mobile devices
2020-04-17 21:38:50 +03:00
Marc Hartmayer d7e0350ecf Improve card layout on mobile devices 2020-04-17 19:49:43 +02:00
Lauri Ojansivu bf15fa988d Rename Dockerfile.amd64 back to Dockerfile, because automatic builds did not work.
Thanks to xet7 !
2020-04-17 18:34:41 +03:00
Lauri Ojansivu db2d59181c Update ChangeLog. 2020-04-17 10:21:41 +03:00
Lauri Ojansivu dcc9a0d498 Merge branch 'brokencode64-arm64-dockerfile' 2020-04-17 10:18:53 +03:00
Lauri Ojansivu 936dea591a Merge branch 'arm64-dockerfile' of https://github.com/brokencode64/wekan into brokencode64-arm64-dockerfile 2020-04-17 10:18:30 +03:00
Lauri Ojansivu cf7e5472ba Update translations. 2020-04-17 10:10:40 +03:00
Lauri Ojansivu 5c61119716 Update ChangeLog. 2020-04-17 10:08:45 +03:00
Lauri Ojansivu 3558d711ff Merge pull request #3020 from ckavili/master
oidc server is updated to work with OCP OAuth
2020-04-17 10:02:38 +03:00
thewoosterisroot 72c1c49da3 Adding dockerfiles for multi-arch builds and manifest. 2020-04-16 19:30:50 +02:00
Cansu Kavili 93f8ff946f updated with env and annotation parameters 2020-04-15 09:54:22 +02:00
Cansu Kavili 61d582ffd9 updated with env and annotation parameters 2020-04-15 09:52:34 +02:00
Cansu Kavili 60384be5ff add annotations if specified in values file 2020-04-15 09:50:30 +02:00
Cansu Kavili 8ac305778a Add env parameters if specified in values file 2020-04-15 09:50:07 +02:00
Lauri Ojansivu 5be93741ee Update ChangeLog. 2020-04-15 03:44:30 +03:00
Lauri Ojansivu c2f91baaf9 Merge pull request #3019 from devinsm/mobile-friendly-checklist
Allows for use of checklist on mobile
2020-04-15 03:40:14 +03:00
Devin McAllester 9df5c0e2de Allows for use of checklist on mobile 2020-04-14 17:05:58 -07:00
Lauri Ojansivu f1a9e30956 v3.96 2020-04-15 00:05:48 +03:00
Lauri Ojansivu 4b202aaa08 Update ChangeLog. 2020-04-14 23:43:38 +03:00
Lauri Ojansivu 5ef1a92366 Update translations. 2020-04-14 23:41:22 +03:00
Lauri Ojansivu 1a836969e1 Remove 1.8.x files because Sandstorm now uses newest Meteor.
Thanks to xet7 !
2020-04-14 23:01:21 +03:00
Lauri Ojansivu 079b3da6ef Merge pull request #3015 from boeserwolf/master
Fix <p> margin in card labels
2020-04-14 21:23:41 +03:00
Lauri Ojansivu 13563e20e7 Merge pull request #3014 from salleman33/master
hide password auth with PASSWORD_LOGIN_ENABLED variable
2020-04-14 21:23:10 +03:00
Allemand 4f1330c777 Update docker-compose.yml 2020-04-14 09:12:39 +02:00
Allemand 94b558cf43 Update start-wekan.sh 2020-04-14 09:09:38 +02:00
Allemand e22e24d917 Update start-wekan.bat 2020-04-14 09:07:49 +02:00
Allemand 55545a52ca Update wekan-help 2020-04-14 09:05:14 +02:00
Allemand e0cb5a0c7a Update config 2020-04-14 09:01:43 +02:00
Allemand efd3faf9ef Update sandstorm-pkgdef.capnp 2020-04-14 08:58:47 +02:00
Allemand 2fd577d2c0 Update docker-compose.yml 2020-04-14 08:56:52 +02:00
Allemand 659e8418f7 Update Dockerfile 2020-04-14 08:51:59 +02:00
Allemand 18610d2fe6 Update settings.js 2020-04-14 08:49:47 +02:00
Allemand 1389ed0fce Update layouts.js 2020-04-14 08:48:51 +02:00
boeserwolf c39364af83 Fix margin in card labels 2020-04-13 23:47:00 +02:00
salleman 3e817257ef hide password auth with PASSWORD_LOGIN_ENABLED variable 2020-04-13 21:06:27 +02:00
Lauri Ojansivu c5f782976b Update Sandstorm release script for newest Meteor and Node 12.x 2020-04-13 10:55:00 -04:00
Lauri Ojansivu b841f798f8 Merge pull request #3013 from salleman33/master
debug isBoardAdmin on main page
2020-04-13 17:48:16 +03:00
salleman 35ae07e2a6 debug isBoardAdmin on main page 2020-04-13 15:46:29 +02:00
Cansu Kavili 2c792363bf ocp hack added 2020-04-13 14:03:19 +02:00
Lauri Ojansivu a68ecc079d Update translations. 2020-04-13 02:12:10 +03:00
Lauri Ojansivu b2d546579c Fix capnp workaround to work with newest Meteor and Node 12.x.
Thanks to kentonv !
2020-04-13 02:09:46 +03:00
Romulus Urakagi Tsai 5899b9366c Change coagmano:stylus package to 1.1.0 since 2.0.0 building is super slow 2020-03-02 02:10:54 +00:00
Romulus Urakagi Tsai 4b196d5378 Merge branch 'master' of https://github.com/wekan/wekan into lib-change 2020-02-13 09:02:26 +00:00
Romulus Urakagi Tsai b34ed58289 Start writing migration 2020-02-13 08:47:41 +00:00
Romulus Urakagi Tsai 617fdaeb74 Fix sandstorm storage path 2020-01-14 07:06:20 +00:00
Romulus Urakagi Tsai d26bf04bfa Change to relative path and /var/attachments to store 2020-01-14 06:29:34 +00:00
Romulus Tsai 蔡仲明 a365899312 Merge branch 'master' of https://github.com/PDIS/wekan 2020-01-02 17:34:10 +08:00
Romulus Urakagi Tsai 6ebd6defe9 Uploading dialog done 2020-01-02 09:16:28 +00:00
Romulus Urakagi Tsai 93337c20f8 Change upload routine, add upload popup 2019-12-24 08:57:34 +00:00
Romulus Urakagi Tsai 6cdd464f54 Uploaded done, but uploading not 2019-11-27 09:40:19 +00:00
蔡仲明 (Romulus Urakagi Tsai) 3e0bedd8c7 Merge pull request #1 from wekan/master
Update master
2019-11-21 11:25:56 +08:00
Romulus Urakagi Tsai 4dcdec0084 Attachment upload from card done, need to fix download link 2019-11-20 10:40:09 +00:00
Romulus Urakagi Tsai 05c53ca01d Trying to upload an attachment with Meteor-Files 2019-11-18 01:47:26 +00:00
202 changed files with 10899 additions and 9941 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
{
"presets": [
"@babel/preset-stage-3"
]
}
+3 -3
View File
@@ -1,4 +1,4 @@
FROM ubuntu:disco
FROM ubuntu:rolling
LABEL maintainer="sgr"
ENV BUILD_DEPS="gnupg gosu bsdtar wget curl bzip2 g++ build-essential python git ca-certificates iproute2"
@@ -6,8 +6,8 @@ ENV DEBIAN_FRONTEND=noninteractive
ENV \
DEBUG=false \
NODE_VERSION=8.17.0 \
METEOR_RELEASE=1.8.1 \
NODE_VERSION=12.16.3 \
METEOR_RELEASE=1.10.2 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
NPM_VERSION=latest \
+1
View File
@@ -11,6 +11,7 @@
"browser": true,
"meteor": true
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
@@ -65,9 +65,9 @@ apps:
parts:
mongodb:
source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.22.tgz
source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.2.6.tgz
plugin: dump
stage-packages: [libssl1.0.0]
stage-packages: [libssl1.0.0, libcurl3]
filesets:
mongo:
- usr
@@ -81,19 +81,20 @@ parts:
wekan:
source: .
plugin: nodejs
node-engine: 8.17.0
node-engine: 12.16.3
node-packages:
- node-gyp
- node-pre-gyp
- fibers@2.0.0
- fibers
build-packages:
- ca-certificates
- apt-utils
- python
# - python3
- python3
- g++
- capnproto
- curl
- libcurl3
- execstack
- nodejs
- npm
@@ -104,6 +105,18 @@ parts:
rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules
# Create the OpenAPI specification
rm -rf .build
## Use Meteor 1.8.x on Snap
#rm -rf .meteor
#mv .snap-meteor-1.8/.meteor .
#mv .snap-meteor-1.8/package.json .
#mv .snap-meteor-1.8/package-lock.json .
## Meteor 1.9.x has changes to Buffer() => Buffer.alloc(), so reverting those
#mv .snap-meteor-1.8/cfs_access-point.txt fix-download-unicode/
#mv .snap-meteor-1.8/export.js models/
#mv .snap-meteor-1.8/wekanCreator.js models/
#mv .snap-meteor-1.8/ldap.js packages/wekan-ldap/server/ldap.js
#mv .snap-meteor-1.8/oidc_server.js packages/wekan-oidc/oidc_server.js
rm -rf .snap-meteor-1.8
#mkdir -p .build/python
#cd .build/python
#git clone --depth 1 -b master https://github.com/Kronuz/esprima-python
@@ -83,7 +83,7 @@ parts:
wekan:
source: .
plugin: nodejs
node-engine: 12.14.1
node-engine: 12.14.3
node-packages:
- node-gyp
- node-pre-gyp
+3 -3
View File
@@ -6,7 +6,7 @@
meteor-base@1.4.0
# Build system
ecmascript@0.14.2
ecmascript@0.14.3
standard-minifier-css@1.6.0
standard-minifier-js@2.6.0
mquandalle:jade
@@ -23,7 +23,7 @@ dburles:collection-helpers
idmontie:migrations
matb33:collection-hooks
matteodem:easy-search
mongo@1.9.0
mongo@1.10.0
mquandalle:collection-mutations
# Account system
@@ -72,7 +72,7 @@ simple:rest-accounts-password
useraccounts:core
email@1.2.3
horka:swipebox
dynamic-import@0.5.1
dynamic-import@0.5.2
staringatlights:fast-render
accounts-password@1.6.0
+1 -1
View File
@@ -1 +1 @@
METEOR@1.10.1
METEOR@1.10.2
+7 -7
View File
@@ -104,13 +104,13 @@ meteorspark:util@0.2.0
minifier-css@1.5.0
minifier-js@2.6.0
minifiers@1.1.8-faster-rebuild.0
minimongo@1.5.0
minimongo@1.6.0
mobile-status-bar@1.1.0
modern-browsers@0.1.5
modules@0.15.0
modules-runtime@0.12.0
momentjs:moment@2.24.0
mongo@1.9.1
momentjs:moment@2.26.0
mongo@1.10.0
mongo-decimal@0.1.1
mongo-dev-server@1.1.0
mongo-id@1.0.7
@@ -127,7 +127,7 @@ mquandalle:mousetrap-bindglobal@0.0.1
mquandalle:perfect-scrollbar@0.6.5_2
msavin:usercache@1.8.0
npm-bcrypt@0.9.3
npm-mongo@3.7.0
npm-mongo@3.7.1
oauth@1.3.0
oauth2@1.3.0
observe-sequence@1.0.16
@@ -162,11 +162,11 @@ simple:json-routes@2.1.0
simple:rest-accounts-password@1.1.2
simple:rest-bearer-token-parser@1.0.1
simple:rest-json-error-handler@1.0.1
socket-stream-client@0.2.3
socket-stream-client@0.3.0
softwarerero:accounts-t9n@1.3.11
spacebars@1.0.15
spacebars-compiler@1.1.3
srp@1.0.12
srp@1.1.0
standard-minifier-css@1.6.0
standard-minifier-js@2.6.0
staringatlights:fast-render@3.2.0
@@ -181,7 +181,7 @@ tracker@1.2.0
twbs:bootstrap@3.3.6
ui@1.0.13
underscore@1.0.10
url@1.2.0
url@1.3.0
useraccounts:core@1.14.2
useraccounts:flow-routing@1.14.2
useraccounts:unstyled@1.14.2
@@ -1,20 +0,0 @@
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.
notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.3.5-remove-old-dev-bundle-link
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base
1.8.3-split-jquery-from-blaze
-2
View File
@@ -1,2 +0,0 @@
dev_bundle
local
-7
View File
@@ -1,7 +0,0 @@
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
# - ensuring you don't accidentally deploy one app on top of another
# - providing package authors with aggregated statistics
dvyihgykyzec6y1dpg
Whitespace-only changes.
-100
View File
@@ -1,100 +0,0 @@
# Meteor packages used by this project, one per line.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base@1.4.0
# Build system
ecmascript@0.13.2
standard-minifier-css@1.5.4
standard-minifier-js@2.5.2
mquandalle:jade
# Polyfills
es5-shim@4.8.0
# Collections
aldeed:collection2
cfs:standard-packages
cottz:publish-relations
dburles:collection-helpers
idmontie:migrations
matb33:collection-hooks
matteodem:easy-search
mongo@1.7.0
mquandalle:collection-mutations
# Account system
kenton:accounts-sandstorm
service-configuration@1.0.11
useraccounts:unstyled
useraccounts:flow-routing
wekan-ldap
wekan-accounts-cas
wekan-accounts-oidc
# Utilities
check@1.3.1
jquery@1.11.10
random@1.1.0
reactive-dict@1.3.0
session@1.2.0
tracker@1.2.0
underscore@1.0.10
3stack:presence
alethes:pages
arillo:flow-router-helpers
audit-argument-checks@1.0.7
kadira:blaze-layout
kadira:dochead
mquandalle:autofocus
ongoworks:speakingurl
raix:handlebar-helpers
tap:i18n
http@1.4.2
# UI components
blaze
reactive-var@1.0.11
fortawesome:fontawesome
mousetrap:mousetrap
mquandalle:jquery-textcomplete
mquandalle:jquery-ui-drag-drop-sort
mquandalle:mousetrap-bindglobal
peerlibrary:blaze-components@=0.15.1
templates:tabs
verron:autosize
simple:json-routes
rajit:bootstrap3-datepicker
shell-server@0.4.0
simple:rest-accounts-password
useraccounts:core
email@1.2.3
horka:swipebox
dynamic-import@0.5.1
staringatlights:fast-render
accounts-password@1.5.2
cfs:gridfs
rzymek:fullcalendar
momentjs:moment@2.22.2
browser-policy-framing@1.1.0
mquandalle:moment
msavin:usercache
wekan-scrollbar
mquandalle:perfect-scrollbar
mdg:meteor-apm-agent@3.2.0-rc.0!
# Keep stylus in 1.1.0, because building v2 takes extra 52 minutes.
coagmano:stylus@1.1.0!
lucasantoniassi:accounts-lockout
meteorhacks:subs-manager
meteorhacks:picker
lamhieu:unblock
meteorhacks:aggregate@1.3.0
wekan-markdown
konecty:mongo-counter
percolate:synced-cron
easylogic:summernote
cfs:filesystem
ostrio:cookies
-2
View File
@@ -1,2 +0,0 @@
server
browser
-1
View File
@@ -1 +0,0 @@
METEOR@1.8.3
-198
View File
@@ -1,198 +0,0 @@
3stack:presence@1.1.2
accounts-base@1.4.5
accounts-oauth@1.1.16
accounts-password@1.5.2
aldeed:collection2@2.10.0
aldeed:collection2-core@1.2.0
aldeed:schema-deny@1.1.0
aldeed:schema-index@1.1.1
aldeed:simple-schema@1.5.4
alethes:pages@1.8.6
allow-deny@1.1.0
arillo:flow-router-helpers@0.5.2
audit-argument-checks@1.0.7
autoupdate@1.6.0
babel-compiler@7.4.2
babel-runtime@1.4.0
base64@1.0.12
binary-heap@1.0.11
blaze@2.3.4
blaze-tools@1.0.10
boilerplate-generator@1.6.0
browser-policy-common@1.0.11
browser-policy-framing@1.1.0
caching-compiler@1.2.1
caching-html-compiler@1.1.3
callback-hook@1.2.0
cfs:access-point@0.1.49
cfs:base-package@0.0.30
cfs:collection@0.5.5
cfs:collection-filters@0.2.4
cfs:data-man@0.0.6
cfs:file@0.1.17
cfs:filesystem@0.1.2
cfs:gridfs@0.0.34
cfs:http-methods@0.0.32
cfs:http-publish@0.0.13
cfs:power-queue@0.9.11
cfs:reactive-list@0.0.9
cfs:reactive-property@0.0.4
cfs:standard-packages@0.5.10
cfs:storage-adapter@0.2.4
cfs:tempstore@0.1.6
cfs:upload-http@0.0.20
cfs:worker@0.1.5
check@1.3.1
chuangbo:cookie@1.1.0
coagmano:stylus@1.1.0
coffeescript@1.0.17
cottz:publish-relations@2.0.8
dburles:collection-helpers@1.1.0
ddp@1.4.0
ddp-client@2.3.3
ddp-common@1.4.0
ddp-rate-limiter@1.0.7
ddp-server@2.3.0
deps@1.0.12
diff-sequence@1.1.1
dynamic-import@0.5.1
easylogic:summernote@0.8.8
ecmascript@0.13.2
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.9.0
ecmascript-runtime-server@0.8.0
ejson@1.1.1
email@1.2.3
es5-shim@4.8.0
fastclick@1.0.13
fetch@0.1.1
fortawesome:fontawesome@4.7.0
geojson-utils@1.0.10
horka:swipebox@1.0.2
hot-code-push@1.0.4
html-tools@1.0.11
htmljs@1.0.11
http@1.4.2
id-map@1.1.0
idmontie:migrations@1.0.3
inter-process-messaging@0.1.0
jquery@1.11.11
kadira:blaze-layout@2.3.0
kadira:dochead@1.5.0
kadira:flow-router@2.12.1
kenton:accounts-sandstorm@0.7.0
konecty:mongo-counter@0.0.5_3
lamhieu:meteorx@2.1.1
lamhieu:unblock@1.0.0
launch-screen@1.1.1
livedata@1.0.18
localstorage@1.2.0
logging@1.1.20
lucasantoniassi:accounts-lockout@1.0.0
matb33:collection-hooks@0.9.1
matteodem:easy-search@1.6.4
mdg:meteor-apm-agent@3.2.5
mdg:validation-error@0.5.1
meteor@1.9.3
meteor-base@1.4.0
meteor-platform@1.2.6
meteorhacks:aggregate@1.3.0
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.4.3
minifier-js@2.5.1
minifiers@1.1.8-faster-rebuild.0
minimongo@1.4.5
mobile-status-bar@1.0.14
modern-browsers@0.1.4
modules@0.14.0
modules-runtime@0.11.0
momentjs:moment@2.24.0
mongo@1.7.0
mongo-decimal@0.1.1
mongo-dev-server@1.1.0
mongo-id@1.0.7
mongo-livedata@1.0.12
mousetrap:mousetrap@1.4.6_1
mquandalle:autofocus@1.0.0
mquandalle:collection-mutations@0.1.0
mquandalle:jade@0.4.9
mquandalle:jade-compiler@0.4.5
mquandalle:jquery-textcomplete@0.8.0_1
mquandalle:jquery-ui-drag-drop-sort@0.2.0
mquandalle:moment@1.0.1
mquandalle:mousetrap-bindglobal@0.0.1
mquandalle:perfect-scrollbar@0.6.5_2
msavin:usercache@1.8.0
npm-bcrypt@0.9.3
npm-mongo@3.2.0
oauth@1.2.8
oauth2@1.2.1
observe-sequence@1.0.16
ongoworks:speakingurl@1.1.0
ordered-dict@1.1.0
ostrio:cookies@2.5.0
peerlibrary:assert@0.3.0
peerlibrary:base-component@0.16.0
peerlibrary:blaze-components@0.15.1
peerlibrary:computed-field@0.10.0
peerlibrary:reactive-field@0.6.0
percolate:synced-cron@1.3.2
promise@0.11.2
raix:eventemitter@0.1.3
raix:handlebar-helpers@0.2.5
rajit:bootstrap3-datepicker@1.7.1_1
random@1.1.0
rate-limit@1.0.9
reactive-dict@1.3.0
reactive-var@1.0.11
reload@1.3.0
retry@1.1.0
routepolicy@1.1.0
rzymek:fullcalendar@3.8.0
server-render@0.3.1
service-configuration@1.0.11
session@1.2.0
sha@1.0.9
shell-server@0.4.0
simple:authenticate-user-by-token@1.0.1
simple:json-routes@2.1.0
simple:rest-accounts-password@1.1.2
simple:rest-bearer-token-parser@1.0.1
simple:rest-json-error-handler@1.0.1
socket-stream-client@0.2.2
softwarerero:accounts-t9n@1.3.11
spacebars@1.0.15
spacebars-compiler@1.1.3
srp@1.0.12
standard-minifier-css@1.5.4
standard-minifier-js@2.5.2
staringatlights:fast-render@3.2.0
staringatlights:inject-data@2.3.0
tap:i18n@1.8.2
templates:tabs@2.3.0
templating@1.3.2
templating-compiler@1.3.3
templating-runtime@1.3.2
templating-tools@1.1.2
tracker@1.2.0
twbs:bootstrap@3.3.6
ui@1.0.13
underscore@1.0.10
url@1.2.0
useraccounts:core@1.14.2
useraccounts:flow-routing@1.14.2
useraccounts:unstyled@1.14.2
verron:autosize@3.0.8
webapp@1.7.5
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-oidc@1.0.12
wekan-scrollbar@3.1.3
yasaricli:slugify@0.0.7
zimme:active-route@2.3.2
-914
View File
@@ -1,914 +0,0 @@
(function () {
/* Imports */
var Meteor = Package.meteor.Meteor;
var global = Package.meteor.global;
var meteorEnv = Package.meteor.meteorEnv;
var FS = Package['cfs:base-package'].FS;
var check = Package.check.check;
var Match = Package.check.Match;
var EJSON = Package.ejson.EJSON;
var HTTP = Package['cfs:http-methods'].HTTP;
/* Package-scope variables */
var rootUrlPathPrefix, baseUrl, getHeaders, getHeadersByCollection, _existingMountPoints, mountUrls;
(function(){
///////////////////////////////////////////////////////////////////////
// //
// packages/cfs_access-point/packages/cfs_access-point.js //
// //
///////////////////////////////////////////////////////////////////////
//
(function () {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/cfs:access-point/access-point-common.js //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ""; // 1
// Adjust the rootUrlPathPrefix if necessary // 2
if (rootUrlPathPrefix.length > 0) { // 3
if (rootUrlPathPrefix.slice(0, 1) !== '/') { // 4
rootUrlPathPrefix = '/' + rootUrlPathPrefix; // 5
} // 6
if (rootUrlPathPrefix.slice(-1) === '/') { // 7
rootUrlPathPrefix = rootUrlPathPrefix.slice(0, -1); // 8
} // 9
} // 10
// 11
// prepend ROOT_URL when isCordova // 12
if (Meteor.isCordova) { // 13
rootUrlPathPrefix = Meteor.absoluteUrl(rootUrlPathPrefix.replace(/^\/+/, '')).replace(/\/+$/, ''); // 14
} // 15
// 16
baseUrl = '/cfs'; // 17
FS.HTTP = FS.HTTP || {}; // 18
// 19
// Note the upload URL so that client uploader packages know what it is // 20
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files'; // 21
// 22
/** // 23
* @method FS.HTTP.setBaseUrl // 24
* @public // 25
* @param {String} newBaseUrl - Change the base URL for the HTTP GET and DELETE endpoints. // 26
* @returns {undefined} // 27
*/ // 28
FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { // 29
// 30
// Adjust the baseUrl if necessary // 31
if (newBaseUrl.slice(0, 1) !== '/') { // 32
newBaseUrl = '/' + newBaseUrl; // 33
} // 34
if (newBaseUrl.slice(-1) === '/') { // 35
newBaseUrl = newBaseUrl.slice(0, -1); // 36
} // 37
// 38
// Update the base URL // 39
baseUrl = newBaseUrl; // 40
// 41
// Change the upload URL so that client uploader packages know what it is // 42
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files'; // 43
// 44
// Remount URLs with the new baseUrl, unmounting the old, on the server only. // 45
// If existingMountPoints is empty, then we haven't run the server startup // 46
// code yet, so this new URL will be used at that point for the initial mount. // 47
if (Meteor.isServer && !FS.Utility.isEmpty(_existingMountPoints)) { // 48
mountUrls(); // 49
} // 50
}; // 51
// 52
/* // 53
* FS.File extensions // 54
*/ // 55
// 56
/** // 57
* @method FS.File.prototype.url Construct the file url // 58
* @public // 59
* @param {Object} [options] // 60
* @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
* @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
* @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
* @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
* @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself.
* @param {String} [options.uploading=null] A URL to return while the file is being uploaded. // 66
* @param {String} [options.storing=null] A URL to return while the file is being stored. // 67
* @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
* // 69
* Returns the HTTP URL for getting the file or its metadata. // 70
*/ // 71
FS.File.prototype.url = function(options) { // 72
var self = this; // 73
options = options || {}; // 74
options = FS.Utility.extend({ // 75
store: null, // 76
auth: null, // 77
download: false, // 78
metadata: false, // 79
brokenIsFine: false, // 80
uploading: null, // return this URL while uploading // 81
storing: null, // return this URL while storing // 82
filename: null // override the filename that is shown to the user // 83
}, options.hash || options); // check for "hash" prop if called as helper // 84
// 85
// Primarily useful for displaying a temporary image while uploading an image // 86
if (options.uploading && !self.isUploaded()) { // 87
return options.uploading; // 88
} // 89
// 90
if (self.isMounted()) { // 91
// See if we've stored in the requested store yet // 92
var storeName = options.store || self.collection.primaryStore.name; // 93
if (!self.hasStored(storeName)) { // 94
if (options.storing) { // 95
return options.storing; // 96
} else if (!options.brokenIsFine) { // 97
// We want to return null if we know the URL will be a broken // 98
// link because then we can avoid rendering broken links, broken // 99
// images, etc. // 100
return null; // 101
} // 102
} // 103
// 104
// Add filename to end of URL if we can determine one // 105
var filename = options.filename || self.name({store: storeName}); // 106
if (typeof filename === "string" && filename.length) { // 107
filename = '/' + filename; // 108
} else { // 109
filename = ''; // 110
} // 111
// 112
// TODO: Could we somehow figure out if the collection requires login? // 113
var authToken = ''; // 114
if (Meteor.isClient && typeof Accounts !== "undefined" && typeof Accounts._storedLoginToken === "function") { // 115
if (options.auth !== false) { // 116
// Add reactive deps on the user // 117
Meteor.userId(); // 118
// 119
var authObject = { // 120
authToken: Accounts._storedLoginToken() || '' // 121
}; // 122
// 123
// If it's a number, we use that as the expiration time (in seconds) // 124
if (options.auth === +options.auth) { // 125
authObject.expiration = FS.HTTP.now() + options.auth * 1000; // 126
} // 127
// 128
// Set the authToken // 129
var authString = JSON.stringify(authObject); // 130
authToken = FS.Utility.btoa(authString); // 131
} // 132
} else if (typeof options.auth === "string") { // 133
// If the user supplies auth token the user will be responsible for // 134
// updating // 135
authToken = options.auth; // 136
} // 137
// 138
// Construct query string // 139
var params = {}; // 140
if (authToken !== '') { // 141
params.token = authToken; // 142
} // 143
if (options.download) { // 144
params.download = true; // 145
} // 146
if (options.store) { // 147
// We use options.store here instead of storeName because we want to omit the queryString // 148
// whenever possible, allowing users to have "clean" URLs if they want. The server will // 149
// assume the first store defined on the server, which means that we are assuming that // 150
// the first on the client is also the first on the server. If that's not the case, the // 151
// store option should be supplied. // 152
params.store = options.store; // 153
} // 154
var queryString = FS.Utility.encodeParams(params); // 155
if (queryString.length) { // 156
queryString = '?' + queryString; // 157
} // 158
// 159
// Determine which URL to use // 160
var area; // 161
if (options.metadata) { // 162
area = '/record'; // 163
} else { // 164
area = '/files'; // 165
} // 166
// 167
// Construct and return the http method url // 168
return rootUrlPathPrefix + baseUrl + area + '/' + self.collection.name + '/' + self._id + filename + queryString; // 169
} // 170
// 171
}; // 172
// 173
// 174
// 175
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function () {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/cfs:access-point/access-point-handlers.js //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
getHeaders = []; // 1
getHeadersByCollection = {}; // 2
// 3
FS.HTTP.Handlers = {}; // 4
// 5
/** // 6
* @method FS.HTTP.Handlers.Del // 7
* @public // 8
* @returns {any} response // 9
* // 10
* HTTP DEL request handler // 11
*/ // 12
FS.HTTP.Handlers.Del = function httpDelHandler(ref) { // 13
var self = this; // 14
var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 15
// 16
// If DELETE request, validate with 'remove' allow/deny, delete the file, and return // 17
FS.Utility.validateAction(ref.collection.files._validators['remove'], ref.file, self.userId); // 18
// 19
/* // 20
* From the DELETE spec: // 21
* A successful response SHOULD be 200 (OK) if the response includes an // 22
* entity describing the status, 202 (Accepted) if the action has not // 23
* yet been enacted, or 204 (No Content) if the action has been enacted // 24
* but the response does not include an entity. // 25
*/ // 26
self.setStatusCode(200); // 27
// 28
return { // 29
deleted: !!ref.file.remove() // 30
}; // 31
}; // 32
// 33
/** // 34
* @method FS.HTTP.Handlers.GetList // 35
* @public // 36
* @returns {Object} response // 37
* // 38
* HTTP GET file list request handler // 39
*/ // 40
FS.HTTP.Handlers.GetList = function httpGetListHandler() { // 41
// Not Yet Implemented // 42
// Need to check publications and return file list based on // 43
// what user is allowed to see // 44
}; // 45
// 46
/* // 47
requestRange will parse the range set in request header - if not possible it // 48
will throw fitting errors and autofill range for both partial and full ranges // 49
// 50
throws error or returns the object: // 51
{ // 52
start // 53
end // 54
length // 55
unit // 56
partial // 57
} // 58
*/ // 59
var requestRange = function(req, fileSize) { // 60
if (req) { // 61
if (req.headers) { // 62
var rangeString = req.headers.range; // 63
// 64
// Make sure range is a string // 65
if (rangeString === ''+rangeString) { // 66
// 67
// range will be in the format "bytes=0-32767" // 68
var parts = rangeString.split('='); // 69
var unit = parts[0]; // 70
// 71
// Make sure parts consists of two strings and range is of type "byte" // 72
if (parts.length == 2 && unit == 'bytes') { // 73
// Parse the range // 74
var range = parts[1].split('-'); // 75
var start = Number(range[0]); // 76
var end = Number(range[1]); // 77
// 78
// Fix invalid ranges? // 79
if (range[0] != start) start = 0; // 80
if (range[1] != end || !end) end = fileSize - 1; // 81
// 82
// Make sure range consists of a start and end point of numbers and start is less than end // 83
if (start < end) { // 84
// 85
var partSize = 0 - start + end + 1; // 86
// 87
// Return the parsed range // 88
return { // 89
start: start, // 90
end: end, // 91
length: partSize, // 92
size: fileSize, // 93
unit: unit, // 94
partial: (partSize < fileSize) // 95
}; // 96
// 97
} else { // 98
throw new Meteor.Error(416, "Requested Range Not Satisfiable"); // 99
} // 100
// 101
} else { // 102
// The first part should be bytes // 103
throw new Meteor.Error(416, "Requested Range Unit Not Satisfiable"); // 104
} // 105
// 106
} else { // 107
// No range found // 108
} // 109
// 110
} else { // 111
// throw new Error('No request headers set for _parseRange function'); // 112
} // 113
} else { // 114
throw new Error('No request object passed to _parseRange function'); // 115
} // 116
// 117
return { // 118
start: 0, // 119
end: fileSize - 1, // 120
length: fileSize, // 121
size: fileSize, // 122
unit: 'bytes', // 123
partial: false // 124
}; // 125
}; // 126
// 127
/** // 128
* @method FS.HTTP.Handlers.Get // 129
* @public // 130
* @returns {any} response // 131
* // 132
* HTTP GET request handler // 133
*/ // 134
FS.HTTP.Handlers.Get = function httpGetHandler(ref) { // 135
var self = this; // 136
// Once we have the file, we can test allow/deny validators // 137
// XXX: pass on the "share" query eg. ?share=342hkjh23ggj for shared url access? // 138
FS.Utility.validateAction(ref.collection._validators['download'], ref.file, self.userId /*, self.query.shareId*/); // 139
// 140
var storeName = ref.storeName; // 141
// 142
// If no storeName was specified, use the first defined storeName // 143
if (typeof storeName !== "string") { // 144
// No store handed, we default to primary store // 145
storeName = ref.collection.primaryStore.name; // 146
} // 147
// 148
// Get the storage reference // 149
var storage = ref.collection.storesLookup[storeName]; // 150
// 151
if (!storage) { // 152
throw new Meteor.Error(404, "Not Found", 'There is no store "' + storeName + '"'); // 153
} // 154
// 155
// Get the file // 156
var copyInfo = ref.file.copies[storeName]; // 157
// 158
if (!copyInfo) { // 159
throw new Meteor.Error(404, "Not Found", 'This file was not stored in the ' + storeName + ' store'); // 160
} // 161
// 162
// Set the content type for file // 163
if (typeof copyInfo.type === "string") { // 164
self.setContentType(copyInfo.type); // 165
} else { // 166
self.setContentType('application/octet-stream'); // 167
} // 168
// 169
// Add 'Content-Disposition' header if requested a download/attachment URL // 170
if (typeof ref.download !== "undefined") { // 171
var filename = ref.filename || copyInfo.name; // 172
self.addHeader('Content-Disposition', 'attachment; filename="' + filename + '"'); // 173
} else { // 174
self.addHeader('Content-Disposition', 'inline'); // 175
} // 176
// 177
// Get the contents range from request // 178
var range = requestRange(self.request, copyInfo.size); // 179
// 180
// Some browsers cope better if the content-range header is // 181
// still included even for the full file being returned. // 182
self.addHeader('Content-Range', range.unit + ' ' + range.start + '-' + range.end + '/' + range.size); // 183
// 184
// If a chunk/range was requested instead of the whole file, serve that' // 185
if (range.partial) { // 186
self.setStatusCode(206, 'Partial Content'); // 187
} else { // 188
self.setStatusCode(200, 'OK'); // 189
} // 190
// 191
// Add any other global custom headers and collection-specific custom headers // 192
FS.Utility.each(getHeaders.concat(getHeadersByCollection[ref.collection.name] || []), function(header) { // 193
self.addHeader(header[0], header[1]); // 194
}); // 195
// 196
// Inform clients about length (or chunk length in case of ranges) // 197
self.addHeader('Content-Length', range.length); // 198
// 199
// Last modified header (updatedAt from file info) // 200
self.addHeader('Last-Modified', copyInfo.updatedAt.toUTCString()); // 201
// 202
// Inform clients that we accept ranges for resumable chunked downloads // 203
self.addHeader('Accept-Ranges', range.unit); // 204
// 205
if (FS.debug) console.log('Read file "' + (ref.filename || copyInfo.name) + '" ' + range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);
// 207
var readStream = storage.adapter.createReadStream(ref.file, {start: range.start, end: range.end}); // 208
// 209
readStream.on('error', function(err) { // 210
// Send proper error message on get error // 211
if (err.message && err.statusCode) { // 212
self.Error(new Meteor.Error(err.statusCode, err.message)); // 213
} else { // 214
self.Error(new Meteor.Error(503, 'Service unavailable')); // 215
} // 216
}); // 217
// 218
readStream.pipe(self.createWriteStream()); // 219
}; // 220
const originalHandler = FS.HTTP.Handlers.Get;
FS.HTTP.Handlers.Get = function (ref) {
//console.log(ref.filename);
try {
var userAgent = (this.requestHeaders['user-agent']||'').toLowerCase();
if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('trident') >= 0 || userAgent.indexOf('chrome') >= 0) {
ref.filename = encodeURIComponent(ref.filename);
} else if(userAgent.indexOf('firefox') >= 0) {
ref.filename = new Buffer(ref.filename).toString('binary');
} else {
/* safari*/
ref.filename = new Buffer(ref.filename).toString('binary');
}
} catch (ex){
ref.filename = 'tempfix';
}
return originalHandler.call(this, ref);
};
// 221
/** // 222
* @method FS.HTTP.Handlers.PutInsert // 223
* @public // 224
* @returns {Object} response object with _id property // 225
* // 226
* HTTP PUT file insert request handler // 227
*/ // 228
FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { // 229
var self = this; // 230
var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 231
// 232
FS.debug && console.log("HTTP PUT (insert) handler"); // 233
// 234
// Create the nice FS.File // 235
var fileObj = new FS.File(); // 236
// 237
// Set its name // 238
fileObj.name(opts.filename || null); // 239
// 240
// Attach the readstream as the file's data // 241
fileObj.attachData(self.createReadStream(), {type: self.requestHeaders['content-type'] || 'application/octet-stream'});
// 243
// Validate with insert allow/deny // 244
FS.Utility.validateAction(ref.collection.files._validators['insert'], fileObj, self.userId); // 245
// 246
// Insert file into collection, triggering readStream storage // 247
ref.collection.insert(fileObj); // 248
// 249
// Send response // 250
self.setStatusCode(200); // 251
// 252
// Return the new file id // 253
return {_id: fileObj._id}; // 254
}; // 255
// 256
/** // 257
* @method FS.HTTP.Handlers.PutUpdate // 258
* @public // 259
* @returns {Object} response object with _id and chunk properties // 260
* // 261
* HTTP PUT file update chunk request handler // 262
*/ // 263
FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { // 264
var self = this; // 265
var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 266
// 267
var chunk = parseInt(opts.chunk, 10); // 268
if (isNaN(chunk)) chunk = 0; // 269
// 270
FS.debug && console.log("HTTP PUT (update) handler received chunk: ", chunk); // 271
// 272
// Validate with insert allow/deny; also mounts and retrieves the file // 273
FS.Utility.validateAction(ref.collection.files._validators['insert'], ref.file, self.userId); // 274
// 275
self.createReadStream().pipe( FS.TempStore.createWriteStream(ref.file, chunk) ); // 276
// 277
// Send response // 278
self.setStatusCode(200); // 279
// 280
return { _id: ref.file._id, chunk: chunk }; // 281
}; // 282
// 283
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
(function () {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// packages/cfs:access-point/access-point-server.js //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
var path = Npm.require("path"); // 1
// 2
HTTP.publishFormats({ // 3
fileRecordFormat: function (input) { // 4
// Set the method scope content type to json // 5
this.setContentType('application/json'); // 6
if (FS.Utility.isArray(input)) { // 7
return EJSON.stringify(FS.Utility.map(input, function (obj) { // 8
return FS.Utility.cloneFileRecord(obj); // 9
})); // 10
} else { // 11
return EJSON.stringify(FS.Utility.cloneFileRecord(input)); // 12
} // 13
} // 14
}); // 15
// 16
/** // 17
* @method FS.HTTP.setHeadersForGet // 18
* @public // 19
* @param {Array} headers - List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
* @param {Array|String} [collections] - Which collections the headers should be added for. Omit this argument to add the header for all collections.
* @returns {undefined} // 22
*/ // 23
FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { // 24
if (typeof collections === "string") { // 25
collections = [collections]; // 26
} // 27
if (collections) { // 28
FS.Utility.each(collections, function(collectionName) { // 29
getHeadersByCollection[collectionName] = headers || []; // 30
}); // 31
} else { // 32
getHeaders = headers || []; // 33
} // 34
}; // 35
// 36
/** // 37
* @method FS.HTTP.publish // 38
* @public // 39
* @param {FS.Collection} collection // 40
* @param {Function} func - Publish function that returns a cursor. // 41
* @returns {undefined} // 42
* // 43
* Publishes all documents returned by the cursor at a GET URL // 44
* with the format baseUrl/record/collectionName. The publish // 45
* function `this` is similar to normal `Meteor.publish`. // 46
*/ // 47
FS.HTTP.publish = function fsHttpPublish(collection, func) { // 48
var name = baseUrl + '/record/' + collection.name; // 49
// Mount collection listing URL using http-publish package // 50
HTTP.publish({ // 51
name: name, // 52
defaultFormat: 'fileRecordFormat', // 53
collection: collection, // 54
collectionGet: true, // 55
collectionPost: false, // 56
documentGet: true, // 57
documentPut: false, // 58
documentDelete: false // 59
}, func); // 60
// 61
FS.debug && console.log("Registered HTTP method GET URLs:\n\n" + name + '\n' + name + '/:id\n'); // 62
}; // 63
// 64
/** // 65
* @method FS.HTTP.unpublish // 66
* @public // 67
* @param {FS.Collection} collection // 68
* @returns {undefined} // 69
* // 70
* Unpublishes a restpoint created by a call to `FS.HTTP.publish` // 71
*/ // 72
FS.HTTP.unpublish = function fsHttpUnpublish(collection) { // 73
// Mount collection listing URL using http-publish package // 74
HTTP.unpublish(baseUrl + '/record/' + collection.name); // 75
}; // 76
// 77
_existingMountPoints = {}; // 78
// 79
/** // 80
* @method defaultSelectorFunction // 81
* @private // 82
* @returns { collection, file } // 83
* // 84
* This is the default selector function // 85
*/ // 86
var defaultSelectorFunction = function() { // 87
var self = this; // 88
// Selector function // 89
// // 90
// This function will have to return the collection and the // 91
// file. If file not found undefined is returned - if null is returned the // 92
// search was not possible // 93
var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 94
// 95
// Get the collection name from the url // 96
var collectionName = opts.collectionName; // 97
// 98
// Get the id from the url // 99
var id = opts.id; // 100
// 101
// Get the collection // 102
var collection = FS._collections[collectionName]; // 103
// 104
// Get the file if possible else return null // 105
var file = (id && collection)? collection.findOne({ _id: id }): null; // 106
// 107
// Return the collection and the file // 108
return { // 109
collection: collection, // 110
file: file, // 111
storeName: opts.store, // 112
download: opts.download, // 113
filename: opts.filename // 114
}; // 115
}; // 116
// 117
/* // 118
* @method FS.HTTP.mount // 119
* @public // 120
* @param {array of string} mountPoints mount points to map rest functinality on // 121
* @param {function} selector_f [selector] function returns `{ collection, file }` for mount points to work with // 122
* // 123
*/ // 124
FS.HTTP.mount = function(mountPoints, selector_f) { // 125
// We take mount points as an array and we get a selector function // 126
var selectorFunction = selector_f || defaultSelectorFunction; // 127
// 128
var accessPoint = { // 129
'stream': true, // 130
'auth': expirationAuth, // 131
'post': function(data) { // 132
// Use the selector for finding the collection and file reference // 133
var ref = selectorFunction.call(this); // 134
// 135
// We dont support post - this would be normal insert eg. of filerecord? // 136
throw new Meteor.Error(501, "Not implemented", "Post is not supported"); // 137
}, // 138
'put': function(data) { // 139
// Use the selector for finding the collection and file reference // 140
var ref = selectorFunction.call(this); // 141
// 142
// Make sure we have a collection reference // 143
if (!ref.collection) // 144
throw new Meteor.Error(404, "Not Found", "No collection found"); // 145
// 146
// Make sure we have a file reference // 147
if (ref.file === null) { // 148
// No id supplied so we will create a new FS.File instance and // 149
// insert the supplied data. // 150
return FS.HTTP.Handlers.PutInsert.apply(this, [ref]); // 151
} else { // 152
if (ref.file) { // 153
return FS.HTTP.Handlers.PutUpdate.apply(this, [ref]); // 154
} else { // 155
throw new Meteor.Error(404, "Not Found", 'No file found'); // 156
} // 157
} // 158
}, // 159
'get': function(data) { // 160
// Use the selector for finding the collection and file reference // 161
var ref = selectorFunction.call(this); // 162
// 163
// Make sure we have a collection reference // 164
if (!ref.collection) // 165
throw new Meteor.Error(404, "Not Found", "No collection found"); // 166
// 167
// Make sure we have a file reference // 168
if (ref.file === null) { // 169
// No id supplied so we will return the published list of files ala // 170
// http.publish in json format // 171
return FS.HTTP.Handlers.GetList.apply(this, [ref]); // 172
} else { // 173
if (ref.file) { // 174
return FS.HTTP.Handlers.Get.apply(this, [ref]); // 175
} else { // 176
throw new Meteor.Error(404, "Not Found", 'No file found'); // 177
} // 178
} // 179
}, // 180
'delete': function(data) { // 181
// Use the selector for finding the collection and file reference // 182
var ref = selectorFunction.call(this); // 183
// 184
// Make sure we have a collection reference // 185
if (!ref.collection) // 186
throw new Meteor.Error(404, "Not Found", "No collection found"); // 187
// 188
// Make sure we have a file reference // 189
if (ref.file) { // 190
return FS.HTTP.Handlers.Del.apply(this, [ref]); // 191
} else { // 192
throw new Meteor.Error(404, "Not Found", 'No file found'); // 193
} // 194
} // 195
}; // 196
// 197
var accessPoints = {}; // 198
// 199
// Add debug message // 200
FS.debug && console.log('Registered HTTP method URLs:'); // 201
// 202
FS.Utility.each(mountPoints, function(mountPoint) { // 203
// Couple mountpoint and accesspoint // 204
accessPoints[mountPoint] = accessPoint; // 205
// Remember our mountpoints // 206
_existingMountPoints[mountPoint] = mountPoint; // 207
// Add debug message // 208
FS.debug && console.log(mountPoint); // 209
}); // 210
// 211
// XXX: HTTP:methods should unmount existing mounts in case of overwriting? // 212
HTTP.methods(accessPoints); // 213
// 214
}; // 215
// 216
/** // 217
* @method FS.HTTP.unmount // 218
* @public // 219
* @param {string | array of string} [mountPoints] Optional, if not specified all mountpoints are unmounted // 220
* // 221
*/ // 222
FS.HTTP.unmount = function(mountPoints) { // 223
// The mountPoints is optional, can be string or array if undefined then // 224
// _existingMountPoints will be used // 225
var unmountList; // 226
// Container for the mount points to unmount // 227
var unmountPoints = {}; // 228
// 229
if (typeof mountPoints === 'undefined') { // 230
// Use existing mount points - unmount all // 231
unmountList = _existingMountPoints; // 232
} else if (mountPoints === ''+mountPoints) { // 233
// Got a string // 234
unmountList = [mountPoints]; // 235
} else if (mountPoints.length) { // 236
// Got an array // 237
unmountList = mountPoints; // 238
} // 239
// 240
// If we have a list to unmount // 241
if (unmountList) { // 242
// Iterate over each item // 243
FS.Utility.each(unmountList, function(mountPoint) { // 244
// Check _existingMountPoints to make sure the mount point exists in our // 245
// context / was created by the FS.HTTP.mount // 246
if (_existingMountPoints[mountPoint]) { // 247
// Mark as unmount // 248
unmountPoints[mountPoint] = false; // 249
// Release // 250
delete _existingMountPoints[mountPoint]; // 251
} // 252
}); // 253
FS.debug && console.log('FS.HTTP.unmount:'); // 254
FS.debug && console.log(unmountPoints); // 255
// Complete unmount // 256
HTTP.methods(unmountPoints); // 257
} // 258
}; // 259
// 260
// ### FS.Collection maps on HTTP pr. default on the following restpoints: // 261
// * // 262
// baseUrl + '/files/:collectionName/:id/:filename', // 263
// baseUrl + '/files/:collectionName/:id', // 264
// baseUrl + '/files/:collectionName' // 265
// // 266
// Change/ replace the existing mount point by: // 267
// ```js // 268
// // unmount all existing // 269
// FS.HTTP.unmount(); // 270
// // Create new mount point // 271
// FS.HTTP.mount([ // 272
// '/cfs/files/:collectionName/:id/:filename', // 273
// '/cfs/files/:collectionName/:id', // 274
// '/cfs/files/:collectionName' // 275
// ]); // 276
// ``` // 277
// // 278
mountUrls = function mountUrls() { // 279
// We unmount first in case we are calling this a second time // 280
FS.HTTP.unmount(); // 281
// 282
FS.HTTP.mount([ // 283
baseUrl + '/files/:collectionName/:id/:filename', // 284
baseUrl + '/files/:collectionName/:id', // 285
baseUrl + '/files/:collectionName' // 286
]); // 287
}; // 288
// 289
// Returns the userId from URL token // 290
var expirationAuth = function expirationAuth() { // 291
var self = this; // 292
// 293
// Read the token from '/hello?token=base64' // 294
var encodedToken = self.query.token; // 295
// 296
FS.debug && console.log("token: "+encodedToken); // 297
// 298
if (!encodedToken || !Meteor.users) return false; // 299
// 300
// Check the userToken before adding it to the db query // 301
// Set the this.userId // 302
var tokenString = FS.Utility.atob(encodedToken); // 303
// 304
var tokenObject; // 305
try { // 306
tokenObject = JSON.parse(tokenString); // 307
} catch(err) { // 308
throw new Meteor.Error(400, 'Bad Request'); // 309
} // 310
// 311
// XXX: Do some check here of the object // 312
var userToken = tokenObject.authToken; // 313
if (userToken !== ''+userToken) { // 314
throw new Meteor.Error(400, 'Bad Request'); // 315
} // 316
// 317
// If we have an expiration token we should check that it's still valid // 318
if (tokenObject.expiration != null) { // 319
// check if its too old // 320
var now = Date.now(); // 321
if (tokenObject.expiration < now) { // 322
FS.debug && console.log('Expired token: ' + tokenObject.expiration + ' is less than ' + now); // 323
throw new Meteor.Error(500, 'Expired token'); // 324
} // 325
} // 326
// 327
// We are not on a secure line - so we have to look up the user... // 328
var user = Meteor.users.findOne({ // 329
$or: [ // 330
{'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)}, // 331
{'services.resume.loginTokens.token': userToken} // 332
] // 333
}); // 334
// 335
// Set the userId in the scope // 336
return user && user._id; // 337
}; // 338
// 339
HTTP.methods( // 340
{'/cfs/servertime': { // 341
get: function(data) { // 342
return Date.now().toString(); // 343
} // 344
} // 345
}); // 346
// 347
// Unify client / server api // 348
FS.HTTP.now = function() { // 349
return Date.now(); // 350
}; // 351
// 352
// Start up the basic mount points // 353
Meteor.startup(function () { // 354
mountUrls(); // 355
}); // 356
// 357
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}).call(this);
///////////////////////////////////////////////////////////////////////
}).call(this);
/* Exports */
if (typeof Package === 'undefined') Package = {};
Package['cfs:access-point'] = {};
})();
-238
View File
@@ -1,238 +0,0 @@
/* global JsonRoutes */
if (Meteor.isServer) {
// todo XXX once we have a real API in place, move that route there
// todo XXX also share the route definition between the client and the server
// so that we could use something like
// `ApiRoutes.path('boards/export', boardId)``
// on the client instead of copy/pasting the route path manually between the
// client and the server.
/**
* @operation export
* @tag Boards
*
* @summary This route is used to export the board.
*
* @description If user is already logged-in, pass loginToken as param
* "authToken": '/api/boards/:boardId/export?authToken=:token'
*
* See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
* for detailed explanations
*
* @param {string} boardId the ID of the board we are exporting
* @param {string} authToken the loginToken
*/
JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) {
const boardId = req.params.boardId;
let user = null;
const loginToken = req.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)) {
JsonRoutes.sendResult(res, {
code: 200,
data: exporter.build(),
});
} else {
// we could send an explicit error message, but on the other hand the only
// way to get there is by hacking the UI so let's keep it raw.
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 = new Buffer(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 = doc.createReadStream();
readStream.on('data', function(chunk) {
buffer = Buffer.concat([buffer, chunk]);
});
readStream.on('error', function(err) {
callback(err, 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(byBoard)
.fetch()
.map(attachment => {
return {
_id: attachment._id,
cardId: attachment.cardId,
// url: FlowRouter.url(attachment.url()),
file: getBase64DataSync(attachment),
name: attachment.original.name,
type: attachment.original.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);
}
}
-640
View File
@@ -1,640 +0,0 @@
import ldapjs from 'ldapjs';
import util from 'util';
import Bunyan from 'bunyan';
import { log_debug, log_info, log_warn, log_error } from './logger';
export default class LDAP {
constructor() {
this.ldapjs = ldapjs;
this.connected = false;
this.options = {
host: this.constructor.settings_get('LDAP_HOST'),
port: this.constructor.settings_get('LDAP_PORT'),
Reconnect: this.constructor.settings_get('LDAP_RECONNECT'),
timeout: this.constructor.settings_get('LDAP_TIMEOUT'),
connect_timeout: this.constructor.settings_get('LDAP_CONNECT_TIMEOUT'),
idle_timeout: this.constructor.settings_get('LDAP_IDLE_TIMEOUT'),
encryption: this.constructor.settings_get('LDAP_ENCRYPTION'),
ca_cert: this.constructor.settings_get('LDAP_CA_CERT'),
reject_unauthorized:
this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false,
Authentication: this.constructor.settings_get('LDAP_AUTHENTIFICATION'),
Authentication_UserDN: this.constructor.settings_get(
'LDAP_AUTHENTIFICATION_USERDN',
),
Authentication_Password: this.constructor.settings_get(
'LDAP_AUTHENTIFICATION_PASSWORD',
),
Authentication_Fallback: this.constructor.settings_get(
'LDAP_LOGIN_FALLBACK',
),
BaseDN: this.constructor.settings_get('LDAP_BASEDN'),
Internal_Log_Level: this.constructor.settings_get('INTERNAL_LOG_LEVEL'),
User_Authentication: this.constructor.settings_get(
'LDAP_USER_AUTHENTICATION',
),
User_Authentication_Field: this.constructor.settings_get(
'LDAP_USER_AUTHENTICATION_FIELD',
),
User_Attributes: this.constructor.settings_get('LDAP_USER_ATTRIBUTES'),
User_Search_Filter: this.constructor.settings_get(
'LDAP_USER_SEARCH_FILTER',
),
User_Search_Scope: this.constructor.settings_get(
'LDAP_USER_SEARCH_SCOPE',
),
User_Search_Field: this.constructor.settings_get(
'LDAP_USER_SEARCH_FIELD',
),
Search_Page_Size: this.constructor.settings_get('LDAP_SEARCH_PAGE_SIZE'),
Search_Size_Limit: this.constructor.settings_get(
'LDAP_SEARCH_SIZE_LIMIT',
),
group_filter_enabled: this.constructor.settings_get(
'LDAP_GROUP_FILTER_ENABLE',
),
group_filter_object_class: this.constructor.settings_get(
'LDAP_GROUP_FILTER_OBJECTCLASS',
),
group_filter_group_id_attribute: this.constructor.settings_get(
'LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE',
),
group_filter_group_member_attribute: this.constructor.settings_get(
'LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE',
),
group_filter_group_member_format: this.constructor.settings_get(
'LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT',
),
group_filter_group_name: this.constructor.settings_get(
'LDAP_GROUP_FILTER_GROUP_NAME',
),
};
}
static settings_get(name, ...args) {
let value = process.env[name];
if (value !== undefined) {
if (value === 'true' || value === 'false') {
value = JSON.parse(value);
} else if (value !== '' && !isNaN(value)) {
value = Number(value);
}
return value;
} else {
log_warn(`Lookup for unset variable: ${name}`);
}
}
connectSync(...args) {
if (!this._connectSync) {
this._connectSync = Meteor.wrapAsync(this.connectAsync, this);
}
return this._connectSync(...args);
}
searchAllSync(...args) {
if (!this._searchAllSync) {
this._searchAllSync = Meteor.wrapAsync(this.searchAllAsync, this);
}
return this._searchAllSync(...args);
}
connectAsync(callback) {
log_info('Init setup');
let replied = false;
const connectionOptions = {
url: `${this.options.host}:${this.options.port}`,
timeout: this.options.timeout,
connectTimeout: this.options.connect_timeout,
idleTimeout: this.options.idle_timeout,
reconnect: this.options.Reconnect,
};
if (this.options.Internal_Log_Level !== 'disabled') {
connectionOptions.log = new Bunyan({
name: 'ldapjs',
component: 'client',
stream: process.stderr,
level: this.options.Internal_Log_Level,
});
}
const tlsOptions = {
rejectUnauthorized: this.options.reject_unauthorized,
};
if (this.options.ca_cert && this.options.ca_cert !== '') {
// Split CA cert into array of strings
const chainLines = this.constructor
.settings_get('LDAP_CA_CERT')
.split('\n');
let cert = [];
const ca = [];
chainLines.forEach(line => {
cert.push(line);
if (line.match(/-END CERTIFICATE-/)) {
ca.push(cert.join('\n'));
cert = [];
}
});
tlsOptions.ca = ca;
}
if (this.options.encryption === 'ssl') {
connectionOptions.url = `ldaps://${connectionOptions.url}`;
connectionOptions.tlsOptions = tlsOptions;
} else {
connectionOptions.url = `ldap://${connectionOptions.url}`;
}
log_info('Connecting', connectionOptions.url);
log_debug(`connectionOptions${util.inspect(connectionOptions)}`);
this.client = ldapjs.createClient(connectionOptions);
this.bindSync = Meteor.wrapAsync(this.client.bind, this.client);
this.client.on('error', error => {
log_error('connection', error);
if (replied === false) {
replied = true;
callback(error, null);
}
});
this.client.on('idle', () => {
log_info('Idle');
this.disconnect();
});
this.client.on('close', () => {
log_info('Closed');
});
if (this.options.encryption === 'tls') {
// Set host parameter for tls.connect which is used by ldapjs starttls. This shouldn't be needed in newer nodejs versions (e.g v5.6.0).
// https://github.com/RocketChat/Rocket.Chat/issues/2035
// https://github.com/mcavage/node-ldapjs/issues/349
tlsOptions.host = this.options.host;
log_info('Starting TLS');
log_debug('tlsOptions', tlsOptions);
this.client.starttls(tlsOptions, null, (error, response) => {
if (error) {
log_error('TLS connection', error);
if (replied === false) {
replied = true;
callback(error, null);
}
return;
}
log_info('TLS connected');
this.connected = true;
if (replied === false) {
replied = true;
callback(null, response);
}
});
} else {
this.client.on('connect', response => {
log_info('LDAP connected');
this.connected = true;
if (replied === false) {
replied = true;
callback(null, response);
}
});
}
setTimeout(() => {
if (replied === false) {
log_error('connection time out', connectionOptions.connectTimeout);
replied = true;
callback(new Error('Timeout'));
}
}, connectionOptions.connectTimeout);
}
getUserFilter(username) {
const filter = [];
if (this.options.User_Search_Filter !== '') {
if (this.options.User_Search_Filter[0] === '(') {
filter.push(`${this.options.User_Search_Filter}`);
} else {
filter.push(`(${this.options.User_Search_Filter})`);
}
}
const usernameFilter = this.options.User_Search_Field.split(',').map(
item => `(${item}=${username})`,
);
if (usernameFilter.length === 0) {
log_error('LDAP_LDAP_User_Search_Field not defined');
} else if (usernameFilter.length === 1) {
filter.push(`${usernameFilter[0]}`);
} else {
filter.push(`(|${usernameFilter.join('')})`);
}
return `(&${filter.join('')})`;
}
bindUserIfNecessary(username, password) {
if (this.domainBinded === true) {
return;
}
if (!this.options.User_Authentication) {
return;
}
if (!this.options.BaseDN) throw new Error('BaseDN is not provided');
const userDn = `${this.options.User_Authentication_Field}=${username},${this.options.BaseDN}`;
this.bindSync(userDn, password);
this.domainBinded = true;
}
bindIfNecessary() {
if (this.domainBinded === true) {
return;
}
if (this.options.Authentication !== true) {
return;
}
log_info('Binding UserDN', this.options.Authentication_UserDN);
this.bindSync(
this.options.Authentication_UserDN,
this.options.Authentication_Password,
);
this.domainBinded = true;
}
searchUsersSync(username, page) {
this.bindIfNecessary();
const searchOptions = {
filter: this.getUserFilter(username),
scope: this.options.User_Search_Scope || 'sub',
sizeLimit: this.options.Search_Size_Limit,
};
if (!!this.options.User_Attributes)
searchOptions.attributes = this.options.User_Attributes.split(',');
if (this.options.Search_Page_Size > 0) {
searchOptions.paged = {
pageSize: this.options.Search_Page_Size,
pagePause: !!page,
};
}
log_info('Searching user', username);
log_debug('searchOptions', searchOptions);
log_debug('BaseDN', this.options.BaseDN);
if (page) {
return this.searchAllPaged(this.options.BaseDN, searchOptions, page);
}
return this.searchAllSync(this.options.BaseDN, searchOptions);
}
getUserByIdSync(id, attribute) {
this.bindIfNecessary();
const Unique_Identifier_Field = this.constructor
.settings_get('LDAP_UNIQUE_IDENTIFIER_FIELD')
.split(',');
let filter;
if (attribute) {
filter = new this.ldapjs.filters.EqualityFilter({
attribute,
value: new Buffer(id, 'hex'),
});
} else {
const filters = [];
Unique_Identifier_Field.forEach(item => {
filters.push(
new this.ldapjs.filters.EqualityFilter({
attribute: item,
value: new Buffer(id, 'hex'),
}),
);
});
filter = new this.ldapjs.filters.OrFilter({ filters });
}
const searchOptions = {
filter,
scope: 'sub',
};
log_info('Searching by id', id);
log_debug('search filter', searchOptions.filter.toString());
log_debug('BaseDN', this.options.BaseDN);
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
if (!Array.isArray(result) || result.length === 0) {
return;
}
if (result.length > 1) {
log_error('Search by id', id, 'returned', result.length, 'records');
}
return result[0];
}
getUserByUsernameSync(username) {
this.bindIfNecessary();
const searchOptions = {
filter: this.getUserFilter(username),
scope: this.options.User_Search_Scope || 'sub',
};
log_info('Searching user', username);
log_debug('searchOptions', searchOptions);
log_debug('BaseDN', this.options.BaseDN);
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
if (!Array.isArray(result) || result.length === 0) {
return;
}
if (result.length > 1) {
log_error(
'Search by username',
username,
'returned',
result.length,
'records',
);
}
return result[0];
}
getUserGroups(username, ldapUser) {
if (!this.options.group_filter_enabled) {
return true;
}
const filter = ['(&'];
if (this.options.group_filter_object_class !== '') {
filter.push(`(objectclass=${this.options.group_filter_object_class})`);
}
if (this.options.group_filter_group_member_attribute !== '') {
const format_value =
ldapUser[this.options.group_filter_group_member_format];
if (format_value) {
filter.push(
`(${this.options.group_filter_group_member_attribute}=${format_value})`,
);
}
}
filter.push(')');
const searchOptions = {
filter: filter.join('').replace(/#{username}/g, username),
scope: 'sub',
};
log_debug('Group list filter LDAP:', searchOptions.filter);
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
if (!Array.isArray(result) || result.length === 0) {
return [];
}
const grp_identifier = this.options.group_filter_group_id_attribute || 'cn';
const groups = [];
result.map(item => {
groups.push(item[grp_identifier]);
});
log_debug(`Groups: ${groups.join(', ')}`);
return groups;
}
isUserInGroup(username, ldapUser) {
if (!this.options.group_filter_enabled) {
return true;
}
const grps = this.getUserGroups(username, ldapUser);
const filter = ['(&'];
if (this.options.group_filter_object_class !== '') {
filter.push(`(objectclass=${this.options.group_filter_object_class})`);
}
if (this.options.group_filter_group_member_attribute !== '') {
const format_value =
ldapUser[this.options.group_filter_group_member_format];
if (format_value) {
filter.push(
`(${this.options.group_filter_group_member_attribute}=${format_value})`,
);
}
}
if (this.options.group_filter_group_id_attribute !== '') {
filter.push(
`(${this.options.group_filter_group_id_attribute}=${this.options.group_filter_group_name})`,
);
}
filter.push(')');
const searchOptions = {
filter: filter.join('').replace(/#{username}/g, username),
scope: 'sub',
};
log_debug('Group filter LDAP:', searchOptions.filter);
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
if (!Array.isArray(result) || result.length === 0) {
return false;
}
return true;
}
extractLdapEntryData(entry) {
const values = {
_raw: entry.raw,
};
Object.keys(values._raw).forEach(key => {
const value = values._raw[key];
if (!['thumbnailPhoto', 'jpegPhoto'].includes(key)) {
if (value instanceof Buffer) {
values[key] = value.toString();
} else {
values[key] = value;
}
}
});
return values;
}
searchAllPaged(BaseDN, options, page) {
this.bindIfNecessary();
const processPage = ({ entries, title, end, next }) => {
log_info(title);
// Force LDAP idle to wait the record processing
this.client._updateIdle(true);
page(null, entries, {
end,
next: () => {
// Reset idle timer
this.client._updateIdle();
next && next();
},
});
};
this.client.search(BaseDN, options, (error, res) => {
if (error) {
log_error(error);
page(error);
return;
}
res.on('error', error => {
log_error(error);
page(error);
return;
});
let entries = [];
const internalPageSize =
options.paged && options.paged.pageSize > 0
? options.paged.pageSize * 2
: 500;
res.on('searchEntry', entry => {
entries.push(this.extractLdapEntryData(entry));
if (entries.length >= internalPageSize) {
processPage({
entries,
title: 'Internal Page',
end: false,
});
entries = [];
}
});
res.on('page', (result, next) => {
if (!next) {
this.client._updateIdle(true);
processPage({
entries,
title: 'Final Page',
end: true,
});
} else if (entries.length) {
log_info('Page');
processPage({
entries,
title: 'Page',
end: false,
next,
});
entries = [];
}
});
res.on('end', () => {
if (entries.length) {
processPage({
entries,
title: 'Final Page',
end: true,
});
entries = [];
}
});
});
}
searchAllAsync(BaseDN, options, callback) {
this.bindIfNecessary();
this.client.search(BaseDN, options, (error, res) => {
if (error) {
log_error(error);
callback(error);
return;
}
res.on('error', error => {
log_error(error);
callback(error);
return;
});
const entries = [];
res.on('searchEntry', entry => {
entries.push(this.extractLdapEntryData(entry));
});
res.on('end', () => {
log_info('Search result count', entries.length);
callback(null, entries);
});
});
}
authSync(dn, password) {
log_info('Authenticating', dn);
try {
if (password === '') {
throw new Error('Password is not provided');
}
this.bindSync(dn, password);
log_info('Authenticated', dn);
return true;
} catch (error) {
log_info('Not authenticated', dn);
log_debug('error', error);
return false;
}
}
disconnect() {
this.connected = false;
this.domainBinded = false;
log_info('Disconecting');
this.client.unbind();
}
}
-163
View File
@@ -1,163 +0,0 @@
Oidc = {};
OAuth.registerService('oidc', 2, null, function(query) {
var debug = process.env.DEBUG || false;
var token = getToken(query);
if (debug) console.log('XXX: register token:', token);
var accessToken = token.access_token || token.id_token;
var expiresAt = +new Date() + 1000 * parseInt(token.expires_in, 10);
var userinfo = getUserInfo(accessToken);
if (debug) console.log('XXX: userinfo:', userinfo);
var serviceData = {};
serviceData.id = userinfo[process.env.OAUTH2_ID_MAP]; // || userinfo["id"];
serviceData.username = userinfo[process.env.OAUTH2_USERNAME_MAP]; // || userinfo["uid"];
serviceData.fullname = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"];
serviceData.accessToken = accessToken;
serviceData.expiresAt = expiresAt;
serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"];
if (accessToken) {
var tokenContent = getTokenContent(accessToken);
var fields = _.pick(
tokenContent,
getConfiguration().idTokenWhitelistFields,
);
_.extend(serviceData, fields);
}
if (token.refresh_token) serviceData.refreshToken = token.refresh_token;
if (debug) console.log('XXX: serviceData:', serviceData);
var profile = {};
profile.name = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"];
profile.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"];
if (debug) console.log('XXX: profile:', profile);
return {
serviceData: serviceData,
options: { profile: profile },
};
});
var userAgent = 'Meteor';
if (Meteor.release) {
userAgent += '/' + Meteor.release;
}
var getToken = function(query) {
var debug = process.env.DEBUG || false;
var config = getConfiguration();
if (config.tokenEndpoint.includes('https://')) {
var serverTokenEndpoint = config.tokenEndpoint;
} else {
var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint;
}
var requestPermissions = config.requestPermissions;
var response;
try {
response = HTTP.post(serverTokenEndpoint, {
headers: {
Accept: 'application/json',
'User-Agent': userAgent,
},
params: {
code: query.code,
client_id: config.clientId,
client_secret: OAuth.openSecret(config.secret),
redirect_uri: OAuth._redirectUri('oidc', config),
grant_type: 'authorization_code',
scope: requestPermissions,
state: query.state,
},
});
} catch (err) {
throw _.extend(
new Error(
'Failed to get token from OIDC ' +
serverTokenEndpoint +
': ' +
err.message,
),
{ response: err.response },
);
}
if (response.data.error) {
// if the http response was a json object with an error attribute
throw new Error(
'Failed to complete handshake with OIDC ' +
serverTokenEndpoint +
': ' +
response.data.error,
);
} else {
if (debug) console.log('XXX: getToken response: ', response.data);
return response.data;
}
};
var getUserInfo = function(accessToken) {
var debug = process.env.DEBUG || false;
var config = getConfiguration();
// Some userinfo endpoints use a different base URL than the authorization or token endpoints.
// This logic allows the end user to override the setting by providing the full URL to userinfo in their config.
if (config.userinfoEndpoint.includes('https://')) {
var serverUserinfoEndpoint = config.userinfoEndpoint;
} else {
var serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint;
}
var response;
try {
response = HTTP.get(serverUserinfoEndpoint, {
headers: {
'User-Agent': userAgent,
Authorization: 'Bearer ' + accessToken,
},
});
} catch (err) {
throw _.extend(
new Error(
'Failed to fetch userinfo from OIDC ' +
serverUserinfoEndpoint +
': ' +
err.message,
),
{ response: err.response },
);
}
if (debug) console.log('XXX: getUserInfo response: ', response.data);
return response.data;
};
var getConfiguration = function() {
var config = ServiceConfiguration.configurations.findOne({ service: 'oidc' });
if (!config) {
throw new ServiceConfiguration.ConfigError('Service oidc not configured.');
}
return config;
};
var getTokenContent = function(token) {
var content = null;
if (token) {
try {
var parts = token.split('.');
var header = JSON.parse(new Buffer(parts[0], 'base64').toString());
content = JSON.parse(new Buffer(parts[1], 'base64').toString());
var signature = new Buffer(parts[2], 'base64');
var signed = parts[0] + '.' + parts[1];
} catch (err) {
this.content = {
exp: 0,
};
}
}
return content;
};
Oidc.retrieveCredential = function(credentialToken, credentialSecret) {
return OAuth.retrieveCredential(credentialToken, credentialSecret);
};
File diff suppressed because it is too large. Load diff
-73
View File
@@ -1,73 +0,0 @@
{
"name": "wekan",
"version": "v3.95.0",
"description": "Open-Source kanban",
"private": true,
"scripts": {
"lint": "eslint --cache --ext .js --ignore-path .eslintignore .",
"lint:eslint:fix": "eslint --ext .js --ignore-path .eslintignore --fix .",
"lint:staged": "lint-staged",
"prettify": "prettier --write '**/*.js' '**/*.jsx'",
"test": "npm run lint"
},
"lint-staged": {
"*.js": [
"meteor npm run prettify",
"meteor npm run lint:eslint:fix",
"git add --force"
],
"*.jsx": [
"meteor npm run prettify",
"meteor npm run lint:eslint:fix",
"git add --force"
],
"*.json": [
"prettier --write",
"git add --force"
]
},
"pre-commit": "lint:staged",
"eslintConfig": {
"extends": "@meteorjs/eslint-config-meteor"
},
"repository": {
"type": "git",
"url": "git+https://github.com/wekan/wekan.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/wekan/wekan/issues"
},
"homepage": "https://wekan.github.io",
"devDependencies": {
"eslint": "^6.8.0",
"eslint-config-meteor": "^0.1.1",
"eslint-config-prettier": "^6.10.0",
"eslint-import-resolver-meteor": "^0.4.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-meteor": "^6.0.0",
"eslint-plugin-prettier": "^3.1.2",
"lint-staged": "^10.0.8",
"pre-commit": "^1.2.2",
"prettier": "^1.19.1",
"prettier-eslint": "^9.0.1"
},
"dependencies": {
"@babel/runtime": "^7.8.7",
"ajv": "^6.12.0",
"babel-runtime": "^6.26.0",
"bcrypt": "^4.0.1",
"bson": "^4.0.3",
"bunyan": "^1.8.12",
"es6-promise": "^4.2.8",
"gridfs-stream": "^1.1.1",
"ldapjs": "^1.0.2",
"meteor-node-stubs": "^1.0.0",
"mongodb": "^3.5.5",
"os": "^0.1.1",
"page": "^1.11.5",
"qs": "^6.9.1",
"source-map-support": "^0.5.16",
"xss": "^1.0.6"
}
}
-853
View File
@@ -1,853 +0,0 @@
const DateString = Match.Where(function(dateAsString) {
check(dateAsString, String);
return moment(dateAsString, moment.ISO_8601).isValid();
});
export class WekanCreator {
constructor(data) {
// we log current date, to use the same timestamp for all our actions.
// this helps to retrieve all elements performed by the same import.
this._nowDate = new Date();
// The object creation dates, indexed by Wekan id
// (so we only parse actions once!)
this.createdAt = {
board: null,
cards: {},
lists: {},
swimlanes: {},
};
// The object creator Wekan Id, indexed by the object Wekan id
// (so we only parse actions once!)
this.createdBy = {
cards: {}, // only cards have a field for that
};
// Map of labels Wekan ID => Wekan ID
this.labels = {};
// Map of swimlanes Wekan ID => Wekan ID
this.swimlanes = {};
// Map of lists Wekan ID => Wekan ID
this.lists = {};
// Map of cards Wekan ID => Wekan ID
this.cards = {};
// Map of comments Wekan ID => Wekan ID
this.commentIds = {};
// Map of attachments Wekan ID => Wekan ID
this.attachmentIds = {};
// Map of checklists Wekan ID => Wekan ID
this.checklists = {};
// Map of checklistItems Wekan ID => Wekan ID
this.checklistItems = {};
// The comments, indexed by Wekan card id (to map when importing cards)
this.comments = {};
// Map of rules Wekan ID => Wekan ID
this.rules = {};
// the members, indexed by Wekan member id => Wekan user ID
this.members = data.membersMapping ? data.membersMapping : {};
// Map of triggers Wekan ID => Wekan ID
this.triggers = {};
// Map of actions Wekan ID => Wekan ID
this.actions = {};
// maps a wekanCardId to an array of wekanAttachments
this.attachments = {};
}
/**
* If dateString is provided,
* return the Date it represents.
* If not, will return the date when it was first called.
* This is useful for us, as we want all import operations to
* have the exact same date for easier later retrieval.
*
* @param {String} dateString a properly formatted Date
*/
_now(dateString) {
if (dateString) {
return new Date(dateString);
}
if (!this._nowDate) {
this._nowDate = new Date();
}
return this._nowDate;
}
/**
* if wekanUserId is provided and we have a mapping,
* return it.
* Otherwise return current logged user.
* @param wekanUserId
* @private
*/
_user(wekanUserId) {
if (wekanUserId && this.members[wekanUserId]) {
return this.members[wekanUserId];
}
return Meteor.userId();
}
checkActivities(wekanActivities) {
check(wekanActivities, [
Match.ObjectIncluding({
activityType: String,
createdAt: DateString,
}),
]);
// XXX we could perform more thorough checks based on action type
}
checkBoard(wekanBoard) {
check(
wekanBoard,
Match.ObjectIncluding({
archived: Boolean,
title: String,
// XXX refine control by validating 'color' against a list of
// allowed values (is it worth the maintenance?)
color: String,
permission: Match.Where(value => {
return ['private', 'public'].indexOf(value) >= 0;
}),
}),
);
}
checkCards(wekanCards) {
check(wekanCards, [
Match.ObjectIncluding({
archived: Boolean,
dateLastActivity: DateString,
labelIds: [String],
title: String,
sort: Number,
}),
]);
}
checkLabels(wekanLabels) {
check(wekanLabels, [
Match.ObjectIncluding({
// XXX refine control by validating 'color' against a list of allowed
// values (is it worth the maintenance?)
color: String,
}),
]);
}
checkLists(wekanLists) {
check(wekanLists, [
Match.ObjectIncluding({
archived: Boolean,
title: String,
}),
]);
}
checkSwimlanes(wekanSwimlanes) {
check(wekanSwimlanes, [
Match.ObjectIncluding({
archived: Boolean,
title: String,
}),
]);
}
checkChecklists(wekanChecklists) {
check(wekanChecklists, [
Match.ObjectIncluding({
cardId: String,
title: String,
}),
]);
}
checkChecklistItems(wekanChecklistItems) {
check(wekanChecklistItems, [
Match.ObjectIncluding({
cardId: String,
title: String,
}),
]);
}
checkRules(wekanRules) {
check(wekanRules, [
Match.ObjectIncluding({
triggerId: String,
actionId: String,
title: String,
}),
]);
}
checkTriggers(wekanTriggers) {
// XXX More check based on trigger type
check(wekanTriggers, [
Match.ObjectIncluding({
activityType: String,
desc: String,
}),
]);
}
getMembersToMap(data) {
// we will work on the list itself (an ordered array of objects) when a
// mapping is done, we add a 'wekan' field to the object representing the
// imported member
const membersToMap = data.members;
const users = data.users;
// auto-map based on username
membersToMap.forEach(importedMember => {
importedMember.id = importedMember.userId;
delete importedMember.userId;
const user = users.filter(user => {
return user._id === importedMember.id;
})[0];
if (user.profile && user.profile.fullname) {
importedMember.fullName = user.profile.fullname;
}
importedMember.username = user.username;
const wekanUser = Users.findOne({ username: importedMember.username });
if (wekanUser) {
importedMember.wekanId = wekanUser._id;
}
});
return membersToMap;
}
checkActions(wekanActions) {
// XXX More check based on action type
check(wekanActions, [
Match.ObjectIncluding({
actionType: String,
desc: String,
}),
]);
}
// You must call parseActions before calling this one.
createBoardAndLabels(boardToImport) {
const boardToCreate = {
archived: boardToImport.archived,
color: boardToImport.color,
// very old boards won't have a creation activity so no creation date
createdAt: this._now(boardToImport.createdAt),
labels: [],
members: [
{
userId: Meteor.userId(),
wekanId: Meteor.userId(),
isActive: true,
isAdmin: true,
isNoComments: false,
isCommentOnly: false,
swimlaneId: false,
},
],
// Standalone Export has modifiedAt missing, adding modifiedAt to fix it
modifiedAt: this._now(boardToImport.modifiedAt),
permission: boardToImport.permission,
slug: getSlug(boardToImport.title) || 'board',
stars: 0,
title: boardToImport.title,
};
// now add other members
if (boardToImport.members) {
boardToImport.members.forEach(wekanMember => {
// do we already have it in our list?
if (
!boardToCreate.members.some(
member => member.wekanId === wekanMember.wekanId,
)
)
boardToCreate.members.push({
...wekanMember,
userId: wekanMember.wekanId,
});
});
}
boardToImport.labels.forEach(label => {
const labelToCreate = {
_id: Random.id(6),
color: label.color,
name: label.name,
};
// We need to remember them by Wekan ID, as this is the only ref we have
// when importing cards.
this.labels[label._id] = labelToCreate._id;
boardToCreate.labels.push(labelToCreate);
});
const boardId = Boards.direct.insert(boardToCreate);
Boards.direct.update(boardId, {
$set: {
modifiedAt: this._now(),
},
});
// log activity
Activities.direct.insert({
activityType: 'importBoard',
boardId,
createdAt: this._now(),
source: {
id: boardToImport.id,
system: 'Wekan',
},
// We attribute the import to current user,
// not the author from the original object.
userId: this._user(),
});
return boardId;
}
/**
* Create the Wekan cards corresponding to the supplied Wekan cards,
* as well as all linked data: activities, comments, and attachments
* @param wekanCards
* @param boardId
* @returns {Array}
*/
createCards(wekanCards, boardId) {
const result = [];
wekanCards.forEach(card => {
const cardToCreate = {
archived: card.archived,
boardId,
// very old boards won't have a creation activity so no creation date
createdAt: this._now(this.createdAt.cards[card._id]),
dateLastActivity: this._now(),
description: card.description,
listId: this.lists[card.listId],
swimlaneId: this.swimlanes[card.swimlaneId],
sort: card.sort,
title: card.title,
// we attribute the card to its creator if available
userId: this._user(this.createdBy.cards[card._id]),
isOvertime: card.isOvertime || false,
startAt: card.startAt ? this._now(card.startAt) : null,
dueAt: card.dueAt ? this._now(card.dueAt) : null,
spentTime: card.spentTime || null,
};
// add labels
if (card.labelIds) {
cardToCreate.labelIds = card.labelIds.map(wekanId => {
return this.labels[wekanId];
});
}
// add members {
if (card.members) {
const wekanMembers = [];
// we can't just map, as some members may not have been mapped
card.members.forEach(sourceMemberId => {
if (this.members[sourceMemberId]) {
const wekanId = this.members[sourceMemberId];
// we may map multiple Wekan members to the same wekan user
// in which case we risk adding the same user multiple times
if (!wekanMembers.find(wId => wId === wekanId)) {
wekanMembers.push(wekanId);
}
}
return true;
});
if (wekanMembers.length > 0) {
cardToCreate.members = wekanMembers;
}
}
// set color
if (card.color) {
cardToCreate.color = card.color;
}
// insert card
const cardId = Cards.direct.insert(cardToCreate);
// keep track of Wekan id => Wekan id
this.cards[card._id] = cardId;
// // log activity
// Activities.direct.insert({
// activityType: 'importCard',
// boardId,
// cardId,
// createdAt: this._now(),
// listId: cardToCreate.listId,
// source: {
// id: card._id,
// system: 'Wekan',
// },
// // we attribute the import to current user,
// // not the author of the original card
// userId: this._user(),
// });
// add comments
const comments = this.comments[card._id];
if (comments) {
comments.forEach(comment => {
const commentToCreate = {
boardId,
cardId,
createdAt: this._now(comment.createdAt),
text: comment.text,
// we attribute the comment to the original author, default to current user
userId: this._user(comment.userId),
};
// dateLastActivity will be set from activity insert, no need to
// update it ourselves
const commentId = CardComments.direct.insert(commentToCreate);
this.commentIds[comment._id] = commentId;
// Activities.direct.insert({
// activityType: 'addComment',
// boardId: commentToCreate.boardId,
// cardId: commentToCreate.cardId,
// commentId,
// createdAt: this._now(commentToCreate.createdAt),
// // we attribute the addComment (not the import)
// // to the original author - it is needed by some UI elements.
// userId: commentToCreate.userId,
// });
});
}
const attachments = this.attachments[card._id];
const wekanCoverId = card.coverId;
if (attachments) {
attachments.forEach(att => {
const file = new FS.File();
// Simulating file.attachData on the client generates multiple errors
// - HEAD returns null, which causes exception down the line
// - the template then tries to display the url to the attachment which causes other errors
// so we make it server only, and let UI catch up once it is done, forget about latency comp.
const self = this;
if (Meteor.isServer) {
if (att.url) {
file.attachData(att.url, function(error) {
file.boardId = boardId;
file.cardId = cardId;
file.userId = self._user(att.userId);
// The field source will only be used to prevent adding
// attachments' related activities automatically
file.source = 'import';
if (error) {
throw error;
} else {
const wekanAtt = Attachments.insert(file, () => {
// we do nothing
});
self.attachmentIds[att._id] = wekanAtt._id;
//
if (wekanCoverId === att._id) {
Cards.direct.update(cardId, {
$set: {
coverId: wekanAtt._id,
},
});
}
}
});
} else if (att.file) {
file.attachData(
new Buffer(att.file, 'base64'),
{
type: att.type,
},
error => {
file.name(att.name);
file.boardId = boardId;
file.cardId = cardId;
file.userId = self._user(att.userId);
// The field source will only be used to prevent adding
// attachments' related activities automatically
file.source = 'import';
if (error) {
throw error;
} else {
const wekanAtt = Attachments.insert(file, () => {
// we do nothing
});
this.attachmentIds[att._id] = wekanAtt._id;
//
if (wekanCoverId === att._id) {
Cards.direct.update(cardId, {
$set: {
coverId: wekanAtt._id,
},
});
}
}
},
);
}
}
// todo XXX set cover - if need be
});
}
result.push(cardId);
});
return result;
}
// Create labels if they do not exist and load this.labels.
createLabels(wekanLabels, board) {
wekanLabels.forEach(label => {
const color = label.color;
const name = label.name;
const existingLabel = board.getLabel(name, color);
if (existingLabel) {
this.labels[label.id] = existingLabel._id;
} else {
const idLabelCreated = board.pushLabel(name, color);
this.labels[label.id] = idLabelCreated;
}
});
}
createLists(wekanLists, boardId) {
wekanLists.forEach((list, listIndex) => {
const listToCreate = {
archived: list.archived,
boardId,
// We are being defensing here by providing a default date (now) if the
// creation date wasn't found on the action log. This happen on old
// Wekan boards (eg from 2013) that didn't log the 'createList' action
// we require.
createdAt: this._now(this.createdAt.lists[list.id]),
title: list.title,
sort: list.sort ? list.sort : listIndex,
};
const listId = Lists.direct.insert(listToCreate);
Lists.direct.update(listId, {
$set: {
updatedAt: this._now(),
},
});
this.lists[list._id] = listId;
// // log activity
// Activities.direct.insert({
// activityType: 'importList',
// boardId,
// createdAt: this._now(),
// listId,
// source: {
// id: list._id,
// system: 'Wekan',
// },
// // We attribute the import to current user,
// // not the creator of the original object
// userId: this._user(),
// });
});
}
createSwimlanes(wekanSwimlanes, boardId) {
wekanSwimlanes.forEach((swimlane, swimlaneIndex) => {
const swimlaneToCreate = {
archived: swimlane.archived,
boardId,
// We are being defensing here by providing a default date (now) if the
// creation date wasn't found on the action log. This happen on old
// Wekan boards (eg from 2013) that didn't log the 'createList' action
// we require.
createdAt: this._now(this.createdAt.swimlanes[swimlane._id]),
title: swimlane.title,
sort: swimlane.sort ? swimlane.sort : swimlaneIndex,
};
// set color
if (swimlane.color) {
swimlaneToCreate.color = swimlane.color;
}
const swimlaneId = Swimlanes.direct.insert(swimlaneToCreate);
Swimlanes.direct.update(swimlaneId, {
$set: {
updatedAt: this._now(),
},
});
this.swimlanes[swimlane._id] = swimlaneId;
});
}
createChecklists(wekanChecklists) {
const result = [];
wekanChecklists.forEach((checklist, checklistIndex) => {
// Create the checklist
const checklistToCreate = {
cardId: this.cards[checklist.cardId],
title: checklist.title,
createdAt: checklist.createdAt,
sort: checklist.sort ? checklist.sort : checklistIndex,
};
const checklistId = Checklists.direct.insert(checklistToCreate);
this.checklists[checklist._id] = checklistId;
result.push(checklistId);
});
return result;
}
createTriggers(wekanTriggers, boardId) {
wekanTriggers.forEach(trigger => {
if (trigger.hasOwnProperty('labelId')) {
trigger.labelId = this.labels[trigger.labelId];
}
if (trigger.hasOwnProperty('memberId')) {
trigger.memberId = this.members[trigger.memberId];
}
trigger.boardId = boardId;
const oldId = trigger._id;
delete trigger._id;
this.triggers[oldId] = Triggers.direct.insert(trigger);
});
}
createActions(wekanActions, boardId) {
wekanActions.forEach(action => {
if (action.hasOwnProperty('labelId')) {
action.labelId = this.labels[action.labelId];
}
if (action.hasOwnProperty('memberId')) {
action.memberId = this.members[action.memberId];
}
action.boardId = boardId;
const oldId = action._id;
delete action._id;
this.actions[oldId] = Actions.direct.insert(action);
});
}
createRules(wekanRules, boardId) {
wekanRules.forEach(rule => {
// Create the rule
rule.boardId = boardId;
rule.triggerId = this.triggers[rule.triggerId];
rule.actionId = this.actions[rule.actionId];
delete rule._id;
Rules.direct.insert(rule);
});
}
createChecklistItems(wekanChecklistItems) {
wekanChecklistItems.forEach((checklistitem, checklistitemIndex) => {
// Create the checklistItem
const checklistItemTocreate = {
title: checklistitem.title,
checklistId: this.checklists[checklistitem.checklistId],
cardId: this.cards[checklistitem.cardId],
sort: checklistitem.sort ? checklistitem.sort : checklistitemIndex,
isFinished: checklistitem.isFinished,
};
const checklistItemId = ChecklistItems.direct.insert(
checklistItemTocreate,
);
this.checklistItems[checklistitem._id] = checklistItemId;
});
}
parseActivities(wekanBoard) {
wekanBoard.activities.forEach(activity => {
switch (activity.activityType) {
case 'addAttachment': {
// We have to be cautious, because the attachment could have been removed later.
// In that case Wekan still reports its addition, but removes its 'url' field.
// So we test for that
const wekanAttachment = wekanBoard.attachments.filter(attachment => {
return attachment._id === activity.attachmentId;
})[0];
if (typeof wekanAttachment !== 'undefined' && wekanAttachment) {
if (wekanAttachment.url || wekanAttachment.file) {
// we cannot actually create the Wekan attachment, because we don't yet
// have the cards to attach it to, so we store it in the instance variable.
const wekanCardId = activity.cardId;
if (!this.attachments[wekanCardId]) {
this.attachments[wekanCardId] = [];
}
this.attachments[wekanCardId].push(wekanAttachment);
}
}
break;
}
case 'addComment': {
const wekanComment = wekanBoard.comments.filter(comment => {
return comment._id === activity.commentId;
})[0];
const id = activity.cardId;
if (!this.comments[id]) {
this.comments[id] = [];
}
this.comments[id].push(wekanComment);
break;
}
case 'createBoard': {
this.createdAt.board = activity.createdAt;
break;
}
case 'createCard': {
const cardId = activity.cardId;
this.createdAt.cards[cardId] = activity.createdAt;
this.createdBy.cards[cardId] = activity.userId;
break;
}
case 'createList': {
const listId = activity.listId;
this.createdAt.lists[listId] = activity.createdAt;
break;
}
case 'createSwimlane': {
const swimlaneId = activity.swimlaneId;
this.createdAt.swimlanes[swimlaneId] = activity.createdAt;
break;
}
}
});
}
importActivities(activities, boardId) {
activities.forEach(activity => {
switch (activity.activityType) {
// Board related activities
// TODO: addBoardMember, removeBoardMember
case 'createBoard': {
Activities.direct.insert({
userId: this._user(activity.userId),
type: 'board',
activityTypeId: boardId,
activityType: activity.activityType,
boardId,
createdAt: this._now(activity.createdAt),
});
break;
}
// List related activities
// TODO: removeList, archivedList
case 'createList': {
Activities.direct.insert({
userId: this._user(activity.userId),
type: 'list',
activityType: activity.activityType,
listId: this.lists[activity.listId],
boardId,
createdAt: this._now(activity.createdAt),
});
break;
}
// Card related activities
// TODO: archivedCard, restoredCard, joinMember, unjoinMember
case 'createCard': {
Activities.direct.insert({
userId: this._user(activity.userId),
activityType: activity.activityType,
listId: this.lists[activity.listId],
cardId: this.cards[activity.cardId],
boardId,
createdAt: this._now(activity.createdAt),
});
break;
}
case 'moveCard': {
Activities.direct.insert({
userId: this._user(activity.userId),
oldListId: this.lists[activity.oldListId],
activityType: activity.activityType,
listId: this.lists[activity.listId],
cardId: this.cards[activity.cardId],
boardId,
createdAt: this._now(activity.createdAt),
});
break;
}
// Comment related activities
case 'addComment': {
Activities.direct.insert({
userId: this._user(activity.userId),
activityType: activity.activityType,
cardId: this.cards[activity.cardId],
commentId: this.commentIds[activity.commentId],
boardId,
createdAt: this._now(activity.createdAt),
});
break;
}
// Attachment related activities
case 'addAttachment': {
Activities.direct.insert({
userId: this._user(activity.userId),
type: 'card',
activityType: activity.activityType,
attachmentId: this.attachmentIds[activity.attachmentId],
cardId: this.cards[activity.cardId],
boardId,
createdAt: this._now(activity.createdAt),
});
break;
}
// Checklist related activities
case 'addChecklist': {
Activities.direct.insert({
userId: this._user(activity.userId),
activityType: activity.activityType,
cardId: this.cards[activity.cardId],
checklistId: this.checklists[activity.checklistId],
boardId,
createdAt: this._now(activity.createdAt),
});
break;
}
case 'addChecklistItem': {
Activities.direct.insert({
userId: this._user(activity.userId),
activityType: activity.activityType,
cardId: this.cards[activity.cardId],
checklistId: this.checklists[activity.checklistId],
checklistItemId: activity.checklistItemId.replace(
activity.checklistId,
this.checklists[activity.checklistId],
),
boardId,
createdAt: this._now(activity.createdAt),
});
break;
}
}
});
}
//check(board) {
check() {
//try {
// check(data, {
// membersMapping: Match.Optional(Object),
// });
// this.checkActivities(board.activities);
// this.checkBoard(board);
// this.checkLabels(board.labels);
// this.checkLists(board.lists);
// this.checkSwimlanes(board.swimlanes);
// this.checkCards(board.cards);
//this.checkChecklists(board.checklists);
// this.checkRules(board.rules);
// this.checkActions(board.actions);
//this.checkTriggers(board.triggers);
//this.checkChecklistItems(board.checklistItems);
//} catch (e) {
// throw new Meteor.Error('error-json-schema');
// }
}
create(board, currentBoardId) {
// TODO : Make isSandstorm variable global
const isSandstorm =
Meteor.settings &&
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && currentBoardId) {
const currentBoard = Boards.findOne(currentBoardId);
currentBoard.archive();
}
this.parseActivities(board);
const boardId = this.createBoardAndLabels(board);
this.createLists(board.lists, boardId);
this.createSwimlanes(board.swimlanes, boardId);
this.createCards(board.cards, boardId);
this.createChecklists(board.checklists);
this.createChecklistItems(board.checklistItems);
this.importActivities(board.activities, boardId);
this.createTriggers(board.triggers, boardId);
this.createActions(board.actions, boardId);
this.createRules(board.rules, boardId);
// XXX add members
return boardId;
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
dist: eoan
dist: focal
sudo: required
env:
TRAVIS_DOCKER_COMPOSE_VERSION: 1.24.0
TRAVIS_NODE_VERSION: 12.16.2
TRAVIS_NODE_VERSION: 12.16.3
TRAVIS_NPM_VERSION: latest
before_install:
+1 -1
View File
@@ -39,7 +39,7 @@ host = https://www.transifex.com
# tap:i18n requires us to use `-` separator in the language identifiers whereas
# Transifex uses a `_` separator, without an option to customize it on one side
# or the other, so we need to do a Manual mapping.
lang_map = bg_BG:bg, en_GB:en-GB, es_AR:es-AR, el_GR:el, fi_FI:fi, hu_HU:hu, id_ID:id, mn_MN:mn, no:nb, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, sl_SI:sl, zh_CN:zh-CN, zh_TW:zh-TW, zh_HK:zh-HK
lang_map = bg_BG:bg, en_GB:en-GB, es_AR:es-AR, es_CL:es-CL, el_GR:el, fi_FI:fi, hu_HU:hu, id_ID:id, mn_MN:mn, no:nb, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, sl_SI:sl, zh_CN:zh-CN, zh_TW:zh-TW, zh_HK:zh-HK
[wekan.application]
file_filter = i18n/<lang>.i18n.json
+336 -2
View File
@@ -1,3 +1,339 @@
# 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).
Thanks to willhseitz.
- [Theme: Natural](https://github.com/wekan/wekan/pull/3098).
You can select it from Board Settings / Change color / natural.
Thanks to compumatter and helioguardabaxo.
- [Theme: Modern](https://github.com/wekan/wekan/pull/3106).
Thanks to jeroenstoker com and helioguardabaxo.
- [Export board to HTML static page .zip archive](https://github.com/wekan/wekan/pull/3043).
Thanks to Lewiscowles1986.
and fixes the following bugs:
- [Change the swimlaneid of a card only if a new target swimlaneid is selected](https://github.com/wekan/wekan/pull/3108).
Thanks to marc1006.
- [Set '*' as default value for swimlane and list name in card move action](https://github.com/wekan/wekan/pull/3109).
Thanks to hickorysb and marc1006.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.03 2020-05-16 Wekan release
This release adds the following features:
- [Theme: Clearblue](https://github.com/wekan/wekan/pull/3093).
You can select it from Board Settings / Change color / clearblue.
Thanks to CidKramer.
and fixes the following bugs:
- [Fix Can't Scroll on All Boards on mobile phone. Added drag handles](https://github.com/wekan/wekan/issues/3096).
Thanks to xet7.
- [Try to fix Sandstorm Wekan Export menu](https://github.com/wekan/wekan/commit/1ac11d92ba8f38981c87db25e5b5e1fa2adb6968).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.02 2020-05-15 Wekan release
This release adds the following server platforms:
- [Android arm64/x64](https://github.com/wekan/wekan/wiki/Android).
Thanks to xet7.
and adds the following features:
- [Install Wekan to mobile homescreen icon and use fullscreen
PWA](https://github.com/wekan/wekan/commit/8d5adc04645e3e71423f16869f39b8d79969bccd).
[Docs for iOS and Android at wiki PWA page](https://github.com/wekan/wekan/wiki/PWA).
Thanks to xet7.
- [Add options to rebuild-wekan.sh to run Meteor in development mode where after
file change it rebuilds](https://github.com/wekan/wekan/commit/5f915ef966170ea7baca7ddeb11319bc08a26fef).
Thanks to xet7.
and adds the following updates:
- [Update dependencies](https://github.com/wekan/wekan/commit/75bdd33fda58ea0233f5b38c466bcb1a9b0406ab).
Thanks to xet7.
and adds the following translations:
- [Add Spanish (Chile)](https://github.com/wekan/wekan/commit/96507e6777ed77a324eaec9799c5b46b0d25ad26).
Thanks to isos, Transifex user.
and fixes the following bugs:
- [Fix Deleting linked card makes board not load](https://github.com/wekan/wekan/issues/2785).
Thanks to marc1006 and xet7.
- [Fix getStartDayOfWeek once again](https://github.com/wekan/wekan/pull/3061).
Thanks to marc1006.
- [Fix shortcuts list and support card shortcuts when hovering
a card](https://github.com/wekan/wekan/pull/3066).
Thanks to marc1006.
- [Add white-space:normal to copy-to-clipboard button in card
details](https://github.com/wekan/wekan/pull/3075).
Thanks to helioguardabaxo.
- [Fix avatar-image class](https://github.com/wekan/wekan/pull/3083).
Thanks to krupupakku.
- [Fix Swimlanes ID missing in new boards](https://github.com/wekan/wekan/pull/3088).
Thanks to krupupakku.
- [Fix REST API so Create card does now allow an empty member
list](https://github.com/wekan/wekan/pull/3084).
Thanks to wackazong.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.01 2020-04-28 Wekan release
This release adds the following updates:
- [Upgrade to Node v12.16.3](https://github.com/wekan/wekan/commit/1d89e96dd101c11913f1acdd6d16b5650eaf18a7).
Thanks to Node developers and xet7.
and fixes the following bugs:
- [Fix Docker builds](https://github.com/wekan/wekan/commit/280e66947e3afa878c41e876cf827ebcec81a2c6).
Thanks to xet7.
- [Fix Cards and Users API docs at https://wekan.github.io/api/ not generated because of
syntax error and new Javascript syntax](https://github.com/wekan/wekan/commit/9ae20a3f51e63c29f536e2f5b3e66a2c7d88c691).
Wekan uses wekan/releases/generate-docs*.sh Python code to generate OpenAPI docs,
it did not show any errors while generating docs, only left out parts of API docs.
This affected Wekan versions v3.94-v4.00.
Thanks to pvcon13 and xet7.
- [Fix list header height when cards count is shown](https://github.com/wekan/wekan/pull/3056).
Thanks to marc1006.
- [Smaller height for Add Board button](https://github.com/wekan/wekan/commit/6afc9259f084717a0cc3ce6d66979fd7c1471939).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v4.00 2020-04-27 Wekan release
This release fixes the following bugs:
- [Make sure that the board header buttons fit into one line even for devices with 360px width
resolution](https://github.com/wekan/wekan/pull/3052).
Thanks to marc1006.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.99 2020-04-27 Wekan release
This release fixes the following bugs:
- [Fix Boards are very hard to tap in mobile](https://github.com/wekan/wekan/pull/3051).
Thanks to marc1006.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.98 2020-04-25 Wekan release
News:
- There is now many mobile and desktop webbrowser fixes. Please test does your
favourite Javascript enabled webbrowser work, and add issues if something
does not work, and there is no existing issue about that yet.
- Desktop browser mode has setting for Show/Hide drag handles:
top right click username / Change Settings / Show desktop drag handles.
You can request desktop website also at mobile webbrowsers on Android.
At iOS requesting desktop website did not seem to work yet.
- At iOS Safari and Chrome, to see swimlane buttons you need to scroll to right.
Fixes to this and other issues are welcome as pull request.
This release adds the following new features:
- [Pre-fill the title of checklists (Trello-style)](https://github.com/wekan/wekan/pull/3030).
Thanks to boeserwolf.
- [Implement option to change the first day of the week in user settings](https://github.com/wekan/wekan/pull/3032).
Thanks to marc1006.
- [Add babel to build chain and linter. Enables fancy Javascript language
features like optional chaining, for developer happiness](https://github.com/wekan/wekan/pull/3034).
Thanks to boeserwolf.
- [Use only one 'Apply' button for applying the user settings](https://github.com/wekan/wekan/pull/3039).
Thanks to marc1006.
- [Allow variable height for board list items. Allow words in title/description to be able to break
and wrap onto the next line](https://github.com/wekan/wekan/pull/3046).
Thanks to marc1006.
and adds the following updates:
- [Upgrade to Meteor 1.10.2](https://github.com/wekan/wekan/commit/d1f98d0c472fb41e25fb29a9a6f6dae7db003f6f).
Thanks to Meteor developers and xet7.
- [Set Snap MongoDB compatibility to 4.2 according to Meteor ChangeLog](https://github.com/wekan/wekan/commit/7de18eccea3854db3be6197bf21afbfd3ddb65a6).
Thanks to xet7.
and fixes the following bugs:
- [Multiple lint issue fixes](https://github.com/wekan/wekan/pull/3031).
Thanks to marc1006.
- [Fix lint errors in lint error fix](https://github.com/wekan/wekan/commit/9e95c06415e614e587d684ff9660cc53c5f8c8d3).
Thanks to xet7.
- [Fix getStartDayOfWeek function](https://github.com/wekan/wekan/pull/3038).
Thanks to marc1006 and boeserwolf.
- Improve mobile devices support [Part1](https://github.com/wekan/wekan/pull/3040) and [Part2](https://github.com/wekan/wekan/pull/3045).
Thanks to marc1006.
- [Fix Wekan not load at all in Firefox v.68 for Android](https://github.com/wekan/wekan/commit/1235363465b824d26129d4aa74a4445f362c1a73).
Thanks to xet7.
- [Fix comment typo in docker-compose.yml](https://github.com/wekan/wekan/pull/3044).
Thanks to VictorioBerra.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.97 2020-04-19 Wekan release
This release adds the following new features:
- [Sortable boards](https://github.com/wekan/wekan/pull/3027).
Thanks to boeserwolf.
- [Added dockerfiles for multi-arch builds and manifest](https://github.com/wekan/wekan/pull/3023).
[In Progress](https://github.com/wekan/wekan/issues/2999).
Thanks to brokencode64.
- [Make linked card clickable](https://github.com/wekan/wekan/pull/3025).
Thanks to boeserwolf.
and fixes the following bugs:
- [Fix using checklists on mobile and iPad](https://github.com/wekan/wekan/pull/3019).
Thanks to devinsm.
- [Improve card layout on mobile devices](https://github.com/wekan/wekan/pull/3024).
Thanks to marc1006.
- [Make OCP OAuth work with Openshift 4.x](https://github.com/wekan/wekan/pull/3020).
Thanks to ckavili.
- [Remove old warning from Sandstorm import board data loss, because bug has been already
fixed](https://github.com/wekan/wekan/commit/960fe5163b6a2f7c3dca03b5e31d69611b49f079).
Thanks to aputsiaq and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.96 2020-04-15 Wekan release
This release adds the following Sandstorm updates:
- This is the first Sandstorm Wekan release that uses newest Meteor 1.10.1 and Node 12.x.
Now all Wekan platforms use newest Meteor and Node 12.x LTS.
Thanks to kentonv and xet7.
- [Fix capnp workaround to work with newest Meteor and
Node 12.x](https://github.com/wekan/wekan/commit/b2d546579c4957352c29b36c0c8a4a08b944dbb4).
Thanks to kentonv.
- [Update Sandstorm release script for newest Meteor and
Node 12.x](https://github.com/wekan/wekan/commit/c5f782976b971fa3f2323e80a013bbf6a49c0596).
Thanks to xet7.
- [Remove Meteor 1.8.x files because Sandstorm Wekan now uses newest
Meteor](https://github.com/wekan/wekan/commit/1a836969e10215bad47ac56a9b0d9de801b66fd2).
Thanks to xet7.
and adds the following new features:
- [Hide password auth with environment variable PASSWORD_LOGIN_ENABLED=false](https://github.com/wekan/wekan/pull/3014).
Snap example: `sudo snap set wekan password-login-enabled='false'` .
Thanks to salleman33.
and fixes the following bugs:
- [Fix Board admins can not clone or archive their boards at All Boards
page](https://github.com/wekan/wekan/pull/3013).
Thanks to salleman33.
- [Fix `<p>` margin in card labels](https://github.com/wekan/wekan/pull/3015).
Thanks to boeserwolf.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.95 2020-04-12 Wekan release
This release adds the following new features:
@@ -10,8 +346,6 @@ This release adds the following new features:
Thanks to juniormendonca.
- [Public boards overview](https://github.com/wekan/wekan/pull/3008).
Thanks to NicoP-S.
- Try to upgrade Sandstorm Wekan to Meteor 1.10.1 and Node 12.x.
Thanks to kentonv and xet7.
and fixes the following bugs:
+8 -3
View File
@@ -4,10 +4,12 @@ LABEL maintainer="wekan"
# Set the environment variables (defaults where required)
# DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
# ENV BUILD_DEPS="paxctl"
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.2 \
METEOR_RELEASE=1.10-rc.2 \
NODE_VERSION=v12.16.3 \
METEOR_RELEASE=1.10.2 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
NPM_VERSION=latest \
@@ -113,7 +115,8 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-
CORS_EXPOSE_HEADERS="" \
DEFAULT_AUTHENTICATION_METHOD="" \
SCROLLINERTIA="0" \
SCROLLAMOUNT="auto"
SCROLLAMOUNT="auto" \
PASSWORD_LOGIN_ENABLED=true
# Copy the app to the image
COPY ${SRC_PATH} /home/wekan/app
@@ -270,6 +273,8 @@ RUN \
cd /home/wekan/app_build/bundle/programs/server/ && \
gosu wekan:wekan npm install && \
#gosu wekan:wekan npm install bcrypt && \
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy && \
mv /home/wekan/app_build/bundle /build && \
\
# Put back the original tar
+77
View File
@@ -0,0 +1,77 @@
FROM amd64/alpine:3.7 AS builder
# Set the environment variables for builder
ENV QEMU_VERSION=v4.2.0-6 \
QEMU_ARCHITECTURE=aarch64 \
NODE_ARCHITECTURE=linux-arm64 \
NODE_VERSION=v12.16.3 \
WEKAN_VERSION=3.96 \
WEKAN_ARCHITECTURE=arm64
# Install dependencies
RUN apk update && apk add ca-certificates outils-sha1 && \
\
# Download qemu static for our architecture
wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \
\
# Download wekan and shasum
wget https://releases.wekan.team/raspi3/wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
wget https://releases.wekan.team/raspi3/SHA256SUMS.txt && \
# Verify wekan
grep wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip SHA256SUMS.txt | sha256sum -c - && \
\
# Unzip wekan
unzip wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
\
# Download node and shasums
wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
\
# Verify nodejs authenticity
grep node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz SHASUMS256.txt.asc | sha256sum -c - && \
\
# Extract node and remove tar.gz
tar xvzf node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz
# Build wekan dockerfile
FROM arm64v8/ubuntu:19.10
LABEL maintainer="wekan"
# Set the environment variables (defaults where required)
ENV QEMU_ARCHITECTURE=aarch64 \
NODE_ARCHITECTURE=linux-arm64 \
NODE_VERSION=v12.16.1 \
NODE_ENV=production \
NPM_VERSION=latest \
WITH_API=true \
PORT=8080 \
ROOT_URL=http://localhost \
MONGO_URL=mongodb://127.0.0.1:27017/wekan
# Copy qemu-static to image
COPY --from=builder qemu-${QEMU_ARCHITECTURE}-static /usr/bin
# Copy the app to the image
COPY --from=builder bundle /home/wekan/bundle
# Copy
COPY --from=builder node-${NODE_VERSION}-${NODE_ARCHITECTURE} /opt/nodejs
RUN \
set -o xtrace && \
# Add non-root user wekan
useradd --user-group --system --home-dir /home/wekan wekan && \
\
# Install Node
ln -s /opt/nodejs/bin/node /usr/bin/node && \
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \
chown wekan --recursive /home/wekan/.config && \
\
# Install Node dependencies
npm install -g npm@${NPM_VERSION}
EXPOSE $PORT
USER wekan
CMD ["node", "/home/wekan/bundle/main.js"]
+1 -1
View File
@@ -34,7 +34,7 @@ and PWA app that can be added as icon on Android and bookmark on iOS, used like
**NOTE**:
- Please read the [FAQ](https://github.com/wekan/wekan/wiki/FAQ) first
- Please don't feed the trolls and spammers that are mentioned in the FAQ :)
- Please don't feed the [trolls](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-troll) and [spammers](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-spammer) that are mentioned in the FAQ :)
## About Wekan
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v3.95.0"
appVersion: "v4.09.0"
files:
userUploads:
- README.md
+6
View File
@@ -0,0 +1,6 @@
// PWA
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/pwa-service-worker.js');
});
}
+1 -1
View File
@@ -7,7 +7,7 @@ BlazeComponent.extendComponent({
return Boards.find(
{ archived: true },
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
},
+3 -7
View File
@@ -1,7 +1,7 @@
import { Cookies } from 'meteor/ostrio:cookies';
const cookies = new Cookies();
const subManager = new SubsManager();
const { calculateIndex, enableClickOnTouch } = Utils;
const { calculateIndex } = Utils;
const swimlaneWhileSortingHeight = 150;
BlazeComponent.extendComponent({
@@ -191,9 +191,6 @@ BlazeComponent.extendComponent({
},
});
// ugly touch event hotfix
enableClickOnTouch('.js-swimlane:not(.placeholder)');
this.autorun(() => {
let showDesktopDragHandles = false;
currentUser = Meteor.user();
@@ -205,7 +202,7 @@ BlazeComponent.extendComponent({
} else {
showDesktopDragHandles = false;
}
if (!Utils.isMiniScreen() && showDesktopDragHandles) {
if (Utils.isMiniScreen() || showDesktopDragHandles) {
$swimlanesDom.sortable({
handle: '.js-swimlane-header-handle',
});
@@ -215,9 +212,8 @@ BlazeComponent.extendComponent({
});
}
// Disable drag-dropping if the current user is not a board member or is miniscreen
// Disable drag-dropping if the current user is not a board member
$swimlanesDom.sortable('option', 'disabled', !userIsMember());
$swimlanesDom.sortable('option', 'disabled', Utils.isMiniScreen());
});
function userIsMember() {
+446
View File
@@ -293,3 +293,449 @@ setBoardColor(color)
//.header-quick-access
// backgroud-color: #568ba2
/*
Alternate "Clear" Styling
*/
setBoardClear(color1,color2)
//color1: The quick access color
//color2: The main bar color
&.sk-spinner div,
.board-backgrounds-list &.background-box,
.board-list & a
background: linear-gradient(180deg, color1 0%, color2 100%)
//background: linear-gradient(180deg, rgb(73, 155, 234) 0%, rgb(0, 174, 204) 100%)
.is-selected .minicard
border-left: 3px solid color1
&.pop-over .pop-over-list li a:not(.disabled):hover,
.sidebar .sidebar-content .sidebar-btn:hover,
.sidebar-list li a:hover
background-color: lighten(color1, 10%)
&#header ul li.current, &#header-quick-access ul li.current
border-bottom: 4px solid lighten(color2, 10%)
&#header-quick-access
background: darken(color1, 10%)
//background: rgba(66,137,204,1)
color: #FFF
&#header-quick-access #header-new-board-icon,
&#header-quick-access #header-user-bar,
&#header-quick-access ul li
color: rgba(255,255,255,0.5)
// The background-color value here is not seen,
// its covered by the background of #header-main-bar
// it's just to aid transitions between boards
&#header
background-color: color2
border-bottom: 1px solid darken(color2, 20%)
border-top: 1px solid darken(color2, 40%)
// Since the theme uses a gradient for the header
// and gradients break transitions, it has to be set here
&#header #header-main-bar
background: linear-gradient(180deg, color1 0%, color2 100%)
&#header #header-main-bar p
margin-bottom: 6px
&#header #header-main-bar .board-header-btn.emphasis
background: lighten(color2, 10%)
&:hover,
.board-header-btn-close
background: rgba(0,0,0,0.2)
&:hover .board-header-btn-close
background: rgba(0,0,0,0.2)
.materialCheckBox.is-checked
border-bottom: 2px solid color1
border-right: 2px solid color1
.is-multiselection-active .multi-selection-checkbox
&.is-checked + .minicard
background: lighten(color2, 90%)
&:not(.is-checked) + .minicard:hover:not(.minicard-composer)
background: lighten(color2, 97%)
.toggle-switch:checked ~ .toggle-label
background-color: lighten(color1, 20%)
&:after
background-color: darken(color1, 20%)
.board-canvas
background: linear-gradient(135deg, color1 0%, color2 100%)
.swimlane
background: none
.list:first-child
margin-left: 15px
.list
background: rgba(255,255,255,0.35)
margin: 10px
border: 0
border-radius: 14px
.list.list-composer
background: rgba(255,255,255,0.1)
height: min-content
flex: unset
width: 270px
padding-bottom: 16px
.list.list-composer .open-list-composer
border-radius: 7px
color: rgba(0,0,0,0.3)
padding: 7px 10px
display: block
.list.list-composer .open-list-composer:hover
box-shadow: 0 1px 2px rgba(0,0,0,.2)
background: rgba(255,255,255,0.7)
color: rgba(0,0,0,0.6)
.list-header
background-color: rgba(255,255,255,0.25)
border-radius: 14px 14px 0 0
.list-header:not([class*="list-header-"])
border-bottom: 6px solid rgba(255,255,255,0)
.list-header .list-header-name
color: rgba(0,0,0,0.6)
.list-body
padding: 11px
.minicard
border-radius: 7px
padding: 10px 10px 4px 10px
box-shadow: 2px 2px 4px 0px rgba(0,0,0,0.15)
color: #222
.card-details
border-radius: 0 0 14px 14px
box-shadow: 0 0 7px 0 rgba(0,0,0,0.5)
margin-left: -10px
.list-body .open-minicard-composer
border-radius: 7px
color: rgba(0,0,0,.3)
margin-bottom: 11px
.list-body .open-minicard-composer:hover
background: rgba(255,255,255,0.7)
color: rgba(0,0,0,0.6)
button[type=submit].primary, input[type=submit].primary
box-shadow: none
background-color: rgba(255,255,255,0.5)
color: rgba(0,0,0,0.55)
border-radius: 7px
border: 0
button[type="submit"].primary:hover, input[type="submit"].primary:hover
background-color: rgba(255,255,255,0.7)
color: rgba(0,0,0,0.8)
box-shadow: 0 1px 2px rgba(0,0,0,.2)
.quiet, .quiet a
color: rgba(0,0,0,0.4)
.list-header .list-header-watch-icon
color: rgba(0,0,0,0.5)
position: absolute
margin-top: -34px
margin-let: -11px
a.fa, a i.fa
color: rgba(0,0,0,0.3)
a:not(.disabled).is-active.fa, a:not(.disabled).is-active i.fa, a:not(.disabled):hover.fa, a:not(.disabled):hover i.fa
color: rgba(0,0,0,0.6)
input[type="email"], input[type="password"], input[type="text"]
border: 0
border-radius: 7px
.sidebar-shadow
box-shadow: none
border-left: 9px solid color2
.is-open .sidebar-shadow
box-shadow: -10px 0 8px rgba(0,0,0,0.3)
.list.ui-sortable-helper
transform:rotate(0deg)
.minicard-wrapper.placeholder
background: rgba(0,0,0,0.1)
.minicard-wrapper.ui-sortable-helper
transform:rotate(0deg)
opacity: 0.8
.list-body .open-minicard-composer
color: rgba(0,0,0,.3)
.swinlane.ui-sortable-helper
transform:rotate(0deg)
.swimlane .swimlane-header-wrap
background: linear-gradient(0deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.25) 100%)
.swimlane-header-wrap .inlined-form
width: 100%
.swimlane-header-wrap .list-composer
text-align: center
margin: 5px
.swimlane-header-wrap .list-name-input.full-line
margin: 0
display: inline-block
width: 270px
.swimlane-header-wrap .edit-controls
display: inline-block
vertical-align: middle
.swimlane-header-wrap .primary.confirm
margin-right: 0
.swimlane-header-wrap .fa.fa-times-thin
margin-top: 2px
// This is a general fix so that the little grabby hand appears when dragging the list via the title
.list.ui-sortable-helper,
.list.ui-sortable-helper .list-header.ui-sortable-handle,
.list.ui-sortable-helper .viewer
cursor:-webkit-grabbing;
cursor:grabbing
.board-color-clearblue
setBoardClear(rgb(73, 155, 234),rgb(0, 174, 204))
/*
Alternate "Natural" Styling
*/
.board-color-natural
setBoardColor(#596557)
&#header-quick-access
background-color: #2d392b
.ui-sortable
background-color:#dedede
.list-header
background-color: #c9cfc3
border-bottom: 6px solid #c9cfc3
.swimlane .swimlane-header-wrap
background-color: #c2c0ab
/*
Alternate "Modern" Styling
*/
.board-color-modern
setBoardColor(#2A80B8)
/* General */
body
background: #f5f5f5
&#header-quick-access
padding: 10px
font-size: 14px
background: #333 !important
&#header-quick-access ul
overflow: visible
&#header-quick-access ul li.current
border: 0 !important
font-weight: bold
&#header-quick-access ul li.separator
display: none
&#header-quick-access ul li:nth-child(3)
margin-right: 10px
&#header-quick-access ul li a
padding: 5px 10px
border-radius: 2px
&#header-quick-access ul li.current a
border-radius: 2px
background: rgba(255,255,255,.2)
&#header #header-main-bar h1
font-family: Poppins
font-weight: bold
&#header-quick-access #header-user-bar
position relative
&#header-quick-access #header-user-bar .header-user-bar-name
margin: 5px 3px 0 0;
section#notifications-drawer
top: 46px;
box-shadow: 0 4px 20px rgba(0,0,0,.1)
max-width: 100%
section#notifications-drawer .header
top: 46px;
border-radius: 0 3px
height: 21px
background: #f7f7f7
/* Swimlane */
.swimlane
background: #f5f5f5
.swimlane .swimlane-header-wrap .swimlane-header
font-family: Poppins
/* All board views */
.board-list .board-list-item
padding: 20px
.board-list-item-name
font-family: Poppins
/* Board */
.list
background: transparent
border-left: 0
margin: 10px 0
padding: 0px
border-radius: 5px
min-width: 300px
.list-body .open-minicard-composer:hover /*me*/
background: none
box-shadow: none
.list:first-child
margin-left: 5px
.list.list-composer.js-list-composer
transition: all .3s ease
min-width: 80px
.open-list-composer.js-open-inlined-form:hover
color: #222
.list-header
background: none
border-bottom-width: 0px
.list-header .list-header-name
font-family: Poppins
color: #000
font-weight: 500
/* Card changes */
.minicard
background: #FFF
padding: 15px 15px 10px
box-shadow: 0 3px 8px rgba(0,0,0,.05)
.minicard-plum:hover:not(.minicard-composer), .is-selected .minicard-plum, .draggable-hover-card .minicard-plum
background: none
.minicard-title
line-height: 1.5em
.minicard .minicard-cover
background-size: cover
margin: -15px -15px 10px
height: 100px
.card-label-orange
color: #fff
.card-date
font-size: 12px
padding: 3px 5px
/* Pop over */
.header-title
font-family: Poppins
font-size: 16px
color: #333
.pop-over
box-shadow: 0 4px 20px rgba(0,0,0,.2)
border: 0
border-radius: 5px
.pop-over .header
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
width: 260px
.pop-over-list li > a
border-radius: 5px
.pop-over-list li > a > i
margin-right: 5px
.pop-over-list li>a .sub-name
margin-bottom: 8px
/* Sidebar */
.sidebar .sidebar-shadow
box-shadow: 0 0 60px rgba(0,0,0,.2)
.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
+7 -3
View File
@@ -1,10 +1,10 @@
template(name="boardList")
.wrapper
ul.board-list.clearfix
ul.board-list.clearfix.js-boards
li.js-add-board
a.board-list-item.label {{_ 'add-board'}}
each boards
li(class="{{#if isStarred}}starred{{/if}}" class=colorClass)
li(class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board
if isInvited
.board-list-item
span.details
@@ -31,6 +31,10 @@ template(name="boardList")
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
if isMiniScreen
i.fa.board-handle(
class="fa-arrows"
title="{{_ 'Drag board'}}")
unless isMiniScreen
if isSandstorm
i.fa.js-clone-board(
@@ -39,7 +43,7 @@ template(name="boardList")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else if currentUser.isBoardAdmin
else if isAdministrable
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
+73 -12
View File
@@ -1,4 +1,5 @@
const subManager = new SubsManager();
const { calculateIndex, enableClickOnTouch } = Utils;
Template.boardListHeaderBar.events({
'click .js-open-archived-board'() {
@@ -7,8 +8,8 @@ Template.boardListHeaderBar.events({
});
Template.boardListHeaderBar.helpers({
title(){
return FlowRouter.getRouteName() == 'home' ? 'my-boards' :'public';
title() {
return FlowRouter.getRouteName() === 'home' ? 'my-boards' : 'public';
},
templatesBoardId() {
return Meteor.user() && Meteor.user().getTemplatesBoardId();
@@ -23,25 +24,85 @@ BlazeComponent.extendComponent({
Meteor.subscribe('setting');
},
onRendered() {
function userIsAllowedToMove() {
return Meteor.user();
}
const itemsSelector = '.js-board:not(.placeholder)';
const $boards = this.$('.js-boards');
$boards.sortable({
connectWith: '.js-boards',
tolerance: 'pointer',
appendTo: '.board-list',
helper: 'clone',
distance: 7,
items: itemsSelector,
placeholder: 'board-wrapper placeholder',
start(evt, ui) {
ui.helper.css('z-index', 1000);
ui.placeholder.height(ui.helper.height());
EscapeActions.executeUpTo('popup-close');
},
stop(evt, ui) {
// To attribute the new index number, we need to get the DOM element
// of the previous and the following card -- if any.
const prevBoardDom = ui.item.prev('.js-board').get(0);
const nextBoardBom = ui.item.next('.js-board').get(0);
const sortIndex = calculateIndex(prevBoardDom, nextBoardBom, 1);
const boardDomElement = ui.item.get(0);
const board = Blaze.getData(boardDomElement);
// Normally the jquery-ui sortable library moves the dragged DOM element
// to its new position, which disrupts Blaze reactive updates mechanism
// (especially when we move the last card of a list, or when multiple
// users move some cards at the same time). To prevent these UX glitches
// we ask sortable to gracefully cancel the move, and to put back the
// DOM in its initial state. The card move is then handled reactively by
// Blaze with the below query.
$boards.sortable('cancel');
board.move(sortIndex.base);
},
});
// ugly touch event hotfix
enableClickOnTouch(itemsSelector);
// Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => {
if (Utils.isMiniScreen()) {
$boards.sortable({
handle: '.board-handle',
});
}
$boards.sortable('option', 'disabled', !userIsAllowedToMove());
});
},
boards() {
let query = {
const query = {
archived: false,
type: 'board',
}
if (FlowRouter.getRouteName() == 'home')
query['members.userId'] = Meteor.userId()
else
query.permission = 'public'
};
if (FlowRouter.getRouteName() === 'home')
query['members.userId'] = Meteor.userId();
else query.permission = 'public';
return Boards.find(
query,
{ sort: ['title'] },
);
return Boards.find(query, {
sort: { sort: 1 /* boards default sorting */ },
});
},
isStarred() {
const user = Meteor.user();
return user && user.hasStarred(this.currentData()._id);
},
isAdministrable() {
const user = Meteor.user();
return user && user.isBoardAdmin(this.currentData()._id);
},
hasOvertimeCards() {
subManager.subscribe('board', this.currentData()._id, false);
+33 -3
View File
@@ -11,6 +11,19 @@ $spaceBetweenTiles = 16px
box-sizing: border-box
position: relative
&.placeholder:after
content: '';
display: block;
background: darken(white, 20%)
border-radius: 3px;
height: 106px;
margin: 8px;
&.ui-sortable-helper
cursor: grabbing
transform: rotate(4deg)
display: block !important
&.starred
.fa-star,
.fa-star-o
@@ -20,7 +33,7 @@ $spaceBetweenTiles = 16px
overflow: hidden;
background-color: #999
color: #f6f6f6
height: 90px
height: auto
font-size: 16px
line-height: 22px
border-radius: 3px
@@ -31,6 +44,7 @@ $spaceBetweenTiles = 16px
margin: ($spaceBetweenTiles/2)
position: relative
text-decoration: none
word-wrap: break-word
&.tile
background-size: auto
@@ -55,7 +69,7 @@ $spaceBetweenTiles = 16px
.label
font-weight: normal
line-height:90px
line-height: 56px
:hover
background-color:#939393
@@ -183,7 +197,7 @@ $spaceBetweenTiles = 16px
overflow: scroll
li
width: 50%
width: 50%
.board-list-item
overflow: hidden
@@ -194,6 +208,22 @@ $spaceBetweenTiles = 16px
top: -100px
left: -100px
.board-handle
position: absolute
padding: 7px
top: 50%
transform: translateY(-50%)
right: 10px
font-size: 24px
@media screen and (max-width: 360px)
li
width: 100%
.board-handle
position: absolute
padding: 7px
top: 50%
transform: translateY(-50%)
right: 10px
font-size: 24px
@@ -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() {
+27
View File
@@ -386,3 +386,30 @@ CardEndDate.register('cardEndDate');
return this.date.get().format('l');
}
}.register('minicardEndDate'));
class VoteEndDate extends CardDate {
onCreated() {
super.onCreated();
const self = this;
self.autorun(() => {
self.date.set(moment(self.data().getVoteEnd()));
});
}
classes() {
const classes = 'end-date' + ' ';
return classes;
}
showDate() {
return this.date.get().format('l LT');
}
showTitle() {
return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`;
}
events() {
return super.events().concat({
'click .js-edit-date': Popup.open('editVoteEndDate'),
});
}
}
VoteEndDate.register('voteEndDate');
+51 -32
View File
@@ -32,7 +32,7 @@ template(name="cardDetails")
// else
{{_ 'top-level-card'}}
if isLinkedCard
h3.linked-card-location
a.linked-card-location.js-go-to-linked-card
+viewer
| {{getBoardTitle}} > {{getTitle}}
@@ -202,9 +202,12 @@ template(name="cardDetails")
if getVoteQuestion
hr
.vote-title
h3
i.fa.fa-thumbs-up
card-details-item-title {{_ 'vote-question'}}
div.flex
h3
i.fa.fa-thumbs-up
| {{_ 'vote-question'}}
if getVoteEnd
+voteEndDate
.vote-result
if votePublic
a.card-label.card-label-green.js-show-positive-votes {{ voteCountPositive }}
@@ -212,10 +215,13 @@ template(name="cardDetails")
else
.card-label.card-label-green {{ voteCountPositive }}
.card-label.card-label-red {{ voteCountNegative }}
unless ($and currentBoard.isPublic voteAllowNonBoardMembers )
.card-label.card-label-gray {{ voteCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }}
+viewer
= getVoteQuestion
button.card-details-green.js-vote.js-vote-positive(class="{{#if voteState}}voted{{/if}}") {{_ 'vote-for-it'}}
button.card-details-red.js-vote.js-vote-negative(class="{{#if $eq voteState false}}voted{{/if}}") {{_ 'vote-against'}}
if showVotingButtons
button.card-details-green.js-vote.js-vote-positive(class="{{#if voteState}}voted{{/if}}") {{_ 'vote-for-it'}}
button.card-details-red.js-vote.js-vote-negative(class="{{#if $eq voteState false}}voted{{/if}}") {{_ 'vote-against'}}
//- XXX We should use "editable" to avoid repetiting ourselves
if canModifyCard
@@ -333,16 +339,10 @@ template(name="cardDetailsActionsPopup")
//li: a.js-members {{_ 'card-edit-members'}}
//li: a.js-labels {{_ 'card-edit-labels'}}
//li: a.js-attachments {{_ 'card-edit-attachments'}}
if getVoteQuestion
li
a.js-cancel-voting
i.fa.fa-thumbs-up
| {{_ 'card-cancel-voting'}}
else
li
a.js-start-voting
i.fa.fa-thumbs-up
| {{_ 'card-start-voting'}}
li
a.js-start-voting
i.fa.fa-thumbs-up
| {{_ 'card-edit-voting'}}
li
a.js-custom-fields
i.fa.fa-list-alt
@@ -465,14 +465,14 @@ template(name="cardAssigneesPopup")
i.fa.fa-check
if currentUser.isWorker
ul.pop-over-list.js-card-assignee-list
li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}")
a.name.js-select-assignee(href="#")
+userAvatar(userId=currentUser._id)
span.full-name
= currentUser.profile.fullname
| (<span class="username">{{ currentUser.username }}</span>)
if currentUser.isCardAssignee
i.fa.fa-check
li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}")
a.name.js-select-assignee(href="#")
+userAvatar(userId=currentUser._id)
span.full-name
= currentUser.profile.fullname
| (<span class="username">{{ currentUser.username }}</span>)
if currentUser.isCardAssignee
i.fa.fa-check
template(name="userAvatarAssignee")
a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
@@ -519,7 +519,7 @@ template(name="cardMorePopup")
= ' '
i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
input.inline-input(type="text" id="cardURL" readonly value="{{ absoluteUrl }}" autofocus="autofocus")
button.js-copy-card-link-to-clipboard(class="btn") {{_ 'copy-card-link-to-clipboard'}}
button.js-copy-card-link-to-clipboard(class="btn" id="clipboard") {{_ 'copy-card-link-to-clipboard'}}
span.clearfix
br
h2 {{_ 'change-card-parent'}}
@@ -564,20 +564,39 @@ template(name="setCardColorPopup")
template(name="cardDeletePopup")
p {{_ "card-delete-pop"}}
unless archived
p {{_ "card-delete-suggest-archive"}}
p {{_ "card-delete-suggest-archive"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="deleteVotePopup")
p {{_ "vote-delete-pop"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="cardStartVotingPopup")
form.edit-vote-question
.fields
label(for="vote") {{_ 'vote-question'}}
input.js-vote-field#vote(type="text" name="vote" value="{{card.getVoteQuestion}}" autofocus)
label(for="vote-public") {{_ 'vote-public'}}
a.js-toggle-vote-public
.materialCheckBox#vote-public(name="vote-public")
input.js-vote-field#vote(type="text" name="vote" value="{{getVoteQuestion}}" autofocus disabled="{{#if getVoteQuestion}}disabled{{/if}}")
.check-div
a.flex(class="{{#if getVoteQuestion}}is-disabled{{else}}js-toggle-vote-allow-non-members{{/if}}")
.materialCheckBox#vote-allow-non-members(name="vote-allow-non-members" class="{{#if voteAllowNonBoardMembers}}is-checked{{/if}}")
span {{_ 'allowNonBoardMembers'}}
.check-div
a.flex(class="{{#if getVoteQuestion}}is-disabled{{else}}js-toggle-vote-public{{/if}}")
.materialCheckBox#vote-public(name="vote-public" class="{{#if votePublic}}is-checked{{/if}}")
span {{_ 'vote-public'}}
.check-div.flex
i.fa.fa-hourglass-end
a.js-end-date
span
| {{_ 'card-end'}}
unless getVoteEnd
i.fa.fa-plus
if getVoteEnd
+voteEndDate
button.primary.confirm.js-submit {{_ 'save'}}
//- button.js-remove-color.negate.wide.right {{_ 'delete'}}
button.primary.js-submit {{_ 'save'}}
if getVoteQuestion
button.js-remove-vote.negate.wide.right {{_ 'delete'}}
template(name="positiveVoteMembersPopup")
ul.pop-over-list.js-card-member-list
+129 -52
View File
@@ -1,5 +1,5 @@
const subManager = new SubsManager();
const { calculateIndexData, enableClickOnTouch } = Utils;
const { calculateIndexData } = Utils;
let cardColors;
Meteor.startup(() => {
@@ -54,22 +54,6 @@ BlazeComponent.extendComponent({
}
return null;
},
votePublic() {
const card = this.currentData();
if (card.vote)
return card.vote.public
return null
},
voteCountPositive() {
const card = this.currentData();
if (card.vote && card.vote.positive) return card.vote.positive.length;
return null;
},
voteCountNegative() {
const card = this.currentData();
if (card.vote && card.vote.negative) return card.vote.negative.length;
return null;
},
isWatching() {
const card = this.currentData();
return card.findWatcher(Meteor.userId());
@@ -90,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();
@@ -149,6 +140,15 @@ BlazeComponent.extendComponent({
return result;
},
showVotingButtons() {
const card = this.currentData();
return (
(currentUser.isBoardMember() ||
(currentUser && card.voteAllowNonBoardMembers())) &&
!card.expiredVote()
);
},
onRendered() {
if (Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED) {
// Send Webhook but not create Activities records ---
@@ -232,9 +232,6 @@ BlazeComponent.extendComponent({
},
});
// ugly touch event hotfix
enableClickOnTouch('.card-checklist-items .js-checklist');
const $subtasksDom = this.$('.card-subtasks-items');
$subtasksDom.sortable({
@@ -270,26 +267,21 @@ BlazeComponent.extendComponent({
},
});
// ugly touch event hotfix
enableClickOnTouch('.card-subtasks-items .js-subtasks');
function userIsMember() {
return Meteor.user() && Meteor.user().isBoardMember();
}
// Disable sorting if the current user is not a board member
this.autorun(() => {
if ($checklistsDom.data('sortable')) {
$checklistsDom.sortable('option', 'disabled', !userIsMember());
const disabled = !userIsMember() || Utils.isMiniScreen();
if (
$checklistsDom.data('uiSortable') ||
$checklistsDom.data('sortable')
) {
$checklistsDom.sortable('option', 'disabled', disabled);
}
if ($subtasksDom.data('sortable')) {
$subtasksDom.sortable('option', 'disabled', !userIsMember());
}
if ($checklistsDom.data('sortable')) {
$checklistsDom.sortable('option', 'disabled', Utils.isMiniScreen());
}
if ($subtasksDom.data('sortable')) {
$subtasksDom.sortable('option', 'disabled', Utils.isMiniScreen());
if ($subtasksDom.data('uiSortable') || $subtasksDom.data('sortable')) {
$subtasksDom.sortable('option', 'disabled', disabled);
}
});
},
@@ -379,6 +371,9 @@ BlazeComponent.extendComponent({
this.data().setRequestedBy('');
}
},
'click .js-go-to-linked-card'() {
Utils.goCardId(this.data().linkedId);
},
'click .js-member': Popup.open('cardMember'),
'click .js-add-members': Popup.open('cardMembers'),
'click .js-assignee': Popup.open('cardAssignee'),
@@ -388,7 +383,7 @@ BlazeComponent.extendComponent({
'click .js-start-date': Popup.open('editCardStartDate'),
'click .js-due-date': Popup.open('editCardDueDate'),
'click .js-end-date': Popup.open('editCardEndDate'),
'click .js-show-positive-votes':Popup.open('positiveVoteMembers'),
'click .js-show-positive-votes': Popup.open('positiveVoteMembers'),
'click .js-show-negative-votes': Popup.open('negativeVoteMembers'),
'mouseenter .js-card-details'() {
const parentComponent = this.parentComponent().parentComponent();
@@ -417,9 +412,9 @@ BlazeComponent.extendComponent({
const forIt = $(e.target).hasClass('js-vote-positive');
let newState = null;
if (
this.voteState() == null ||
(this.voteState() == false && forIt) ||
(this.voteState() == true && !forIt)
this.voteState() === null ||
(this.voteState() === false && forIt) ||
(this.voteState() === true && !forIt)
) {
newState = forIt;
}
@@ -617,11 +612,6 @@ Template.cardDetailsActionsPopup.events({
'click .js-copy-card': Popup.open('copyCard'),
'click .js-copy-checklist-cards': Popup.open('copyChecklistToManyCards'),
'click .js-set-card-color': Popup.open('setCardColor'),
'click .js-cancel-voting'(event) {
event.preventDefault();
this.unsetVote();
Popup.close();
},
'click .js-move-card-to-top'(event) {
event.preventDefault();
const minOrder = _.min(
@@ -655,7 +645,7 @@ Template.cardDetailsActionsPopup.events({
},
});
Template.editCardTitleForm.onRendered(function () {
Template.editCardTitleForm.onRendered(function() {
autosize(this.$('.js-edit-card-title'));
});
@@ -669,7 +659,7 @@ Template.editCardTitleForm.events({
},
});
Template.editCardRequesterForm.onRendered(function () {
Template.editCardRequesterForm.onRendered(function() {
autosize(this.$('.js-edit-card-requester'));
});
@@ -682,7 +672,7 @@ Template.editCardRequesterForm.events({
},
});
Template.editCardAssignerForm.onRendered(function () {
Template.editCardAssignerForm.onRendered(function() {
autosize(this.$('.js-edit-card-assigner'));
});
@@ -724,7 +714,7 @@ BlazeComponent.extendComponent({
_id: { $ne: Meteor.user().getTemplatesBoardId() },
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -822,7 +812,7 @@ Template.copyChecklistToManyCardsPopup.events({
// copy subtasks
cursor = Cards.find({ parentId: oldId });
cursor.forEach(function () {
cursor.forEach(function() {
'use strict';
const subtask = arguments[0];
subtask.parentId = _id;
@@ -900,7 +890,7 @@ BlazeComponent.extendComponent({
},
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -971,9 +961,25 @@ BlazeComponent.extendComponent({
}
}
},
'click .js-delete': Popup.afterConfirm('cardDelete', function () {
'click .js-delete': Popup.afterConfirm('cardDelete', function() {
Popup.close();
Cards.remove(this._id);
// verify that there are no linked cards
if (Cards.find({ linkedId: this._id }).count() === 0) {
Cards.remove(this._id);
} else {
// TODO: Maybe later we can list where the linked cards are.
// Now here is popup with a hint that the card cannot be deleted
// as there are linked cards.
// Related:
// client/components/lists/listHeader.js about line 248
// https://github.com/wekan/wekan/issues/2785
const message = `${TAPi18n.__(
'delete-linked-card-before-this-card',
)} linkedId: ${
this._id
} at client/components/cards/cardDetails.js and https://github.com/wekan/wekan/issues/2785`;
alert(message);
}
Utils.goBoardId(this.boardId);
}),
'change .js-field-parent-board'(event) {
@@ -1006,22 +1012,93 @@ BlazeComponent.extendComponent({
events() {
return [
{
'click .js-end-date': Popup.open('editVoteEndDate'),
'submit .edit-vote-question'(evt) {
evt.preventDefault();
const voteQuestion = evt.target.vote.value;
const publicVote = $('#vote-public').hasClass('is-checked');
this.currentCard.setVoteQuestion(voteQuestion, publicVote);
const allowNonBoardMembers = $('#vote-allow-non-members').hasClass(
'is-checked',
);
const endString = this.currentCard.getVoteEnd();
this.currentCard.setVoteQuestion(
voteQuestion,
publicVote,
allowNonBoardMembers,
);
if (endString) {
this.currentCard.setVoteEnd(endString);
}
Popup.close();
},
'click .js-remove-vote': Popup.afterConfirm('deleteVote', () => {
event.preventDefault();
this.currentCard.unsetVote();
Popup.close();
}),
'click a.js-toggle-vote-public'(event) {
event.preventDefault();
$('#vote-public').toggleClass('is-checked');
},
'click a.js-toggle-vote-allow-non-members'(event) {
event.preventDefault();
$('#vote-allow-non-members').toggleClass('is-checked');
},
},
];
},
}).register('cardStartVotingPopup');
// editVoteEndDatePopup
(class extends DatePicker {
onCreated() {
super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
this.data().getVoteEnd() && this.date.set(moment(this.data().getVoteEnd()));
}
events() {
return [
{
'submit .edit-date'(evt) {
evt.preventDefault();
// if no time was given, init with 12:00
const time =
evt.target.time.value ||
moment(new Date().setHours(12, 0, 0)).format('LT');
const dateString = `${evt.target.date.value} ${time}`;
const newDate = moment(dateString, 'L LT', true);
if (newDate.isValid()) {
// if active vote - store it
if (this.currentData().getVoteQuestion()) {
this._storeDate(newDate.toDate());
Popup.close();
} else {
this.currentData().vote = { end: newDate.toDate() }; // set vote end temp
Popup.back();
}
} else {
this.error.set('invalid-date');
evt.target.date.focus();
}
},
'click .js-delete-date'(evt) {
evt.preventDefault();
this._deleteDate();
Popup.close();
},
},
];
}
_storeDate(newDate) {
this.card.setVoteEnd(newDate);
}
_deleteDate() {
this.card.unsetVoteEnd();
}
}.register('editVoteEndDatePopup'));
// Close the card details pane by pressing escape
EscapeActions.register(
'detailsPane',
+11
View File
@@ -10,6 +10,9 @@ avatar-radius = 50%
left: -2000px
top: 0px
#clipboard
white-space: normal
.assignee
border-radius: 3px
display: block
@@ -37,6 +40,8 @@ avatar-radius = 50%
position: absolute
&.avatar-image
object-fit: cover;
object-position: center;
height: 100%
width: @height
@@ -94,6 +99,7 @@ avatar-radius = 50%
animation: flexGrowIn 0.1s
box-shadow: 0 0 7px 0 darken(white, 30%)
transition: flex-basis 0.1s
box-sizing: border-box
.mCustomScrollBox
padding-left: 0
@@ -336,6 +342,11 @@ card-details-color(background, color...)
.vote-title
display: flex
justify-content: space-between
.js-edit-date
align-self: baseline
margin-left: 5px
.vote-result
display: flex
.js-show-positive-votes
+2 -1
View File
@@ -88,7 +88,8 @@ template(name="checklistItems")
template(name='checklistItemDetail')
.js-checklist-item.checklist-item
if canModifyCard
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
.check-box-container
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
+20 -11
View File
@@ -1,4 +1,4 @@
const { calculateIndexData, enableClickOnTouch } = Utils;
const { calculateIndexData, capitalize } = Utils;
function initSorting(items) {
items.sortable({
@@ -36,9 +36,6 @@ function initSorting(items) {
checklistItem.move(checklistId, sortIndex.base);
},
});
// ugly touch event hotfix
enableClickOnTouch('.js-checklist-item:not(.placeholder)');
}
BlazeComponent.extendComponent({
@@ -54,14 +51,15 @@ BlazeComponent.extendComponent({
return Meteor.user() && Meteor.user().isBoardMember();
}
// Disable sorting if the current user is not a board member
// Disable sorting if the current user is not a board member or is a miniscreen
self.autorun(() => {
const $itemsDom = $(self.itemsDom);
if ($itemsDom.data('sortable')) {
$(self.itemsDom).sortable('option', 'disabled', !userIsMember());
}
if ($itemsDom.data('sortable')) {
$(self.itemsDom).sortable('option', 'disabled', Utils.isMiniScreen());
if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
$(self.itemsDom).sortable(
'option',
'disabled',
!userIsMember() || Utils.isMiniScreen(),
);
}
});
},
@@ -177,6 +175,16 @@ BlazeComponent.extendComponent({
}
},
focusChecklistItem(event) {
// If a new checklist is created, pre-fill the title and select it.
const checklist = this.currentData().checklist;
if (!checklist) {
const textarea = event.target;
textarea.value = capitalize(TAPi18n.__('r-checklist'));
textarea.select();
}
},
events() {
const events = {
'click .toggle-delete-checklist-dialog'(event) {
@@ -196,6 +204,7 @@ BlazeComponent.extendComponent({
'submit .js-edit-checklist-item': this.editChecklistItem,
'click .js-delete-checklist-item': this.deleteItem,
'click .confirm-checklist-delete': this.deleteChecklist,
'focus .js-add-checklist-item': this.focusChecklistItem,
keydown: this.pressKey,
},
];
@@ -250,7 +259,7 @@ BlazeComponent.extendComponent({
events() {
return [
{
'click .js-checklist-item .check-box': this.toggleItem,
'click .js-checklist-item .check-box-container': this.toggleItem,
},
];
},
+4 -1
View File
@@ -113,6 +113,9 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
&:hover
background-color: darken(white, 8%)
.check-box-container
padding-right: 1px;
.check-box
margin: 0.1em 0 0 0;
&.is-checked
@@ -121,7 +124,7 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
.item-title
flex: 1
padding-left: 10px;
margin-left: 10px;
&.is-checked
color: #8c8c8c
font-style: italic
+2
View File
@@ -158,6 +158,8 @@
.edit-labels-pop-over
margin-bottom: 8px
.card-label .viewer p
margin: 0
.edit-labels-pop-over .shortcut
display: inline-block
+10 -4
View File
@@ -4,8 +4,8 @@ template(name="minicard")
class="{{#if isLinkedBoard}}linked-board{{/if}}"
class="minicard-{{colorClass}}")
if isMiniScreen
//.handle
// .fa.fa-arrows
.handle
.fa.fa-arrows
unless isMiniScreen
if showDesktopDragHandles
.handle
@@ -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
@@ -103,7 +107,9 @@ template(name="minicard")
if getVoteQuestion
.badge.badge-state-image-only(title=getVoteQuestion)
span.badge-icon.fa.fa-thumbs-up
span.badge-text {{ voteCountPositive }}
span.badge-icon.fa.fa-thumbs-down
span.badge-text {{ voteCountNegative }}
if attachments.count
.badge
span.badge-icon.fa.fa-paperclip
+14
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 [
{
@@ -0,0 +1,37 @@
export function getMembersToMap(data) {
// we will work on the list itself (an ordered array of objects) when a
// mapping is done, we add a 'wekan' field to the object representing the
// imported member
const membersToMap = [];
const importedMembers = [];
let membersIndex;
for (let i = 0; i < data[0].length; i++) {
if (data[0][i].toLowerCase() === 'members') {
membersIndex = i;
}
}
for (let i = 1; i < data.length; i++) {
if (data[i][membersIndex]) {
for (const importedMember of data[i][membersIndex].split(' ')) {
if (importedMember && importedMembers.indexOf(importedMember) === -1) {
importedMembers.push(importedMember);
}
}
}
}
for (let importedMember of importedMembers) {
importedMember = {
username: importedMember,
id: importedMember,
};
const wekanUser = Users.findOne({ username: importedMember.username });
if (wekanUser) importedMember.wekanId = wekanUser._id;
membersToMap.push(importedMember);
}
return membersToMap;
}
+1 -4
View File
@@ -13,11 +13,8 @@ template(name="import")
template(name="importTextarea")
form
p: label(for='import-textarea') {{_ instruction}} {{_ 'import-board-instruction-about-errors'}}
textarea.js-import-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus)
textarea.js-import-json(placeholder="{{_ importPlaceHolder}}" autofocus)
| {{jsonText}}
if isSandstorm
h1.warning {{_ 'import-sandstorm-backup-warning'}}
p.warning {{_ 'import-sandstorm-warning'}}
input.primary.wide(type="submit" value="{{_ 'import'}}")
template(name="importMapMembers")
+40 -12
View File
@@ -1,5 +1,8 @@
import trelloMembersMapper from './trelloMembersMapper';
import wekanMembersMapper from './wekanMembersMapper';
import csvMembersMapper from './csvMembersMapper';
const Papa = require('papaparse');
BlazeComponent.extendComponent({
title() {
@@ -30,20 +33,30 @@ BlazeComponent.extendComponent({
}
},
importData(evt) {
importData(evt, dataSource) {
evt.preventDefault();
const dataJson = this.find('.js-import-json').value;
try {
const dataObject = JSON.parse(dataJson);
this.setError('');
this.importedData.set(dataObject);
const membersToMap = this._prepareAdditionalData(dataObject);
// store members data and mapping in Session
// (we go deep and 2-way, so storing in data context is not a viable option)
const input = this.find('.js-import-json').value;
if (dataSource === 'csv') {
const csv = input.indexOf('\t') > 0 ? input.replace(/(\t)/g, ',') : input;
const ret = Papa.parse(csv);
if (ret && ret.data && ret.data.length) this.importedData.set(ret.data);
else throw new Meteor.Error('error-csv-schema');
const membersToMap = this._prepareAdditionalData(ret.data);
this.membersToMap.set(membersToMap);
this.nextStep();
} catch (e) {
this.setError('error-json-malformed');
} else {
try {
const dataObject = JSON.parse(input);
this.setError('');
this.importedData.set(dataObject);
const membersToMap = this._prepareAdditionalData(dataObject);
// store members data and mapping in Session
// (we go deep and 2-way, so storing in data context is not a viable option)
this.membersToMap.set(membersToMap);
this.nextStep();
} catch (e) {
this.setError('error-json-malformed');
}
}
},
@@ -91,6 +104,9 @@ BlazeComponent.extendComponent({
case 'wekan':
membersToMap = wekanMembersMapper.getMembersToMap(dataObject);
break;
case 'csv':
membersToMap = csvMembersMapper.getMembersToMap(dataObject);
break;
}
return membersToMap;
},
@@ -109,11 +125,23 @@ BlazeComponent.extendComponent({
return `import-board-instruction-${Session.get('importSource')}`;
},
importPlaceHolder() {
const importSource = Session.get('importSource');
if (importSource === 'csv') {
return 'import-csv-placeholder';
} else {
return 'import-json-placeholder';
}
},
events() {
return [
{
submit(evt) {
return this.parentComponent().importData(evt);
return this.parentComponent().importData(
evt,
Session.get('importSource'),
);
},
},
];
+18 -28
View File
@@ -1,6 +1,6 @@
import { Cookies } from 'meteor/ostrio:cookies';
const cookies = new Cookies();
const { calculateIndex, enableClickOnTouch } = Utils;
const { calculateIndex } = Utils;
BlazeComponent.extendComponent({
// Proxy
@@ -74,18 +74,16 @@ BlazeComponent.extendComponent({
const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards);
const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
const currentBoard = Boards.findOne(Session.get('currentBoard'));
let swimlaneId = '';
const defaultSwimlaneId = currentBoard.getDefaultSwimline()._id;
let targetSwimlaneId = null;
// only set a new swimelane ID if the swimlanes view is active
if (
Utils.boardView() === 'board-view-swimlanes' ||
currentBoard.isTemplatesBoard()
)
swimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))._id;
else if (
Utils.boardView() === 'board-view-lists' ||
Utils.boardView() === 'board-view-cal' ||
!Utils.boardView
)
swimlaneId = currentBoard.getDefaultSwimline()._id;
targetSwimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))
._id;
// Normally the jquery-ui sortable library moves the dragged DOM element
// to its new position, which disrupts Blaze reactive updates mechanism
@@ -98,9 +96,12 @@ BlazeComponent.extendComponent({
if (MultiSelection.isActive()) {
Cards.find(MultiSelection.getMongoSelector()).forEach((card, i) => {
const newSwimlaneId = targetSwimlaneId
? targetSwimlaneId
: card.swimlaneId || defaultSwimlaneId;
card.move(
currentBoard._id,
swimlaneId,
newSwimlaneId,
listId,
sortIndex.base + i * sortIndex.increment,
);
@@ -108,15 +109,15 @@ BlazeComponent.extendComponent({
} else {
const cardDomElement = ui.item.get(0);
const card = Blaze.getData(cardDomElement);
card.move(currentBoard._id, swimlaneId, listId, sortIndex.base);
const newSwimlaneId = targetSwimlaneId
? targetSwimlaneId
: card.swimlaneId || defaultSwimlaneId;
card.move(currentBoard._id, newSwimlaneId, listId, sortIndex.base);
}
boardComponent.setIsDragging(false);
},
});
// ugly touch event hotfix
enableClickOnTouch(itemsSelector);
this.autorun(() => {
let showDesktopDragHandles = false;
currentUser = Meteor.user();
@@ -129,7 +130,7 @@ BlazeComponent.extendComponent({
showDesktopDragHandles = false;
}
if (!Utils.isMiniScreen() && showDesktopDragHandles) {
if (Utils.isMiniScreen() || showDesktopDragHandles) {
$cards.sortable({
handle: '.handle',
});
@@ -139,27 +140,16 @@ BlazeComponent.extendComponent({
});
}
if ($cards.data('sortable')) {
if ($cards.data('uiSortable') || $cards.data('sortable')) {
$cards.sortable(
'option',
'disabled',
// Disable drag-dropping when user is not member/is miniscreen
// Disable drag-dropping when user is not member
!userIsMember(),
// Not disable drag-dropping while in multi-selection mode
// MultiSelection.isActive() || !userIsMember(),
);
}
if ($cards.data('sortable')) {
$cards.sortable(
'option',
'disabled',
// Disable drag-dropping when user is not member/is miniscreen
Utils.isMiniScreen(),
// Not disable drag-dropping while in multi-selection mode
// MultiSelection.isActive() || !userIsMember(),
);
}
});
// We want to re-run this function any time a card is added.
+3 -3
View File
@@ -43,9 +43,6 @@
background: white
margin: -3px 0 8px
.list-header-card-count
height: 35px
.list-header-add
flex: 0 0 auto
padding: 20px 12px 4px
@@ -60,6 +57,9 @@
background-color: #e4e4e4;
border-bottom: 6px solid #e4e4e4;
&.list-header-card-count
min-height: 35px
height: auto
&.ui-sortable-handle
cursor: grab
+5 -8
View File
@@ -77,7 +77,7 @@ BlazeComponent.extendComponent({
else if (
Utils.boardView() === 'board-view-lists' ||
Utils.boardView() === 'board-view-cal' ||
!Utils.boardView
!Utils.boardView()
)
swimlaneId = board.getDefaultSwimline()._id;
@@ -411,7 +411,7 @@ BlazeComponent.extendComponent({
type: 'board',
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -597,7 +597,7 @@ BlazeComponent.extendComponent({
type: 'board',
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -658,10 +658,7 @@ BlazeComponent.extendComponent({
_id = element.copy(this.boardId, this.swimlaneId, this.listId);
// 1.B Linked card
} else {
delete element._id;
element.type = 'cardType-linkedCard';
element.linkedId = element.linkedId || element._id;
_id = Cards.insert(element);
_id = element.link(this.boardId, this.swimlaneId, this.listId);
}
Filter.addException(_id);
// List insertion
@@ -675,7 +672,7 @@ BlazeComponent.extendComponent({
element.sort = Boards.findOne(this.boardId)
.swimlanes()
.count();
element.type = 'swimlalne';
element.type = 'swimlane';
_id = element.copy(this.boardId);
} else if (this.isBoardTemplateSearch) {
board = Boards.findOne(element.linkedId);
+1 -2
View File
@@ -30,10 +30,9 @@ template(name="listHeader")
if canSeeAddCard
a.js-add-card.fa.fa-plus.list-header-plus-icon
a.fa.fa-navicon.js-open-list-menu
//a.list-header-handle.handle.fa.fa-arrows.js-list-handle
else
a.list-header-menu-icon.fa.fa-angle-right.js-select-list
//a.list-header-handle.handle.fa.fa-arrows.js-list-handle
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
else if currentUser.isBoardMember
if isWatching
i.list-header-watch-icon.fa.fa-eye
+29 -2
View File
@@ -223,8 +223,35 @@ BlazeComponent.extendComponent({
Template.listMorePopup.events({
'click .js-delete': Popup.afterConfirm('listDelete', function() {
Popup.close();
this.allCards().map(card => Cards.remove(card._id));
Lists.remove(this._id);
// TODO how can we avoid the fetch call?
const allCards = this.allCards().fetch();
const allCardIds = _.pluck(allCards, '_id');
// it's okay if the linked cards are on the same list
if (
Cards.find({
$and: [
{ listId: { $ne: this._id } },
{ linkedId: { $in: allCardIds } },
],
}).count() === 0
) {
allCardIds.map(_id => Cards.remove(_id));
Lists.remove(this._id);
} else {
// TODO: Figure out more informative message.
// Popup with a hint that the list cannot be deleted as there are
// linked cards. We can adapt the query above so we can list the linked
// cards.
// Related:
// client/components/cards/cardDetails.js about line 969
// https://github.com/wekan/wekan/issues/2785
const message = `${TAPi18n.__(
'delete-linked-cards-before-this-list',
)} linkedId: ${
this._id
} at client/components/lists/listHeader.js and https://github.com/wekan/wekan/issues/2785`;
alert(message);
}
Utils.goBoardId(this.boardId);
}),
});
+1 -1
View File
@@ -330,7 +330,7 @@ Template.viewer.events({
// the corresponding text). Clicking a link shouldn't fire these actions, stop
// we stop these event at the viewer component level.
'click a'(event, templateInstance) {
let prevent = true;
const prevent = true;
const userId = event.currentTarget.dataset.userid;
if (userId) {
Popup.open('member').call({ userId }, event, templateInstance);
+24
View File
@@ -15,3 +15,27 @@
local('Roboto-Bold'),
url('/fonts/roboto-bold.woff2') format('woff2'),
url('/fonts/roboto-bold.woff') format('woff')
@font-face
font-family: 'Poppins'
font-style: normal
font-weight: 400
src: local('Poppins'),
local('Poppins-Regular'),
url('/fonts/poppins-regular.woff') format('woff')
@font-face
font-family: 'Poppins'
font-style: normal
font-weight: 500
src: local('Poppins Medium'),
local('Poppins-Medium'),
url('/fonts/poppins-medium.woff') format('woff')
@font-face
font-family: 'Poppins'
font-style: normal
font-weight: 700
src: local('Poppins Bold'),
local('Poppins-Bold'),
url('/fonts/poppins-bold.woff') format('woff')
+1 -1
View File
@@ -175,7 +175,7 @@
.board-header-btn
height: 32px
line-height: @height
font-size: 16px
font-size: 15px
i.fa
line-height: 32px
+10 -4
View File
@@ -6,10 +6,16 @@ head
where the application is deployed with a path prefix, but it seems to be
difficult to do that cleanly with Blaze -- at least without adding extra
packages.
link(rel="shortcut icon" href="/wekan-favicon.png")
link(rel="apple-touch-icon" href="/wekan-favicon.png")
link(rel="mask-icon" href="/wekan-logo-150.svg")
link(rel="manifest" href="/wekan-manifest.json")
link(rel="shortcut icon" type="image/x-icon" href="/favicon.ico")
link(rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png")
link(rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png")
link(rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png")
link(rel="manifest" href="/site.webmanifest")
link(rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5")
meta(name="apple-mobile-web-app-title" content="Wekan")
meta(name="application-name" content="Wekan")
meta(name="msapplication-TileColor" content="#00aba9")
meta(name="theme-color" content="#ffffff")
template(name="userFormsLayout")
section.auth-layout
+5
View File
@@ -31,6 +31,11 @@ Template.userFormsLayout.onCreated(function() {
return this.stop();
},
});
Meteor.call('isPasswordLoginDisabled', (_, result) => {
if (result) {
$('.at-pwd-form').hide();
}
});
});
Template.userFormsLayout.onRendered(() => {
+1 -1
View File
@@ -32,7 +32,7 @@ a:hover,a:focus
border-radius: unset
html, body, input, select, textarea, button
font: 14px Roboto, "Helvetica Neue", Arial, Helvetica, sans-serif
font: 14px Roboto, Poppins, "Helvetica Neue", Arial, Helvetica, sans-serif
line-height: 18px
color: #4d4d4d
+5
View File
@@ -135,6 +135,10 @@ $popupWidth = 300px
margin-bottom: 8px
.pop-over-list
li
display: block
clear: both
li > a
clear: both
cursor: pointer
@@ -316,6 +320,7 @@ $popupWidth = 300px
input[type="file"]
margin: 4px 0 12px
width: 100%
box-sizing: border-box
.pop-over-list
li > a
@@ -11,7 +11,7 @@ BlazeComponent.extendComponent({
},
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -68,8 +68,8 @@ BlazeComponent.extendComponent({
const ruleName = this.data().ruleName.get();
const trigger = this.data().triggerVar.get();
const actionSelected = this.find('#move-spec-action').value;
const swimlaneName = this.find('#swimlaneName').value;
const listName = this.find('#listName').value;
const swimlaneName = this.find('#swimlaneName').value || '*';
const listName = this.find('#listName').value || '*';
const boardId = Session.get('currentBoard');
const destBoardId = this.find('#board-id').value;
const desc = Utils.getTriggerActionDesc(event, this);
@@ -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
@@ -48,7 +48,7 @@ BlazeComponent.extendComponent({
'members.isAdmin': true,
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
},
+26 -7
View File
@@ -230,6 +230,8 @@ template(name="chooseBoardSource")
a(href="{{pathFor '/import/trello'}}") {{_ 'from-trello'}}
li
a(href="{{pathFor '/import/wekan'}}") {{_ 'from-wekan'}}
li
a(href="{{pathFor '/import/csv'}}") {{_ 'from-csv'}}
template(name="archiveBoardPopup")
p {{_ 'close-board-pop'}}
@@ -300,7 +302,7 @@ template(name="boardMenuPopup")
ul.pop-over-list
if withApi
li
a(href="{{exportUrl}}", download="{{exportFilename}}")
a.js-export-board
i.fa.fa-share-alt
| {{_ 'export-board'}}
li
@@ -327,12 +329,10 @@ template(name="boardMenuPopup")
if isSandstorm
hr
ul.pop-over-list
if withApi
li
a(href="{{exportUrl}}", download="{{exportFilename}}")
i.fa.fa-share-alt
i.fa.fa-sign-out
| {{_ 'export-board'}}
li
a.js-export-board
i.fa.fa-share-alt
| {{_ 'export-board'}}
li
a.js-import-board
i.fa.fa-share-alt
@@ -360,6 +360,25 @@ template(name="boardMenuPopup")
i.fa.fa-sitemap
| {{_ 'subtask-settings'}}
template(name="exportBoard")
ul.pop-over-list
li
a.download-json-link(href="{{exportUrl}}", download="{{exportJsonFilename}}")
i.fa.fa-share-alt
| {{_ 'export-board-json'}}
li
a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}")
i.fa.fa-share-alt
| {{_ 'export-board-csv'}}
li
a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}")
i.fa.fa-share-alt
| {{_ 'export-board-tsv'}}
li
a.html-export-board
i.fa.fa-archive
| {{_ 'export-board-html'}}
template(name="labelsWidget")
.board-widget.board-widget-labels
h3
+76 -12
View File
@@ -196,14 +196,14 @@ Template.boardMenuPopup.events({
},
'click .js-change-board-color': Popup.open('boardChangeColor'),
'click .js-change-language': Popup.open('changeLanguage'),
'click .js-archive-board ': Popup.afterConfirm('archiveBoard', function () {
'click .js-archive-board ': Popup.afterConfirm('archiveBoard', function() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
currentBoard.archive();
// XXX We should have some kind of notification on top of the page to
// confirm that the board was successfully archived.
FlowRouter.go('home');
}),
'click .js-delete-board': Popup.afterConfirm('deleteBoard', function () {
'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
Popup.close();
Boards.remove(currentBoard._id);
@@ -213,19 +213,19 @@ Template.boardMenuPopup.events({
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
'click .js-card-settings': Popup.open('boardCardSettings'),
'click .js-export-board': Popup.open('exportBoard'),
});
Template.boardMenuPopup.onCreated(function () {
Template.boardMenuPopup.onCreated(function() {
this.apiEnabled = new ReactiveVar(false);
Meteor.call('_isApiEnabled', (e, result) => {
this.apiEnabled.set(result)
})
})
this.apiEnabled.set(result);
});
});
Template.boardMenuPopup.helpers({
withApi() {
return Template.instance().apiEnabled.get()
return Template.instance().apiEnabled.get();
},
exportUrl() {
const params = {
@@ -248,7 +248,7 @@ Template.memberPopup.events({
Popup.close();
},
'click .js-change-role': Popup.open('changePermissions'),
'click .js-remove-member': Popup.afterConfirm('removeMember', function () {
'click .js-remove-member': Popup.afterConfirm('removeMember', function() {
const boardId = Session.get('currentBoard');
const memberId = this.userId;
Cards.find({ boardId, members: memberId }).forEach(card => {
@@ -406,6 +406,70 @@ BlazeComponent.extendComponent({
},
}).register('chooseBoardSourcePopup');
BlazeComponent.extendComponent({
template() {
return 'exportBoard';
},
withApi() {
return Template.instance().apiEnabled.get();
},
exportUrl() {
const params = {
boardId: Session.get('currentBoard'),
};
const queryParams = {
authToken: Accounts._storedLoginToken(),
};
return FlowRouter.path('/api/boards/:boardId/export', params, queryParams);
},
exportCsvUrl() {
const params = {
boardId: Session.get('currentBoard'),
};
const queryParams = {
authToken: Accounts._storedLoginToken(),
};
return FlowRouter.path(
'/api/boards/:boardId/export/csv',
params,
queryParams,
);
},
exportTsvUrl() {
const params = {
boardId: Session.get('currentBoard'),
};
const queryParams = {
authToken: Accounts._storedLoginToken(),
delimiter: '\t',
};
return FlowRouter.path(
'/api/boards/:boardId/export/csv',
params,
queryParams,
);
},
exportJsonFilename() {
const boardId = Session.get('currentBoard');
return `wekan-export-board-${boardId}.json`;
},
exportCsvFilename() {
const boardId = Session.get('currentBoard');
return `wekan-export-board-${boardId}.csv`;
},
exportTsvFilename() {
const boardId = Session.get('currentBoard');
return `wekan-export-board-${boardId}.tsv`;
},
}).register('exportBoardPopup');
Template.exportBoard.events({
'click .html-export-board': async event => {
event.preventDefault();
await ExportHtml(Popup)();
},
});
Template.labelsWidget.events({
'click .js-label': Popup.open('editLabel'),
'click .js-add-label': Popup.open('createLabel'),
@@ -510,7 +574,7 @@ BlazeComponent.extendComponent({
'members.userId': Meteor.userId(),
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
},
@@ -589,7 +653,7 @@ BlazeComponent.extendComponent({
'subtext-with-parent',
'no-parent',
];
options.forEach(function (element) {
options.forEach(function(element) {
if (element !== value) {
$(`#${element} ${MCB}`).toggleClass(CKCLS, false);
$(`#${element}`).toggleClass(CKCLS, false);
@@ -688,7 +752,7 @@ BlazeComponent.extendComponent({
'members.userId': Meteor.userId(),
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
},
@@ -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');
},
});
+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)
+6 -32
View File
@@ -1,6 +1,6 @@
import { Cookies } from 'meteor/ostrio:cookies';
const cookies = new Cookies();
const { calculateIndex, enableClickOnTouch } = Utils;
const { calculateIndex } = Utils;
function currentListIsInThisSwimlane(swimlaneId) {
const currentList = Lists.findOne(Session.get('currentList'));
@@ -87,9 +87,6 @@ function initSortable(boardComponent, $listsDom) {
},
});
// ugly touch event hotfix
enableClickOnTouch('.js-list:not(.js-list-composer)');
function userIsMember() {
return (
Meteor.user() &&
@@ -111,7 +108,7 @@ function initSortable(boardComponent, $listsDom) {
showDesktopDragHandles = false;
}
if (!Utils.isMiniScreen() && showDesktopDragHandles) {
if (Utils.isMiniScreen() || showDesktopDragHandles) {
$listsDom.sortable({
handle: '.js-list-handle',
});
@@ -122,34 +119,12 @@ function initSortable(boardComponent, $listsDom) {
}
const $listDom = $listsDom;
if ($listDom.data('sortable')) {
if ($listDom.data('uiSortable') || $listDom.data('sortable')) {
$listsDom.sortable(
'option',
'disabled',
// Disable drag-dropping when user is not member/is worker/is miniscreen
!userIsMember(),
// Not disable drag-dropping while in multi-selection mode
// MultiSelection.isActive() || !userIsMember(),
);
}
if ($listDom.data('sortable')) {
$listsDom.sortable(
'option',
'disabled',
// Disable drag-dropping when user is not member/is worker/is miniscreen
Meteor.user().isWorker(),
// Not disable drag-dropping while in multi-selection mode
// MultiSelection.isActive() || !userIsMember(),
);
}
if ($listDom.data('sortable')) {
$listsDom.sortable(
'option',
'disabled',
// Disable drag-dropping when user is not member/is worker/is miniscreen
Utils.isMiniScreen(),
// Disable drag-dropping when user is not member/is worker
!userIsMember() || Meteor.user().isWorker(),
// Not disable drag-dropping while in multi-selection mode
// MultiSelection.isActive() || !userIsMember(),
);
@@ -210,8 +185,7 @@ BlazeComponent.extendComponent({
}
const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
Utils.isMiniScreen() ||
(!Utils.isMiniScreen() && showDesktopDragHandles)
Utils.isMiniScreen() || showDesktopDragHandles
? ['.js-list-handle', '.js-swimlane-header-handle']
: ['.js-list-header'],
);
+2
View File
@@ -29,6 +29,8 @@ avatar-radius = 50%
position: absolute
&.avatar-image
object-fit: cover;
object-position: center;
height: 100%
width: @height
+11 -2
View File
@@ -112,11 +112,20 @@ template(name="changeSettingsPopup")
i.fa.fa-check
unless currentUser.isWorker
li
label.bold
label.bold.clear
i.fa.fa-sort-numeric-asc
| {{_ 'show-cards-minimum-count'}}
input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="0" max="99" onkeydown="return false")
input.js-apply-show-cards-at.left(type="submit" value="{{_ 'apply'}}")
label.bold.clear
i.fa.fa-calendar
| {{_ 'start-day-of-week'}}
select#start-day-of-week.inline-input.left
each day in weekDays startDayOfWeek
if day.isSelected
option(selected="true", value="#{day.value}") #{day.name}
else
option(value="#{day.value}") #{day.name}
input.js-apply-user-settings.left(type="submit" value="{{_ 'apply'}}")
template(name="userDeletePopup")
unless currentUser.isWorker
+35 -3
View File
@@ -224,6 +224,27 @@ Template.changeSettingsPopup.helpers({
return cookies.get('limitToShowCardsCount');
}
},
weekDays(startDay) {
return [
TAPi18n.__('sunday'),
TAPi18n.__('monday'),
TAPi18n.__('tuesday'),
TAPi18n.__('wednesday'),
TAPi18n.__('thursday'),
TAPi18n.__('friday'),
TAPi18n.__('saturday'),
].map(function(day, index) {
return { name: day, value: index, isSelected: index === startDay };
});
},
startDayOfWeek() {
currentUser = Meteor.user();
if (currentUser) {
return currentUser.getStartDayOfWeek();
} else {
return cookies.get('startDayOfWeek');
}
},
});
Template.changeSettingsPopup.events({
@@ -247,20 +268,31 @@ Template.changeSettingsPopup.events({
cookies.set('hasHiddenSystemMessages', 'true');
}
},
'click .js-apply-show-cards-at'(event, templateInstance) {
'click .js-apply-user-settings'(event, templateInstance) {
event.preventDefault();
const minLimit = parseInt(
templateInstance.$('#show-cards-count-at').val(),
10,
);
const startDay = parseInt(
templateInstance.$('#start-day-of-week').val(),
10,
);
const currentUser = Meteor.user();
if (!isNaN(minLimit)) {
currentUser = Meteor.user();
if (currentUser) {
Meteor.call('changeLimitToShowCardsCount', minLimit);
} else {
cookies.set('limitToShowCardsCount', minLimit);
}
Popup.back();
}
if (!isNaN(startDay)) {
if (currentUser) {
Meteor.call('changeStartDayOfWeek', startDay);
} else {
cookies.set('startDayOfWeek', startDay);
}
}
Popup.back();
},
});
+10
View File
@@ -10,12 +10,22 @@ DatePicker = BlazeComponent.extendComponent({
this.defaultTime = defaultTime;
},
startDayOfWeek() {
const currentUser = Meteor.user();
if (currentUser) {
return currentUser.getStartDayOfWeek();
} else {
return 1;
}
},
onRendered() {
const $picker = this.$('.js-datepicker')
.datepicker({
todayHighlight: true,
todayBtn: 'linked',
language: TAPi18n.getLanguage(),
weekStart: this.startDayOfWeek(),
})
.on(
'changeDate',
+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++) {
+213
View File
@@ -0,0 +1,213 @@
const JSZip = require('jszip');
window.ExportHtml = Popup => {
const saveAs = function(blob, filename) {
let dl = document.createElement('a');
dl.href = window.URL.createObjectURL(blob);
dl.onclick = event => document.body.removeChild(event.target);
dl.style.display = 'none';
dl.target = '_blank';
dl.download = filename;
document.body.appendChild(dl);
dl.click();
};
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}`;
};
const removeAnchors = htmlString => {
const replaceOpenAnchor = htmlString.replace(
new RegExp('<a ', 'gim'),
'<span ',
);
return replaceOpenAnchor.replace(new RegExp('</a', 'gim'), '</span');
};
const ensureSidebarRemoved = () => {
document.querySelector('.board-sidebar.sidebar').remove();
};
const addJsonExportToZip = async (zip, boardSlug) => {
const downloadJSONLink = document.querySelector('.download-json-link');
const downloadJSONURL = downloadJSONLink.href;
const response = await fetch(downloadJSONURL);
const responseBody = await response.text();
zip.file(`data/${boardSlug}.json`, responseBody);
};
const closeSidebar = () => {
document.querySelector('.board-header-btn.js-toggle-sidebar').click();
};
const cleanBoardHtml = () => {
Array.from(document.querySelectorAll('script')).forEach(elem =>
elem.remove(),
);
Array.from(
document.querySelectorAll('link:not([rel="stylesheet"])'),
).forEach(elem => elem.remove());
document.querySelector('#header-quick-access').remove();
Array.from(
document.querySelectorAll('#header-main-bar .board-header-btns'),
).forEach(elem => elem.remove());
Array.from(document.querySelectorAll('.list-composer')).forEach(elem =>
elem.remove(),
);
Array.from(
document.querySelectorAll(
'.list-composer,.js-card-composer, .js-add-card',
),
).forEach(elem => elem.remove());
Array.from(
document.querySelectorAll('.js-perfect-scrollbar > div:nth-of-type(n+2)'),
).forEach(elem => elem.remove());
Array.from(document.querySelectorAll('.js-perfect-scrollbar')).forEach(
elem => {
elem.style = 'overflow-y: auto !important;';
elem.classList.remove('js-perfect-scrollbar');
},
);
Array.from(document.querySelectorAll('[href]:not(link)')).forEach(elem =>
elem.attributes.removeNamedItem('href'),
);
Array.from(document.querySelectorAll('[href]')).forEach(elem => {
// eslint-disable-next-line no-self-assign
elem.href = elem.href;
// eslint-disable-next-line no-self-assign
elem.src = elem.src;
});
Array.from(document.querySelectorAll('.is-editable')).forEach(elem => {
elem.classList.remove('is-editable');
});
};
const getBoardSlug = () => {
return window.location.href.split('/').pop();
};
const getStylesheetList = () => {
return Array.from(
document.querySelectorAll('link[href][rel="stylesheet"]'),
);
};
const downloadStylesheets = async (stylesheets, zip) => {
await asyncForEach(stylesheets, async elem => {
const response = await fetch(elem.href);
const responseBody = await response.text();
const finalResponse = responseBody.replace(
new RegExp('packages/[^/]+/upstream/', 'gim'),
'../',
);
const filename = elem.href
.split('/')
.pop()
.split('?')
.shift();
const fileFullPath = `style/${filename}`;
zip.file(fileFullPath, finalResponse);
elem.href = `../${fileFullPath}`;
});
};
const getSrcAttached = () => {
return Array.from(document.querySelectorAll('[src]'));
};
const downloadSrcAttached = async (elements, zip, boardSlug) => {
await asyncForEach(elements, async elem => {
const response = await fetch(elem.src);
const responseBody = await response.blob();
const filename = elem.src
.split('/')
.pop()
.split('?')
.shift();
const fileFullPath = `${boardSlug}/${elem.tagName.toLowerCase()}/${filename}`;
zip.file(fileFullPath, responseBody);
elem.src = `./${elem.tagName.toLowerCase()}/${filename}`;
});
};
const removeCssUrlSurround = url => {
const working = url || '';
return working
.split('url(')
.join('')
.split('")')
.join('')
.split('"')
.join('')
.split("')")
.join('')
.split("'")
.join('')
.split(')')
.join('');
};
const getCardCovers = () => {
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 responseBody = await response.blob();
const filename = removeCssUrlSurround(elem.style['background-image'])
.split('/')
.pop()
.split('?')
.shift()
.split('#')
.shift();
const fileFullPath = `${boardSlug}/covers/${filename}`;
zip.file(fileFullPath, responseBody);
elem.style = "background-image: url('" + `covers/${filename}` + "')";
});
};
const addBoardHTMLToZip = (boardSlug, zip) => {
ensureSidebarRemoved();
const htmlOutputPath = `${boardSlug}/index.html`;
zip.file(
htmlOutputPath,
new Blob([removeAnchors(getPageHtmlString())], {
type: 'application/html',
}),
);
};
return async () => {
const zip = new JSZip();
const boardSlug = getBoardSlug();
await addJsonExportToZip(zip, boardSlug);
Popup.close();
closeSidebar();
cleanBoardHtml();
await downloadStylesheets(getStylesheetList(), zip);
await downloadSrcAttached(getSrcAttached(), zip, boardSlug);
await downloadCardCovers(getCardCovers(), zip, boardSlug);
addBoardHTMLToZip(boardSlug, zip);
const content = await zip.generateAsync({ type: 'blob' });
saveAs(content, `${boardSlug}.zip`);
window.location.reload();
};
};
+21 -11
View File
@@ -1,6 +1,16 @@
// XXX There is no reason to define these shortcuts globally, they should be
// attached to a template (most of them will go in the `board` template).
function getHoveredCardId() {
const card = $('.js-minicard:hover').get(0);
if (!card) return null;
return Blaze.getData(card)._id;
}
function getSelectedCardId() {
return Session.get('selectedCard') || getHoveredCardId();
}
Mousetrap.bind('?', () => {
FlowRouter.go('shortcuts');
});
@@ -50,9 +60,9 @@ Mousetrap.bind(['down', 'up'], (evt, key) => {
}
});
// XXX This shortcut should also work when hovering over a card in board view
Mousetrap.bind('space', evt => {
if (!Session.get('currentCard')) {
const cardId = getSelectedCardId();
if (!cardId) {
return;
}
@@ -62,7 +72,7 @@ Mousetrap.bind('space', evt => {
}
if (Meteor.user().isBoardMember()) {
const card = Cards.findOne(Session.get('currentCard'));
const card = Cards.findOne(cardId);
card.toggleMember(currentUserId);
// We should prevent scrolling in card when spacebar is clicked
// This should do it according to Mousetrap docs, but it doesn't
@@ -70,9 +80,9 @@ Mousetrap.bind('space', evt => {
}
});
// XXX This shortcut should also work when hovering over a card in board view
Mousetrap.bind('c', evt => {
if (!Session.get('currentCard')) {
const cardId = getSelectedCardId();
if (!cardId) {
return;
}
@@ -86,7 +96,7 @@ Mousetrap.bind('c', evt => {
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
) {
const card = Cards.findOne(Session.get('currentCard'));
const card = Cards.findOne(cardId);
card.archive();
// We should prevent scrolling in card when spacebar is clicked
// This should do it according to Mousetrap docs, but it doesn't
@@ -97,19 +107,19 @@ Mousetrap.bind('c', evt => {
Template.keyboardShortcuts.helpers({
mapping: [
{
keys: ['W'],
keys: ['w'],
action: 'shortcut-toggle-sidebar',
},
{
keys: ['Q'],
keys: ['q'],
action: 'shortcut-filter-my-cards',
},
{
keys: ['F'],
keys: ['f'],
action: 'shortcut-toggle-filterbar',
},
{
keys: ['X'],
keys: ['x'],
action: 'shortcut-clear-filters',
},
{
@@ -129,7 +139,7 @@ Template.keyboardShortcuts.helpers({
action: 'shortcut-assign-self',
},
{
keys: ['C'],
keys: ['c'],
action: 'archive-card',
},
],
+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++) {
+1 -1
View File
@@ -49,7 +49,7 @@ window.Popup = new (class {
// has one. This allows us to position a sub-popup exactly at the same
// position than its parent.
let openerElement;
if (clickFromPopup(evt)) {
if (clickFromPopup(evt) && self._getTopStack()) {
openerElement = self._getTopStack().openerElement;
} else {
self._stack = [];
+4 -1
View File
@@ -38,7 +38,7 @@ version: '2'
# sudo service docker start
# ----------------------------------------------------------------------------------
# ==== USAGE OF THIS docker-compose.yml ====
# 1) For seeing does Wekan work, try this and check with your webbroser:
# 1) For seeing does Wekan work, try this and check with your web browser:
# docker-compose up
# 2) Stop Wekan and start Wekan in background:
# docker-compose stop
@@ -598,6 +598,9 @@ services:
# example : LOGOUT_ON_MINUTES=55
#- LOGOUT_ON_MINUTES=
#-------------------------------------------------------------------
# Hide password login form
# - PASSWORD_LOGIN_ENABLED=true
#-------------------------------------------------------------------
depends_on:
- wekandb
+6
View File
@@ -37,6 +37,12 @@ spec:
value: {{ .Values.root_url | default "https://wekan.local" | quote }}
- name: MONGO_URL
value: "{{ template "mongodb-replicaset.url" . }}"
{{- range $key := .Values.env }}
{{- if .value }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
livenessProbe:
httpGet:
path: /
+4
View File
@@ -2,6 +2,10 @@
apiVersion: v1
kind: ServiceAccount
metadata:
{{- if .Values.serviceAccounts.annotations }}
annotations:
{{ .Values.serviceAccounts.annotations | indent 4}}
{{- end }}
labels:
app: {{ template "wekan.name" . }}
chart: {{ template "wekan.chart" . }}
+7 -4
View File
@@ -8,6 +8,7 @@
serviceAccounts:
create: true
name: ""
annotations: ""
## Wekan image configuration
##
@@ -29,7 +30,9 @@ credentials:
## Specify additional environmental variables for the Deployment
##
env: {}
env:
- name: ""
value: ""
service:
type: NodePort
@@ -59,10 +62,10 @@ ingress:
# hosts:
# - wekan-example.local
route:
enabled: false
route:
enabled: false
resources:
resources:
requests:
memory: 128Mi
cpu: 300m
+68 -36
View File
@@ -1,6 +1,6 @@
{
"accept": "قبول",
"act-activity-notify": "اشعارات النشاط",
"act-activity-notify": "اشعار النشاط",
"act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
@@ -74,12 +74,12 @@
"activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"activity-checklist-uncompleted-card": "uncompleted the checklist %s",
"activity-editComment": "edited comment %s",
"activity-deleteComment": "deleted comment %s",
"activity-deleteComment": "تعليق محذوف %s",
"add-attachment": "إضافة مرفق",
"add-board": "إضافة لوحة",
"add-card": "إضافة بطاقة",
"add-swimlane": "Add Swimlane",
"add-subtask": "Add Subtask",
"add-subtask": "إضافة مهمة فرعية",
"add-checklist": "إضافة قائمة تدقيق",
"add-checklist-item": "إضافة عنصر إلى قائمة التحقق",
"add-cover": "إضافة غلاف",
@@ -111,8 +111,8 @@
"restore-board": "استعادة اللوحة",
"no-archived-boards": "لا توجد لوحات في الأرشيف.",
"archives": "أرشيف",
"template": "Template",
"templates": "Templates",
"template": "نموذج",
"templates": "نماذج",
"assign-member": "تعيين عضو",
"attached": "أُرفق)",
"attachment": "مرفق",
@@ -152,8 +152,6 @@
"card-spent": "امضى وقتا",
"card-edit-attachments": "تعديل المرفقات",
"card-edit-custom-fields": "تعديل الحقل المعدل",
"card-start-voting": "Start voting",
"card-cancel-voting": "Delete voting and all votes",
"card-edit-labels": "تعديل العلامات",
"card-edit-members": "تعديل الأعضاء",
"card-labels-title": "تعديل علامات البطاقة.",
@@ -163,13 +161,18 @@
"cardAttachmentsPopup-title": "إرفاق من",
"cardCustomField-datePopup-title": "تغير التاريخ",
"cardCustomFieldsPopup-title": "تعديل الحقل المعدل",
"cardStartVotingPopup-title": "Start a vote",
"cardStartVotingPopup-title": "ابدأ تصويت",
"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": "Public vote",
"vote-for-it": "for it",
"vote-against": "against",
"vote-public": "Show who voted what",
"vote-for-it": "مع",
"vote-against": "ضد",
"deleteVotePopup-title": "Delete vote?",
"vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.",
"cardDeletePopup-title": "حذف البطاقة ?",
"cardDetailsActionsPopup-title": "إجراءات على البطاقة",
"cardLabelsPopup-title": "علامات",
@@ -192,7 +195,7 @@
"changePasswordPopup-title": "تغيير كلمة المرور",
"changePermissionsPopup-title": "تعديل الصلاحيات",
"changeSettingsPopup-title": "تغيير الاعدادات",
"subtasks": "Subtasks",
"subtasks": "المهمات الفرعية",
"checklists": "قوائم التّدقيق",
"click-to-star": "اضغط لإضافة اللوحة للمفضلة.",
"click-to-unstar": "اضغط لحذف اللوحة من المفضلة.",
@@ -203,9 +206,9 @@
"color-black": "black",
"color-blue": "blue",
"color-crimson": "crimson",
"color-darkgreen": "darkgreen",
"color-gold": "gold",
"color-gray": "gray",
"color-darkgreen": "اخضر غامق",
"color-gold": "ذهبي",
"color-gray": "رمادي",
"color-green": "green",
"color-indigo": "indigo",
"color-lime": "lime",
@@ -220,17 +223,17 @@
"color-purple": "purple",
"color-red": "red",
"color-saddlebrown": "saddlebrown",
"color-silver": "silver",
"color-silver": "فضي",
"color-sky": "sky",
"color-slateblue": "slateblue",
"color-white": "white",
"color-white": "أبيض",
"color-yellow": "yellow",
"unset-color": "Unset",
"comment": "تعليق",
"comment-placeholder": "أكتب تعليق",
"comment-only": "التعليق فقط",
"comment-only-desc": "يمكن التعليق على بطاقات فقط.",
"no-comments": "No comments",
"no-comments": "لا يوجد تعليقات",
"no-comments-desc": "Can not see comments and activities.",
"worker": "Worker",
"worker-desc": "Can only move cards, assign itself to card and comment.",
@@ -248,19 +251,21 @@
"createBoardPopup-title": "إنشاء لوحة",
"chooseBoardSourcePopup-title": "استيراد لوحة",
"createLabelPopup-title": "إنشاء علامة",
"createCustomField": "Create Field",
"createCustomFieldPopup-title": "Create Field",
"createCustomField": "انشاء حقل",
"createCustomFieldPopup-title": "انشاء حقل",
"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)",
"custom-field-dropdown-options": "List Options",
"custom-field-dropdown-options-placeholder": "Press enter to add more options",
"custom-field-dropdown-unknown": "(unknown)",
"custom-field-number": "Number",
"custom-field-text": "Text",
"custom-field-number": "رقم",
"custom-field-text": "نص",
"custom-fields": "Custom Fields",
"date": "تاريخ",
"decline": "Decline",
@@ -306,6 +311,7 @@
"error-board-notAMember": "You need to be a member of this board to do that",
"error-json-malformed": "Your text is not valid JSON",
"error-json-schema": "Your JSON data does not include the proper information in the correct format",
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
"error-list-doesNotExist": "This list does not exist",
"error-user-doesNotExist": "This user does not exist",
"error-user-notAllowSelf": "لا يمكنك دعوة نفسك",
@@ -313,6 +319,11 @@
"error-username-taken": "إسم المستخدم مأخوذ مسبقا",
"error-email-taken": "البريد الإلكتروني مأخوذ بالفعل",
"export-board": "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",
"exportBoardPopup-title": "Export board",
"sort": "Sort",
"sort-desc": "Click to Sort List",
"list-sort-by": "Sort the List By:",
@@ -343,19 +354,21 @@
"headerBarCreateBoardPopup-title": "إنشاء لوحة",
"home": "الرئيسية",
"import": "Import",
"link": "Link",
"link": "رابط",
"import-board": "استيراد لوحة",
"import-board-c": "استيراد لوحة",
"import-board-title-trello": "Import board from Trello",
"import-board-title-wekan": "Import board from previous export",
"import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
"import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.",
"import-board-title-csv": "Import board from CSV/TSV",
"from-trello": "من تريلو",
"from-wekan": "From previous export",
"from-csv": "From CSV/TSV",
"import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (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-map-members": "رسم خريطة الأعضاء",
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
"import-show-user-mapping": "Review members mapping",
@@ -388,6 +401,7 @@
"swimlaneActionPopup-title": "Swimlane Actions",
"swimlaneAddPopup-title": "Add a Swimlane below",
"listImportCardPopup-title": "Import a Trello card",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listMorePopup-title": "المزيد",
"link-list": "رابط إلى هذه القائمة",
"list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
@@ -476,15 +490,15 @@
"this-board": "هذه اللوحة",
"this-card": "هذه البطاقة",
"spent-time-hours": "Spent time (hours)",
"overtime-hours": "Overtime (hours)",
"overtime": "Overtime",
"overtime-hours": "وقت اضافي (ساعات)",
"overtime": "وقت اضافي",
"has-overtime-cards": "Has overtime cards",
"has-spenttime-cards": "Has spent time cards",
"time": "الوقت",
"title": "عنوان",
"tracking": "تتبع",
"tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.",
"type": "Type",
"type": "النوع",
"unassign-member": "إلغاء تعيين العضو",
"unsaved-description": "لديك وصف غير محفوظ",
"unwatch": "غير مُشاهد",
@@ -558,7 +572,7 @@
"OS_Totalmem": "الذاكرة الكلية لنظام التشغيل",
"OS_Type": "نوع نظام التشغيل",
"OS_Uptime": "مدة تشغيل نظام التشغيل",
"days": "days",
"days": "أيام",
"hours": "الساعات",
"minutes": "الدقائق",
"seconds": "الثواني",
@@ -572,17 +586,17 @@
"accounts-allowUserNameChange": "Allow Username Change",
"createdAt": "Created at",
"verified": "Verified",
"active": "Active",
"active": "نشط",
"card-received": "Received",
"card-received-on": "Received on",
"card-end": "End",
"card-end-on": "Ends on",
"editCardReceivedDatePopup-title": "Change received date",
"editCardEndDatePopup-title": "Change end date",
"setCardColorPopup-title": "Set color",
"setCardActionsColorPopup-title": "Choose a color",
"setSwimlaneColorPopup-title": "Choose a color",
"setListColorPopup-title": "Choose a color",
"setCardColorPopup-title": "حدد اللون",
"setCardActionsColorPopup-title": "اختر لوناً",
"setSwimlaneColorPopup-title": "اختر لوناً",
"setListColorPopup-title": "اختر لوناً",
"assigned-by": "Assigned By",
"requested-by": "Requested By",
"board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.",
@@ -627,7 +641,7 @@
"r-when-a-card": "When a card",
"r-is": "is",
"r-is-moved": "is moved",
"r-added-to": "added to",
"r-added-to": "Added to",
"r-removed-from": "Removed from",
"r-the-board": "the board",
"r-list": "list",
@@ -675,6 +689,7 @@
"r-of-checklist": "of checklist",
"r-send-email": "Send an email",
"r-to": "to",
"r-of": "of",
"r-subject": "subject",
"r-rule-details": "Rule details",
"r-d-move-to-top-gen": "Move card to top of its list",
@@ -776,5 +791,22 @@
"mark-all-as-read": "Mark all as read",
"remove-all-read": "Remove all read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename"
"allowRenamePopup-title": "Allow Rename",
"start-day-of-week": "Set day of the week start",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "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"
}
+39 -7
View File
@@ -152,8 +152,6 @@
"card-spent": "Изработено време",
"card-edit-attachments": "Промени прикачените файлове",
"card-edit-custom-fields": "Промени собствените полета",
"card-start-voting": "Start voting",
"card-cancel-voting": "Delete voting and all votes",
"card-edit-labels": "Промени етикетите",
"card-edit-members": "Промени членовете",
"card-labels-title": "Промени етикетите за картата.",
@@ -166,10 +164,15 @@
"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": "Public vote",
"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.",
"cardDeletePopup-title": "Желаете да изтриете картата?",
"cardDetailsActionsPopup-title": "Опции",
"cardLabelsPopup-title": "Етикети",
@@ -253,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": "(няма)",
@@ -306,6 +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-list-doesNotExist": "Този списък не съществува",
"error-user-doesNotExist": "Този потребител не съществува",
"error-user-notAllowSelf": "Не можете да поканите себе си",
@@ -313,6 +319,11 @@
"error-username-taken": "Това потребителско име е вече заето",
"error-email-taken": "Имейлът е вече зает",
"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",
"exportBoardPopup-title": "Експортиране на Табло",
"sort": "Sort",
"sort-desc": "Click to Sort List",
"list-sort-by": "Sort the List By:",
@@ -348,14 +359,16 @@
"import-board-c": "Импортирай Табло",
"import-board-title-trello": "Импорт на табло от Trello",
"import-board-title-wekan": "Import board from previous export",
"import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
"import-sandstorm-warning": "Импортирането ще изтрие всичката налична информация в таблото и ще я замени с нова.",
"import-board-title-csv": "Import board from CSV/TSV",
"from-trello": "От Trello",
"from-wekan": "From previous export",
"from-csv": "From CSV/TSV",
"import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (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": "Копирайте валидната Ви JSON информация тук",
"import-csv-placeholder": "Paste your valid CSV/TSV data here",
"import-map-members": "Map members",
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
"import-show-user-mapping": "Review members mapping",
@@ -388,6 +401,7 @@
"swimlaneActionPopup-title": "Swimlane Actions",
"swimlaneAddPopup-title": "Add a Swimlane below",
"listImportCardPopup-title": "Импорт на карта от Trello",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listMorePopup-title": "Още",
"link-list": "Връзка към този списък",
"list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
@@ -627,7 +641,7 @@
"r-when-a-card": "Когато карта",
"r-is": "е",
"r-is-moved": "преместена",
"r-added-to": "добавена в",
"r-added-to": "Added to",
"r-removed-from": "премахната от",
"r-the-board": "таблото",
"r-list": "списък",
@@ -675,6 +689,7 @@
"r-of-checklist": "of checklist",
"r-send-email": "Send an email",
"r-to": "to",
"r-of": "of",
"r-subject": "subject",
"r-rule-details": "Детайли за правилото",
"r-d-move-to-top-gen": "Move card to top of its list",
@@ -776,5 +791,22 @@
"mark-all-as-read": "Mark all as read",
"remove-all-read": "Remove all read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename"
"allowRenamePopup-title": "Allow Rename",
"start-day-of-week": "Set day of the week start",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "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"
}
+39 -7
View File
@@ -152,8 +152,6 @@
"card-spent": "Spent Time",
"card-edit-attachments": "Edit attachments",
"card-edit-custom-fields": "Edit custom fields",
"card-start-voting": "Start voting",
"card-cancel-voting": "Delete voting and all votes",
"card-edit-labels": "Edit labels",
"card-edit-members": "Edit members",
"card-labels-title": "Change the labels for the card.",
@@ -166,10 +164,15 @@
"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": "Public vote",
"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.",
"cardDeletePopup-title": "Diverkañ ar gartenn ?",
"cardDetailsActionsPopup-title": "Card Actions",
"cardLabelsPopup-title": "Labels",
@@ -253,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)",
@@ -306,6 +311,7 @@
"error-board-notAMember": "You need to be a member of this board to do that",
"error-json-malformed": "Your text is not valid JSON",
"error-json-schema": "Your JSON data does not include the proper information in the correct format",
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
"error-list-doesNotExist": "This list does not exist",
"error-user-doesNotExist": "This user does not exist",
"error-user-notAllowSelf": "You can not invite yourself",
@@ -313,6 +319,11 @@
"error-username-taken": "This username is already taken",
"error-email-taken": "Email has already been taken",
"export-board": "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",
"exportBoardPopup-title": "Export board",
"sort": "Sort",
"sort-desc": "Click to Sort List",
"list-sort-by": "Sort the List By:",
@@ -348,14 +359,16 @@
"import-board-c": "Import board",
"import-board-title-trello": "Import board from Trello",
"import-board-title-wekan": "Import board from previous export",
"import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
"import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.",
"import-board-title-csv": "Import board from CSV/TSV",
"from-trello": "From Trello",
"from-wekan": "From previous export",
"from-csv": "From CSV/TSV",
"import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (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-map-members": "Map members",
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
"import-show-user-mapping": "Review members mapping",
@@ -388,6 +401,7 @@
"swimlaneActionPopup-title": "Swimlane Actions",
"swimlaneAddPopup-title": "Add a Swimlane below",
"listImportCardPopup-title": "Import a Trello card",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listMorePopup-title": "Muioc’h",
"link-list": "Link to this list",
"list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
@@ -627,7 +641,7 @@
"r-when-a-card": "When a card",
"r-is": "is",
"r-is-moved": "is moved",
"r-added-to": "added to",
"r-added-to": "Added to",
"r-removed-from": "Removed from",
"r-the-board": "the board",
"r-list": "list",
@@ -675,6 +689,7 @@
"r-of-checklist": "of checklist",
"r-send-email": "Send an email",
"r-to": "to",
"r-of": "of",
"r-subject": "subject",
"r-rule-details": "Rule details",
"r-d-move-to-top-gen": "Move card to top of its list",
@@ -776,5 +791,22 @@
"mark-all-as-read": "Mark all as read",
"remove-all-read": "Remove all read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename"
"allowRenamePopup-title": "Allow Rename",
"start-day-of-week": "Set day of the week start",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "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"
}
+39 -7
View File
@@ -152,8 +152,6 @@
"card-spent": "Temps Dedicat",
"card-edit-attachments": "Edita arxius adjunts",
"card-edit-custom-fields": "Editar camps personalitzats",
"card-start-voting": "Start voting",
"card-cancel-voting": "Delete voting and all votes",
"card-edit-labels": "Edita etiquetes",
"card-edit-members": "Edita membres",
"card-labels-title": "Canvia les etiquetes de la fitxa",
@@ -166,10 +164,15 @@
"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": "Public vote",
"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.",
"cardDeletePopup-title": "Esborrar fitxa?",
"cardDetailsActionsPopup-title": "Accions de fitxes",
"cardLabelsPopup-title": "Etiquetes",
@@ -253,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)",
@@ -306,6 +311,7 @@
"error-board-notAMember": "Necessites ser membre d'aquest tauler per dur a terme aquesta acció",
"error-json-malformed": "El text no és JSON vàlid",
"error-json-schema": "La dades JSON no contenen la informació en el format correcte",
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
"error-list-doesNotExist": "La llista no existeix",
"error-user-doesNotExist": "L'usuari no existeix",
"error-user-notAllowSelf": "No et pots convidar a tu mateix",
@@ -313,6 +319,11 @@
"error-username-taken": "Aquest usuari ja existeix",
"error-email-taken": "L'adreça de correu electrònic ja és en ús",
"export-board": "Exporta tauler",
"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",
"exportBoardPopup-title": "Exporta tauler",
"sort": "Sort",
"sort-desc": "Click to Sort List",
"list-sort-by": "Sort the List By:",
@@ -348,14 +359,16 @@
"import-board-c": "Importa tauler",
"import-board-title-trello": "Importa tauler des de Trello",
"import-board-title-wekan": "Import board from previous export",
"import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
"import-sandstorm-warning": "Estau segur que voleu esborrar aquesta checklist?",
"import-board-title-csv": "Import board from CSV/TSV",
"from-trello": "Des de Trello",
"from-wekan": "From previous export",
"from-csv": "From CSV/TSV",
"import-board-instruction-trello": "En el teu tauler Trello, ves a 'Menú', 'Més'.' Imprimir i Exportar', 'Exportar JSON', i copia el text resultant.",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (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": "Aferra codi JSON vàlid aquí",
"import-csv-placeholder": "Paste your valid CSV/TSV data here",
"import-map-members": "Mapeja el membres",
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
"import-show-user-mapping": "Revisa l'assignació de membres",
@@ -388,6 +401,7 @@
"swimlaneActionPopup-title": "Accions de Carril de Natació",
"swimlaneAddPopup-title": "Add a Swimlane below",
"listImportCardPopup-title": "importa una fitxa de Trello",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listMorePopup-title": "Més",
"link-list": "Enllaça a aquesta llista",
"list-delete-pop": "Totes les accions seran esborrades de la llista d'activitats i no serà possible recuperar la llista",
@@ -627,7 +641,7 @@
"r-when-a-card": "When a card",
"r-is": "is",
"r-is-moved": "is moved",
"r-added-to": "added to",
"r-added-to": "Added to",
"r-removed-from": "Removed from",
"r-the-board": "the board",
"r-list": "list",
@@ -675,6 +689,7 @@
"r-of-checklist": "of checklist",
"r-send-email": "Send an email",
"r-to": "to",
"r-of": "of",
"r-subject": "subject",
"r-rule-details": "Rule details",
"r-d-move-to-top-gen": "Move card to top of its list",
@@ -776,5 +791,22 @@
"mark-all-as-read": "Mark all as read",
"remove-all-read": "Remove all read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename"
"allowRenamePopup-title": "Allow Rename",
"start-day-of-week": "Set day of the week start",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "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"
}
+39 -7
View File
@@ -152,8 +152,6 @@
"card-spent": "Strávený čas",
"card-edit-attachments": "Upravit přílohy",
"card-edit-custom-fields": "Upravit vlastní pole",
"card-start-voting": "Start voting",
"card-cancel-voting": "Delete voting and all votes",
"card-edit-labels": "Upravit štítky",
"card-edit-members": "Upravit členy",
"card-labels-title": "Změnit štítky karty.",
@@ -166,10 +164,15 @@
"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": "Public vote",
"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.",
"cardDeletePopup-title": "Smazat kartu?",
"cardDetailsActionsPopup-title": "Akce karty",
"cardLabelsPopup-title": "Štítky",
@@ -253,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é)",
@@ -306,6 +311,7 @@
"error-board-notAMember": "K provedení změny musíš být členem tohoto tabla",
"error-json-malformed": "Tvůj text není validní JSON",
"error-json-schema": "Tato JSON data neobsahují správné informace v platném formátu",
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
"error-list-doesNotExist": "Tento sloupec ;neexistuje",
"error-user-doesNotExist": "Tento uživatel neexistuje",
"error-user-notAllowSelf": "Nemůžeš pozvat sám sebe",
@@ -313,6 +319,11 @@
"error-username-taken": "Toto uživatelské jméno již existuje",
"error-email-taken": "Tento email byl již použit",
"export-board": "Exportovat tablo",
"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",
"exportBoardPopup-title": "Exportovat tablo",
"sort": "řadit",
"sort-desc": "Click to Sort List",
"list-sort-by": "řadit seznam podle",
@@ -348,14 +359,16 @@
"import-board-c": "Importovat tablo",
"import-board-title-trello": "Import board from Trello",
"import-board-title-wekan": "Importovat tablo z předchozího exportu",
"import-sandstorm-backup-warning": "Nemažte data, která importujete z původního exportovaného tabla nebo Trello předtím, nežli zkontrolujete, jestli lze tuto část zavřít a znovu otevřít nebo jestli se Vám nezobrazuje chyba tabla, což znamená ztrátu dat.",
"import-sandstorm-warning": "Importované tablo spaže všechny existující data v tablu a nahradí je importovaným tablem.",
"import-board-title-csv": "Import board from CSV/TSV",
"from-trello": "Z Trella",
"from-wekan": "Z předchozího exportu",
"from-csv": "From CSV/TSV",
"import-board-instruction-trello": "Na svém Trello tablu, otevři 'Menu', pak 'More', 'Print and Export', 'Export JSON', a zkopíruj výsledný text",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .",
"import-board-instruction-wekan": "Ve vašem tablu jděte do 'Menu', klikněte na 'Exportovat tablo' a zkopírujte text ze staženého souboru.",
"import-board-instruction-about-errors": "Někdy import funguje i přestože dostáváte chyby při importování tabla, které se zobrazí na stránce Všechna tabla.",
"import-json-placeholder": "Sem vlož validní JSON data",
"import-csv-placeholder": "Paste your valid CSV/TSV data here",
"import-map-members": "Mapovat členy",
"import-members-map": "Toto importované tablo obsahuje několik osob. Prosím namapujte osoby z importu na místní uživatelské účty.",
"import-show-user-mapping": "Zkontrolovat namapování členů",
@@ -388,6 +401,7 @@
"swimlaneActionPopup-title": "Akce swimlane",
"swimlaneAddPopup-title": "Přidat swimlane dolů",
"listImportCardPopup-title": "Importovat Trello kartu",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listMorePopup-title": "Více",
"link-list": "Odkaz na tento sloupec",
"list-delete-pop": "Všechny akce budou odstraněny z kanálu aktivity a nebude možné sloupec obnovit. Toto nelze vrátit zpět.",
@@ -627,7 +641,7 @@
"r-when-a-card": "Pokud karta",
"r-is": "je",
"r-is-moved": "je přesunuto",
"r-added-to": "přidáno do",
"r-added-to": "Added to",
"r-removed-from": "Odstraněno z",
"r-the-board": "tablo",
"r-list": "sloupce",
@@ -675,6 +689,7 @@
"r-of-checklist": "ze zaškrtávacího seznamu",
"r-send-email": "Odeslat e-mail",
"r-to": "komu",
"r-of": "of",
"r-subject": "předmět",
"r-rule-details": "Podrobnosti pravidla",
"r-d-move-to-top-gen": "Přesunout kartu na začátek toho sloupce",
@@ -776,5 +791,22 @@
"mark-all-as-read": "Označit vše jako přečtené",
"remove-all-read": "Remove all read",
"allow-rename": "Povolit přejmenování",
"allowRenamePopup-title": "Povolit přejmenování"
"allowRenamePopup-title": "Povolit přejmenování",
"start-day-of-week": "Set day of the week start",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "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"
}
+785 -753
View File
File diff suppressed because it is too large. Load diff
+40 -8
View File
@@ -152,8 +152,6 @@
"card-spent": "Aufgewendete Zeit",
"card-edit-attachments": "Anhänge ändern",
"card-edit-custom-fields": "Benutzerdefinierte Felder editieren",
"card-start-voting": "Abstimmung starten",
"card-cancel-voting": "Abstimmung mit allen Stimmen löschen ",
"card-edit-labels": "Labels ändern",
"card-edit-members": "Mitglieder ändern",
"card-labels-title": "Labels für diese Karte ändern.",
@@ -166,10 +164,15 @@
"cardStartVotingPopup-title": "Abstimmung starten",
"positiveVoteMembersPopup-title": "Befürworter",
"negativeVoteMembersPopup-title": "Gegner",
"card-edit-voting": "Abstimmung editieren",
"editVoteEndDatePopup-title": "Enddatum der Abstimmung ändern",
"allowNonBoardMembers": "Alle eingeloggte Nutzer erlauben",
"vote-question": "Abstimmen über",
"vote-public": "Öffentliche Abstimmung",
"vote-public": "Zeigen, wer was gewählt hat",
"vote-for-it": "Dafür",
"vote-against": "Dagegen",
"deleteVotePopup-title": "Wahl löschen?",
"vote-delete-pop": "Löschen ist unwiderruflich. Alle Aktionen die dieser Karte zugeordnet sind werden ebenfalls gelöscht.",
"cardDeletePopup-title": "Karte löschen?",
"cardDetailsActionsPopup-title": "Kartenaktionen",
"cardLabelsPopup-title": "Labels",
@@ -253,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)",
@@ -306,6 +311,7 @@
"error-board-notAMember": "Um das zu tun, müssen Sie Mitglied dieses Boards sein",
"error-json-malformed": "Ihre Eingabe ist kein gültiges JSON",
"error-json-schema": "Ihre JSON-Daten enthalten nicht die gewünschten Informationen im richtigen Format",
"error-csv-schema": "hre CSV (Comma Separated Values)/TSV (Tab Separated Values) enthalten nicht die gewünschten Informationen im richtigen Format",
"error-list-doesNotExist": "Diese Liste existiert nicht",
"error-user-doesNotExist": "Dieser Nutzer existiert nicht",
"error-user-notAllowSelf": "Sie können sich nicht selbst einladen.",
@@ -313,6 +319,11 @@
"error-username-taken": "Dieser Benutzername ist bereits vergeben",
"error-email-taken": "E-Mail wird schon verwendet",
"export-board": "Board exportieren",
"export-board-json": "Board als JSON exportieren",
"export-board-csv": "Board als CSV exportieren",
"export-board-tsv": "Board als TSV exportieren",
"export-board-html": "Board als HTML exportieren",
"exportBoardPopup-title": "Board exportieren",
"sort": "Sortieren",
"sort-desc": "Zum Sortieren der Liste klicken",
"list-sort-by": "Sortieren der Liste nach:",
@@ -328,7 +339,7 @@
"filter-clear": "Filter entfernen",
"filter-no-label": "Kein Label",
"filter-no-member": "Kein Mitglied",
"filter-no-assignee": "No assignee",
"filter-no-assignee": "Nicht zugewiesen",
"filter-no-custom-fields": "Keine benutzerdefinierten Felder",
"filter-show-archive": "Archivierte Listen anzeigen",
"filter-hide-empty": "Leere Listen verstecken",
@@ -348,14 +359,16 @@
"import-board-c": "Board importieren",
"import-board-title-trello": "Board von Trello importieren",
"import-board-title-wekan": "Board aus vorherigem Export importieren",
"import-sandstorm-backup-warning": "Löschen Sie keine Daten, die Sie aus einem ursprünglich exportierten oder Trelloboard importieren, bevor Sie geprüft haben, ob alles funktioniert. Andernfalls kann es zu Datenverlust kommen, falls es zu einem \"Board nicht gefunden\"-Fehler kommt.",
"import-sandstorm-warning": "Das importierte Board wird alle bereits existierenden Daten löschen und mit den importierten Daten überschreiben.",
"import-board-title-csv": "Board von CSV/TSV importieren",
"from-trello": "Von Trello",
"from-wekan": "Aus vorherigem Export",
"from-csv": "Aus CSV/TSV",
"import-board-instruction-trello": "Gehen Sie in ihrem Trello-Board auf 'Menü', dann 'Mehr', 'Drucken und Exportieren', 'JSON-Export' und kopieren Sie den dort angezeigten Text",
"import-board-instruction-csv": "Fügen Sie die Ihre Comma-Separated- (CSV) / bzw. Tab-Separated-Values (TSV) ein.",
"import-board-instruction-wekan": "Gehen Sie in Ihrem Board auf 'Menü', danach auf 'Board exportieren' und kopieren Sie den Text aus der heruntergeladenen Datei.",
"import-board-instruction-about-errors": "Treten beim importieren eines Board Fehler auf, so kann der Import dennoch erfolgreich abgeschlossen sein und das Board ist auf der Seite \"Alle Boards\" zusehen.",
"import-json-placeholder": "Fügen Sie die korrekten JSON-Daten hier ein",
"import-csv-placeholder": "Fügen Sie die korrekten CSV/TSV-Daten hier ein ",
"import-map-members": "Mitglieder zuordnen",
"import-members-map": "Das importierte Board hat einige Mitglieder. Bitte ordnen sie die Mitglieder, die Sie importieren wollen, Ihren Benutzern zu.",
"import-show-user-mapping": "Mitgliederzuordnung überprüfen",
@@ -388,6 +401,7 @@
"swimlaneActionPopup-title": "Swimlaneaktionen",
"swimlaneAddPopup-title": "Swimlane unterhalb einfügen",
"listImportCardPopup-title": "Eine Trello-Karte importieren",
"listImportCardsTsvPopup-title": "CSV/TSV importieren",
"listMorePopup-title": "Mehr",
"link-list": "Link zu dieser Liste",
"list-delete-pop": "Alle Aktionen werden aus dem Verlauf gelöscht und die Liste kann nicht wiederhergestellt werden.",
@@ -627,7 +641,7 @@
"r-when-a-card": "Wenn Karte",
"r-is": "wird",
"r-is-moved": "verschoben wird",
"r-added-to": "hinzugefügt zu",
"r-added-to": "Hinzugefügt zu",
"r-removed-from": "entfernt von",
"r-the-board": "das Board",
"r-list": "Liste",
@@ -675,6 +689,7 @@
"r-of-checklist": "der Checkliste",
"r-send-email": "Eine E-Mail senden",
"r-to": "an",
"r-of": "von",
"r-subject": "Betreff",
"r-rule-details": "Regeldetails",
"r-d-move-to-top-gen": "Karte nach oben in die Liste verschieben",
@@ -776,5 +791,22 @@
"mark-all-as-read": "Alle als gelesen markieren",
"remove-all-read": "Alle gelesenen entfernen",
"allow-rename": "Umbenennen erlauben",
"allowRenamePopup-title": "Umbenennen erlauben"
"allowRenamePopup-title": "Umbenennen erlauben",
"start-day-of-week": "Wochentagbeginn festlegen",
"monday": "Montag",
"tuesday": "Dienstag",
"wednesday": "Mittwoch",
"thursday": "Donnerstag",
"friday": "Freitag",
"saturday": "Samstag",
"sunday": "Sonntag",
"status": "Status",
"swimlane": "Swimlane",
"owner": "Eigentümer",
"last-modified-at": "Zuletzt geändert um",
"last-activity": "Letzte Aktivität",
"voting": "Abstimunng",
"archived": "Archiviert",
"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"
}
+44 -12
View File
@@ -43,7 +43,7 @@
"act-withBoardTitle": "__board__",
"act-withCardTitle": "[__board__] __card__",
"actions": "Ενέργειες",
"activities": "Activities",
"activities": "Δραστηριότητες",
"activity": "Δραστηριότητα",
"activity-added": "added %s to %s",
"activity-archived": "%s μετακινήθηκε στο Αρχείο",
@@ -55,7 +55,7 @@
"activity-imported-board": "imported %s from %s",
"activity-joined": "joined %s",
"activity-moved": "moved %s from %s to %s",
"activity-on": "on %s",
"activity-on": "στό %s",
"activity-removed": "removed %s from %s",
"activity-sent": "sent %s to %s",
"activity-unjoined": "unjoined %s",
@@ -76,18 +76,18 @@
"activity-editComment": "edited comment %s",
"activity-deleteComment": "deleted comment %s",
"add-attachment": "Add Attachment",
"add-board": "Add Board",
"add-board": "Προσθήκη Πίνακα",
"add-card": "Προσθήκη Κάρτας",
"add-swimlane": "Add Swimlane",
"add-subtask": "Add Subtask",
"add-checklist": "Add Checklist",
"add-checklist": "Προσθήκη Λίστας ελέγχου",
"add-checklist-item": "Add an item to checklist",
"add-cover": "Add Cover",
"add-label": "Προσθήκη Ετικέτας",
"add-list": "Προσθήκη Λίστας",
"add-members": "Προσθήκη Μελών",
"added": "Προστέθηκε",
"addMemberPopup-title": "Μέλοι",
"addMemberPopup-title": "Μέλη",
"admin": "Διαχειριστής",
"admin-desc": "Can view and edit cards, remove members, and change settings for the board.",
"admin-announcement": "Announcement",
@@ -152,8 +152,6 @@
"card-spent": "Spent Time",
"card-edit-attachments": "Edit attachments",
"card-edit-custom-fields": "Edit custom fields",
"card-start-voting": "Start voting",
"card-cancel-voting": "Delete voting and all votes",
"card-edit-labels": "Edit labels",
"card-edit-members": "Edit members",
"card-labels-title": "Change the labels for the card.",
@@ -166,10 +164,15 @@
"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": "Public vote",
"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.",
"cardDeletePopup-title": "Διαγραφή Κάρτας;",
"cardDetailsActionsPopup-title": "Card Actions",
"cardLabelsPopup-title": "Ετικέτες",
@@ -253,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)",
@@ -306,6 +311,7 @@
"error-board-notAMember": "You need to be a member of this board to do that",
"error-json-malformed": "Το κείμενο δεν είναι έγκυρο JSON",
"error-json-schema": "Your JSON data does not include the proper information in the correct format",
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
"error-list-doesNotExist": "Η λίστα δεν υπάρχει",
"error-user-doesNotExist": "Ο χρήστης δεν υπάρχει",
"error-user-notAllowSelf": "You can not invite yourself",
@@ -313,6 +319,11 @@
"error-username-taken": "This username is already taken",
"error-email-taken": "Email has already been taken",
"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",
"exportBoardPopup-title": "Εξαγωγή πίνακα",
"sort": "Sort",
"sort-desc": "Click to Sort List",
"list-sort-by": "Sort the List By:",
@@ -348,14 +359,16 @@
"import-board-c": "Εισαγωγή πίνακα",
"import-board-title-trello": "Import board from Trello",
"import-board-title-wekan": "Import board from previous export",
"import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
"import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.",
"import-board-title-csv": "Import board from CSV/TSV",
"from-trello": "Από το Trello",
"from-wekan": "From previous export",
"from-csv": "From CSV/TSV",
"import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (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-map-members": "Map members",
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
"import-show-user-mapping": "Review members mapping",
@@ -388,6 +401,7 @@
"swimlaneActionPopup-title": "Swimlane Actions",
"swimlaneAddPopup-title": "Add a Swimlane below",
"listImportCardPopup-title": "Εισαγωγή μιας κάρτας Trello",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listMorePopup-title": "Περισσότερα",
"link-list": "Link to this list",
"list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
@@ -627,7 +641,7 @@
"r-when-a-card": "Όταν μία κάρτα",
"r-is": "is",
"r-is-moved": "is moved",
"r-added-to": "added to",
"r-added-to": "Added to",
"r-removed-from": "Removed from",
"r-the-board": "the board",
"r-list": "list",
@@ -675,6 +689,7 @@
"r-of-checklist": "of checklist",
"r-send-email": "Send an email",
"r-to": "to",
"r-of": "of",
"r-subject": "subject",
"r-rule-details": "Rule details",
"r-d-move-to-top-gen": "Μετακίνηση κάρτας στην αρχή της λίστας της",
@@ -776,5 +791,22 @@
"mark-all-as-read": "Mark all as read",
"remove-all-read": "Remove all read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename"
"allowRenamePopup-title": "Allow Rename",
"start-day-of-week": "Set day of the week start",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "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"
}
+39 -7
View File
@@ -152,8 +152,6 @@
"card-spent": "Spent Time",
"card-edit-attachments": "Edit attachments",
"card-edit-custom-fields": "Edit custom fields",
"card-start-voting": "Start voting",
"card-cancel-voting": "Delete voting and all votes",
"card-edit-labels": "Edit labels",
"card-edit-members": "Edit members",
"card-labels-title": "Change the labels for the card.",
@@ -166,10 +164,15 @@
"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": "Public vote",
"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.",
"cardDeletePopup-title": "Delete Card?",
"cardDetailsActionsPopup-title": "Card Actions",
"cardLabelsPopup-title": "Labels",
@@ -253,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)",
@@ -306,6 +311,7 @@
"error-board-notAMember": "You need to be a member of this board to do that",
"error-json-malformed": "Your text is not valid JSON",
"error-json-schema": "Your JSON data does not include the proper information in the correct format",
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
"error-list-doesNotExist": "This list does not exist",
"error-user-doesNotExist": "This user does not exist",
"error-user-notAllowSelf": "You can not invite yourself",
@@ -313,6 +319,11 @@
"error-username-taken": "This username is already taken",
"error-email-taken": "Email has already been taken",
"export-board": "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",
"exportBoardPopup-title": "Export board",
"sort": "Sort",
"sort-desc": "Click to Sort List",
"list-sort-by": "Sort the List By:",
@@ -348,14 +359,16 @@
"import-board-c": "Import board",
"import-board-title-trello": "Import board from Trello",
"import-board-title-wekan": "Import board from previous export",
"import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
"import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.",
"import-board-title-csv": "Import board from CSV/TSV",
"from-trello": "From Trello",
"from-wekan": "From previous export",
"from-csv": "From CSV/TSV",
"import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (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-map-members": "Map members",
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
"import-show-user-mapping": "Review members mapping",
@@ -388,6 +401,7 @@
"swimlaneActionPopup-title": "Swimlane Actions",
"swimlaneAddPopup-title": "Add a Swimlane below",
"listImportCardPopup-title": "Import a Trello card",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listMorePopup-title": "More",
"link-list": "Link to this list",
"list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
@@ -627,7 +641,7 @@
"r-when-a-card": "When a card",
"r-is": "is",
"r-is-moved": "is moved",
"r-added-to": "added to",
"r-added-to": "Added to",
"r-removed-from": "Removed from",
"r-the-board": "the board",
"r-list": "list",
@@ -675,6 +689,7 @@
"r-of-checklist": "of checklist",
"r-send-email": "Send an email",
"r-to": "to",
"r-of": "of",
"r-subject": "subject",
"r-rule-details": "Rule details",
"r-d-move-to-top-gen": "Move card to top of its list",
@@ -776,5 +791,22 @@
"mark-all-as-read": "Mark all as read",
"remove-all-read": "Remove all read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename"
"allowRenamePopup-title": "Allow Rename",
"start-day-of-week": "Set day of the week start",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "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"
}
+38 -9
View File
@@ -152,8 +152,6 @@
"card-spent": "Spent Time",
"card-edit-attachments": "Edit attachments",
"card-edit-custom-fields": "Edit custom fields",
"card-start-voting": "Start voting",
"card-cancel-voting": "Delete voting and all votes",
"card-edit-labels": "Edit labels",
"card-edit-members": "Edit members",
"card-labels-title": "Change the labels for the card.",
@@ -166,10 +164,15 @@
"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": "Public vote",
"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.",
"cardDeletePopup-title": "Delete Card?",
"cardDetailsActionsPopup-title": "Card Actions",
"cardLabelsPopup-title": "Labels",
@@ -253,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)",
@@ -306,6 +311,7 @@
"error-board-notAMember": "You need to be a member of this board to do that",
"error-json-malformed": "Your text is not valid JSON",
"error-json-schema": "Your JSON data does not include the proper information in the correct format",
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format ",
"error-list-doesNotExist": "This list does not exist",
"error-user-doesNotExist": "This user does not exist",
"error-user-notAllowSelf": "You can not invite yourself",
@@ -313,6 +319,11 @@
"error-username-taken": "This username is already taken",
"error-email-taken": "Email has already been taken",
"export-board": "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",
"exportBoardPopup-title": "Export board",
"sort": "Sort",
"sort-desc": "Click to Sort List",
"list-sort-by": "Sort the List By:",
@@ -348,14 +359,16 @@
"import-board-c": "Import board",
"import-board-title-trello": "Import board from Trello",
"import-board-title-wekan": "Import board from previous export",
"import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
"import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.",
"import-board-title-csv": "Import board from CSV/TSV",
"from-trello": "From Trello",
"from-wekan": "From previous export",
"from-csv": "From CSV/TSV",
"import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (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-map-members": "Map members",
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
"import-show-user-mapping": "Review members mapping",
@@ -388,6 +401,7 @@
"swimlaneActionPopup-title": "Swimlane Actions",
"swimlaneAddPopup-title": "Add a Swimlane below",
"listImportCardPopup-title": "Import a Trello card",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listMorePopup-title": "More",
"link-list": "Link to this list",
"list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
@@ -643,7 +657,6 @@
"r-when-a-member": "When a member is",
"r-when-the-member": "When the member",
"r-name": "name",
"r-is": "is",
"r-when-a-attach": "When an attachment",
"r-when-a-checklist": "When a checklist is",
"r-when-the-checklist": "When the checklist",
@@ -657,7 +670,6 @@
"r-top-of": "Top of",
"r-bottom-of": "Bottom of",
"r-its-list": "its list",
"r-list": "list",
"r-archive": "Move to Archive",
"r-unarchive": "Restore from Archive",
"r-card": "card",
@@ -677,6 +689,7 @@
"r-of-checklist": "of checklist",
"r-send-email": "Send an email",
"r-to": "to",
"r-of": "of",
"r-subject": "subject",
"r-rule-details": "Rule details",
"r-d-move-to-top-gen": "Move card to top of its list",
@@ -712,7 +725,6 @@
"r-swimlane-name": "swimlane name",
"r-board-note": "Note: leave a field empty to match every possible value. ",
"r-checklist-note": "Note: checklist's items have to be written as comma separated values.",
"r-added-to": "added to",
"r-when-a-card-is-moved": "When a card is moved to another list",
"r-set": "Set",
"r-update": "Update",
@@ -779,5 +791,22 @@
"mark-all-as-read": "Mark all as read",
"remove-all-read": "Remove all read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename"
"allowRenamePopup-title": "Allow Rename",
"start-day-of-week": "Set day of the week start",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "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"
}
+48 -16
View File
@@ -1,15 +1,15 @@
{
"accept": "Akcepti",
"act-activity-notify": "Activity Notification",
"act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addAttachment": "aldonita alligitecon __attachment__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__",
"act-deleteAttachment": "forigita alligitecon __attachment__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__",
"act-addSubtask": "aldonita subtaskon __subtask__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__",
"act-addLabel": "Aldonita etikedo __label__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane en la tabulo __board__",
"act-addedLabel": "Aldonita etikedo __label__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane en la tabulo __board__",
"act-removeLabel": "Forigita etikedo __label__ de karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__",
"act-removedLabel": "Forigita etikedo __label__ de karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__",
"act-addChecklist": "aldonita kontrololiston __checklist__ al karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__",
"act-addChecklistItem": "aldonita kontrolliston __checklistItem__ al kontrololisto __checklist__ en la karto __card__ en la listo __list__ en la naĝotubo __swimlane__ en la tabulo __board__",
"act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
@@ -152,8 +152,6 @@
"card-spent": "Spent Time",
"card-edit-attachments": "Edit attachments",
"card-edit-custom-fields": "Edit custom fields",
"card-start-voting": "Start voting",
"card-cancel-voting": "Delete voting and all votes",
"card-edit-labels": "Redakti etikedojn",
"card-edit-members": "Redakti membrojn",
"card-labels-title": "Change the labels for the card.",
@@ -166,10 +164,15 @@
"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": "Public vote",
"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.",
"cardDeletePopup-title": "Delete Card?",
"cardDetailsActionsPopup-title": "Card Actions",
"cardLabelsPopup-title": "Etikedoj",
@@ -253,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)",
@@ -306,6 +311,7 @@
"error-board-notAMember": "You need to be a member of this board to do that",
"error-json-malformed": "Via teksto estas nevalida JSON",
"error-json-schema": "Via JSON ne enhavas la ĝustajn informojn en ĝusta formato",
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
"error-list-doesNotExist": "Tio listo ne ekzistas",
"error-user-doesNotExist": "Tio uzanto ne ekzistas",
"error-user-notAllowSelf": "You can not invite yourself",
@@ -313,6 +319,11 @@
"error-username-taken": "Uzantnomo jam prenita",
"error-email-taken": "Email has already been taken",
"export-board": "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",
"exportBoardPopup-title": "Export board",
"sort": "Sort",
"sort-desc": "Click to Sort List",
"list-sort-by": "Sort the List By:",
@@ -348,14 +359,16 @@
"import-board-c": "Import board",
"import-board-title-trello": "Import board from Trello",
"import-board-title-wekan": "Import board from previous export",
"import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
"import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.",
"import-board-title-csv": "Import board from CSV/TSV",
"from-trello": "From Trello",
"from-wekan": "From previous export",
"from-csv": "From CSV/TSV",
"import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (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-map-members": "Map members",
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
"import-show-user-mapping": "Review members mapping",
@@ -388,6 +401,7 @@
"swimlaneActionPopup-title": "Swimlane Actions",
"swimlaneAddPopup-title": "Add a Swimlane below",
"listImportCardPopup-title": "Import a Trello card",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listMorePopup-title": "Pli",
"link-list": "Link to this list",
"list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
@@ -627,7 +641,7 @@
"r-when-a-card": "When a card",
"r-is": "is",
"r-is-moved": "is moved",
"r-added-to": "added to",
"r-added-to": "Added to",
"r-removed-from": "Removed from",
"r-the-board": "the board",
"r-list": "listo",
@@ -675,6 +689,7 @@
"r-of-checklist": "of checklist",
"r-send-email": "Send an email",
"r-to": "to",
"r-of": "of",
"r-subject": "temo",
"r-rule-details": "Rule details",
"r-d-move-to-top-gen": "Move card to top of its list",
@@ -776,5 +791,22 @@
"mark-all-as-read": "Mark all as read",
"remove-all-read": "Remove all read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename"
"allowRenamePopup-title": "Allow Rename",
"start-day-of-week": "Set day of the week start",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "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"
}
+56 -24
View File
@@ -1,20 +1,20 @@
{
"accept": "Aceptar",
"act-activity-notify": "Notificación de Actividad",
"act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"act-addAttachment": "agregado archivo adjunto __attachment__ a tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-deleteAttachment": "eliminado archivo adjunto __attachment__ de la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-addSubtask": "agregada subtarea __subtask__ a tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-addLabel": "Agregada etiqueta __label__ a tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-addedLabel": "Agregada etiqueta __label__ a tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-removeLabel": "Eliminada etiqueta __label__ de tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-removedLabel": "Eliminada etiqueta __label__ de tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-addChecklist": "agregada checklist __checklist__ a tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-addChecklistItem": "agregado ítem __checklistItem__ a checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-removeChecklist": "eliminada checklist __checklist__ de tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-removeChecklistItem": "eliminado ítem __checklistItem__ de checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-checkedItem": "marcado ítem __checklistItem__ de checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-uncheckedItem": "desmarcado ítem __checklistItem__ de checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"act-completeChecklist": "completada checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __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__",
@@ -71,10 +71,10 @@
"add": "Agregar",
"activity-checked-item-card": "checked %s in checklist %s",
"activity-unchecked-item-card": "unchecked %s in checklist %s",
"activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"activity-checklist-completed-card": "completada checklist __checklist__ en la tarjeta __card__ en la lista __list__ en el swimlane __swimlane__ en el tablero __board__",
"activity-checklist-uncompleted-card": "uncompleted the checklist %s",
"activity-editComment": "edited comment %s",
"activity-deleteComment": "deleted comment %s",
"activity-editComment": "comentario %s editado",
"activity-deleteComment": "comentario %s eliminado",
"add-attachment": "Agregar Adjunto",
"add-board": "Agregar Tablero",
"add-card": "Agregar Tarjeta",
@@ -152,8 +152,6 @@
"card-spent": "Tiempo Empleado",
"card-edit-attachments": "Editar adjuntos",
"card-edit-custom-fields": "Editar campos personalizados",
"card-start-voting": "Start voting",
"card-cancel-voting": "Delete voting and all votes",
"card-edit-labels": "Editar etiquetas",
"card-edit-members": "Editar miembros",
"card-labels-title": "Cambiar las etiquetas de la tarjeta.",
@@ -166,10 +164,15 @@
"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": "Public vote",
"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.",
"cardDeletePopup-title": "¿Borrar Tarjeta?",
"cardDetailsActionsPopup-title": "Acciones de la Tarjeta",
"cardLabelsPopup-title": "Etiquetas",
@@ -253,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)",
@@ -306,6 +311,7 @@
"error-board-notAMember": "Necesitás ser miembro de este tablero para hacer eso",
"error-json-malformed": "Tu texto no es JSON válido",
"error-json-schema": "Tus datos JSON no incluyen la información correcta en el formato adecuado",
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
"error-list-doesNotExist": "Esta lista no existe",
"error-user-doesNotExist": "Este usuario no existe",
"error-user-notAllowSelf": "No podés invitarte a vos mismo",
@@ -313,6 +319,11 @@
"error-username-taken": "El nombre de usuario ya existe",
"error-email-taken": "El email ya existe",
"export-board": "Exportar tablero",
"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",
"exportBoardPopup-title": "Exportar tablero",
"sort": "Sort",
"sort-desc": "Click to Sort List",
"list-sort-by": "Sort the List By:",
@@ -348,14 +359,16 @@
"import-board-c": "Importar tablero",
"import-board-title-trello": "Importar tablero de Trello",
"import-board-title-wekan": "Import board from previous export",
"import-sandstorm-backup-warning": "Do not delete data you import from original exported board or Trello before checking does this grain close and open again, or do you get Board not found error, that means data loss.",
"import-sandstorm-warning": "El tablero importado va a borrar todos los datos existentes en el tablero y reemplazarlos con los del tablero en cuestión.",
"import-board-title-csv": "Import board from CSV/TSV",
"from-trello": "De Trello",
"from-wekan": "From previous export",
"from-csv": "From CSV/TSV",
"import-board-instruction-trello": "En tu tablero de Trello, ve a 'Menú', luego a 'Más', 'Imprimir y Exportar', 'Exportar JSON', y copia el texto resultante.",
"import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (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": "Pegá tus datos JSON válidos acá",
"import-csv-placeholder": "Paste your valid CSV/TSV data here",
"import-map-members": "Mapear Miembros",
"import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
"import-show-user-mapping": "Revisar mapeo de miembros",
@@ -388,6 +401,7 @@
"swimlaneActionPopup-title": "Acciones de la Calle",
"swimlaneAddPopup-title": "Add a Swimlane below",
"listImportCardPopup-title": "Importar una tarjeta Trello",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listMorePopup-title": "Mas",
"link-list": "Enlace a esta lista",
"list-delete-pop": "Todas las acciones van a ser eliminadas del agregador de actividad y no podás recuperar la lista. No se puede deshacer.",
@@ -627,7 +641,7 @@
"r-when-a-card": "When a card",
"r-is": "is",
"r-is-moved": "is moved",
"r-added-to": "added to",
"r-added-to": "Added to",
"r-removed-from": "Removed from",
"r-the-board": "the board",
"r-list": "list",
@@ -675,6 +689,7 @@
"r-of-checklist": "of checklist",
"r-send-email": "Send an email",
"r-to": "to",
"r-of": "of",
"r-subject": "subject",
"r-rule-details": "Rule details",
"r-d-move-to-top-gen": "Move card to top of its list",
@@ -776,5 +791,22 @@
"mark-all-as-read": "Mark all as read",
"remove-all-read": "Remove all read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename"
"allowRenamePopup-title": "Allow Rename",
"start-day-of-week": "Set day of the week start",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "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"
}
+812
View File
@@ -0,0 +1,812 @@
{
"accept": "Aceptar",
"act-activity-notify": "Notificación de actividad",
"act-addAttachment": "añadido el adjunto __attachment__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-deleteAttachment": "eliminado el adjunto __attachment__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-addSubtask": "añadida la subtarea __subtask__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-addLabel": "añadida la etiqueta __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-addedLabel": "añadida la etiqueta __label__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-removeLabel": "eliminada la etiqueta __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-removedLabel": "eliminada la etiqueta __label__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-addChecklist": "añadida la lista de verificación __checklist__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-addChecklistItem": "añadido el elemento __checklistItem__ a la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-removeChecklist": "eliminada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-removeChecklistItem": "eliminado el elemento __checklistItem__ de la lista de verificación __checkList__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-checkedItem": "marcado el elemento __checklistItem__ de la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-uncheckedItem": "desmarcado el elemento __checklistItem__ de la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-completeChecklist": "completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-uncompleteChecklist": "no completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-addComment": "comentario en la tarjeta__card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-editComment": "comentario editado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-deleteComment": "comentario eliminado en la tarjeta __card__: __comment__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-createBoard": "creó el tablero __board__",
"act-createSwimlane": "creó el carril de flujo __swimlane__ en el tablero __board__",
"act-createCard": "creada la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-createCustomField": "creado el campo personalizado __customField__ en el tablero __board__",
"act-deleteCustomField": "eliminado el campo personalizado __customField__ del tablero __board__",
"act-setCustomField": "editado el campo personalizado __customField__: __customFieldValue__ en la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-createList": "añadida la lista __list__ al tablero __board__",
"act-addBoardMember": "añadido el mimbro __member__ al tablero __board__",
"act-archivedBoard": "El tablero __board__ se ha archivado",
"act-archivedCard": "La tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado",
"act-archivedList": "La lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado",
"act-archivedSwimlane": "El carril __swimlane__ del tablero __board__ se ha archivado",
"act-importBoard": "importado el tablero __board__",
"act-importCard": "importada la tarjeta __card__ a la lista __list__ del carrril __swimlane__ del tablero __board__",
"act-importList": "importada la lista __list__ al carril __swimlane__ del tablero __board__",
"act-joinMember": "añadido el miembro __member__ a la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-moveCard": "movida la tarjeta __card__ del tablero __board__ de la lista __oldList__ del carril __oldSwimlane__ a la lista __list__ del carril __swimlane__",
"act-moveCardToOtherBoard": "movida la tarjeta __card__ de la lista __oldList__ del carril __oldSwimlane__ del tablero __oldBoard__ a la lista __list__ del carril __swimlane__ del tablero __board__",
"act-removeBoardMember": "eliminado el miembro __member__ del tablero __board__",
"act-restoredCard": "restaurada la tarjeta __card__ a la lista __list__ del carril __swimlane__ del tablero __board__",
"act-unjoinMember": "eliminado el miembro __member__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"act-withBoardTitle": "__board__",
"act-withCardTitle": "[__board__] __card__",
"actions": "Acciones",
"activities": "Actividades",
"activity": "Actividad",
"activity-added": "ha añadido %s a %s",
"activity-archived": "%s se ha archivado",
"activity-attached": "ha adjuntado %s a %s",
"activity-created": "ha creado %s",
"activity-customfield-created": "creó el campo personalizado %s",
"activity-excluded": "ha excluido %s de %s",
"activity-imported": "ha importado %s a %s desde %s",
"activity-imported-board": "ha importado %s desde %s",
"activity-joined": "se ha unido a %s",
"activity-moved": "ha movido %s de %s a %s",
"activity-on": "en %s",
"activity-removed": "ha eliminado %s de %s",
"activity-sent": "ha enviado %s a %s",
"activity-unjoined": "se ha desvinculado de %s",
"activity-subtask-added": "ha añadido la subtarea a %s",
"activity-checked-item": "marcado %s en la lista de verificación %s de %s",
"activity-unchecked-item": "desmarcado %s en lista %s de %s",
"activity-checklist-added": "ha añadido una lista de verificación a %s",
"activity-checklist-removed": "eliminada una lista de verificación desde %s",
"activity-checklist-completed": "lista de verificación completada %s de %s",
"activity-checklist-uncompleted": "no completado la lista %s de %s",
"activity-checklist-item-added": "ha añadido el elemento de la lista de verificación a '%s' en %s",
"activity-checklist-item-removed": "eliminado un elemento de la lista de verificación desde '%s' en %s",
"add": "Añadir",
"activity-checked-item-card": "marcado %s en la lista de verificación %s",
"activity-unchecked-item-card": "desmarcado %s en la lista de verificación %s",
"activity-checklist-completed-card": "completada la lista de verificación __checklist__ de la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
"activity-checklist-uncompleted-card": "no completó la lista de verificación %s",
"activity-editComment": "comentario editado",
"activity-deleteComment": "comentario eliminado",
"add-attachment": "Añadir adjunto",
"add-board": "Añadir tablero",
"add-card": "Añadir una tarjeta",
"add-swimlane": "Añadir un carril de flujo",
"add-subtask": "Añadir subtarea",
"add-checklist": "Añadir una lista de verificación",
"add-checklist-item": "Añadir un elemento a la lista de verificación",
"add-cover": "Añadir portada",
"add-label": "Añadir una etiqueta",
"add-list": "Añadir una lista",
"add-members": "Añadir miembros",
"added": "Añadida el",
"addMemberPopup-title": "Miembros",
"admin": "Administrador",
"admin-desc": "Puedes ver y editar tarjetas, eliminar miembros, y cambiar las preferencias del tablero",
"admin-announcement": "Aviso",
"admin-announcement-active": "Activar el aviso para todo el sistema",
"admin-announcement-title": "Aviso del administrador",
"all-boards": "Tableros",
"and-n-other-card": "y __count__ tarjeta más",
"and-n-other-card_plural": "y otras __count__ tarjetas",
"apply": "Aplicar",
"app-is-offline": "Cargando, espera por favor. Refrescar esta página causará pérdida de datos. Si la carga no funciona, por favor comprueba que el servidor no se ha parado.",
"archive": "Archivar",
"archive-all": "Archivar todo",
"archive-board": "Archivar este tablero",
"archive-card": "Archivar esta tarjeta",
"archive-list": "Archivar esta lista",
"archive-swimlane": "Archivar este carril",
"archive-selection": "Archivar esta selección",
"archiveBoardPopup-title": "¿Archivar este tablero?",
"archived-items": "Archivo",
"archived-boards": "Tableros en el Archivo",
"restore-board": "Restaurar el tablero",
"no-archived-boards": "No hay Tableros en el Archivo",
"archives": "Archivo",
"template": "Plantilla",
"templates": "Plantillas",
"assign-member": "Asignar miembros",
"attached": "adjuntado",
"attachment": "Adjunto",
"attachment-delete-pop": "La eliminación de un fichero adjunto es permanente. Esta acción no puede deshacerse.",
"attachmentDeletePopup-title": "¿Eliminar el adjunto?",
"attachments": "Adjuntos",
"auto-watch": "Suscribirse automáticamente a los tableros cuando son creados",
"avatar-too-big": "El avatar es muy grande (70KB máx.)",
"back": "Atrás",
"board-change-color": "Cambiar el color",
"board-nb-stars": "%s destacados",
"board-not-found": "Tablero no encontrado",
"board-private-info": "Este tablero será <strong>privado</strong>.",
"board-public-info": "Este tablero será <strong>público</strong>.",
"boardChangeColorPopup-title": "Cambiar el fondo del tablero",
"boardChangeTitlePopup-title": "Renombrar el tablero",
"boardChangeVisibilityPopup-title": "Cambiar visibilidad",
"boardChangeWatchPopup-title": "Cambiar vigilancia",
"boardMenuPopup-title": "Preferencias del tablero",
"boardChangeViewPopup-title": "Vista del tablero",
"boards": "Tableros",
"board-view": "Vista del tablero",
"board-view-cal": "Calendario",
"board-view-swimlanes": "Carriles",
"board-view-collapse": "Contraer",
"board-view-lists": "Listas",
"bucket-example": "Como “Cosas por hacer” por ejemplo",
"cancel": "Cancelar",
"card-archived": "Se archivó esta tarjeta",
"board-archived": "Se archivó este tablero",
"card-comments-title": "Esta tarjeta tiene %s comentarios.",
"card-delete-notice": "la eliminación es permanente. Perderás todas las acciones asociadas a esta tarjeta.",
"card-delete-pop": "Se eliminarán todas las acciones del historial de actividades y no se podrá volver a abrir la tarjeta. Esta acción no puede deshacerse.",
"card-delete-suggest-archive": "Puedes mover una tarjeta al Archivo para quitarla del tablero y preservar la actividad.",
"card-due": "Vence",
"card-due-on": "Vence el",
"card-spent": "Tiempo consumido",
"card-edit-attachments": "Editar los adjuntos",
"card-edit-custom-fields": "Editar los campos personalizados",
"card-edit-labels": "Editar las etiquetas",
"card-edit-members": "Editar los miembros",
"card-labels-title": "Cambia las etiquetas de la tarjeta",
"card-members-title": "Añadir o eliminar miembros del tablero desde la tarjeta.",
"card-start": "Comienza",
"card-start-on": "Comienza el",
"cardAttachmentsPopup-title": "Adjuntar desde",
"cardCustomField-datePopup-title": "Cambiar la fecha",
"cardCustomFieldsPopup-title": "Editar los campos personalizados",
"cardStartVotingPopup-title": "Comience a votar",
"positiveVoteMembersPopup-title": "Favorables",
"negativeVoteMembersPopup-title": "Contrarios",
"card-edit-voting": "Editar votación",
"editVoteEndDatePopup-title": "Cambie fecha de termino del voto",
"allowNonBoardMembers": "Permitir todos los usuarios autentificados",
"vote-question": "Pregunta de votación",
"vote-public": "Mostrar quien voto que",
"vote-for-it": "por esto",
"vote-against": "contrarios",
"deleteVotePopup-title": "¿Borrar voto?",
"vote-delete-pop": "El Borrado es permanente. Perderá todas las acciones asociadas con este voto.",
"cardDeletePopup-title": "¿Eliminar la tarjeta?",
"cardDetailsActionsPopup-title": "Acciones de la tarjeta",
"cardLabelsPopup-title": "Etiquetas",
"cardMembersPopup-title": "Miembros",
"cardMorePopup-title": "Más",
"cardTemplatePopup-title": "Crear plantilla",
"cards": "Tarjetas",
"cards-count": "Tarjetas",
"casSignIn": "Iniciar sesión con CAS",
"cardType-card": "Tarjeta",
"cardType-linkedCard": "Tarjeta enlazada",
"cardType-linkedBoard": "Tablero enlazado",
"change": "Cambiar",
"change-avatar": "Cambiar el avatar",
"change-password": "Cambiar la contraseña",
"change-permissions": "Cambiar los permisos",
"change-settings": "Cambiar las preferencias",
"changeAvatarPopup-title": "Cambiar el avatar",
"changeLanguagePopup-title": "Cambiar el idioma",
"changePasswordPopup-title": "Cambiar la contraseña",
"changePermissionsPopup-title": "Cambiar los permisos",
"changeSettingsPopup-title": "Cambiar las preferencias",
"subtasks": "Subtareas",
"checklists": "Lista de verificación",
"click-to-star": "Haz clic para destacar este tablero.",
"click-to-unstar": "Haz clic para dejar de destacar este tablero.",
"clipboard": "el portapapeles o con arrastrar y soltar",
"close": "Cerrar",
"close-board": "Cerrar el tablero",
"close-board-pop": "Podrás restaurar el tablero haciendo clic en el botón \"Archivo\" del encabezado de la pantalla inicial.",
"color-black": "negra",
"color-blue": "azul",
"color-crimson": "carmesí",
"color-darkgreen": "verde oscuro",
"color-gold": "oro",
"color-gray": "gris",
"color-green": "verde",
"color-indigo": "añil",
"color-lime": "lima",
"color-magenta": "magenta",
"color-mistyrose": "rosa claro",
"color-navy": "azul marino",
"color-orange": "naranja",
"color-paleturquoise": "turquesa",
"color-peachpuff": "melocotón",
"color-pink": "rosa",
"color-plum": "púrpura",
"color-purple": "violeta",
"color-red": "roja",
"color-saddlebrown": "marrón",
"color-silver": "plata",
"color-sky": "celeste",
"color-slateblue": "azul",
"color-white": "blanco",
"color-yellow": "amarilla",
"unset-color": "Desmarcar",
"comment": "Comentar",
"comment-placeholder": "Escribir comentario",
"comment-only": "Sólo comentarios",
"comment-only-desc": "Solo puedes comentar en las tarjetas.",
"no-comments": "No hay comentarios",
"no-comments-desc": "No se pueden mostrar comentarios ni actividades.",
"worker": "Trabajador",
"worker-desc": "Solo puede mover tarjetas, asignarse a la tarjeta y comentar.",
"computer": "el ordenador",
"confirm-subtask-delete-dialog": "¿Seguro que quieres eliminar la subtarea?",
"confirm-checklist-delete-dialog": "¿Seguro que quieres eliminar la lista de verificación?",
"copy-card-link-to-clipboard": "Copiar el enlace de la tarjeta al portapapeles",
"linkCardPopup-title": "Enlazar tarjeta",
"searchElementPopup-title": "Buscar",
"copyCardPopup-title": "Copiar la tarjeta",
"copyChecklistToManyCardsPopup-title": "Copiar la plantilla de la lista de verificación en varias tarjetas",
"copyChecklistToManyCardsPopup-instructions": "Títulos y descripciones de las tarjetas de destino en formato JSON",
"copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Título de la primera tarjeta\", \"description\":\"Descripción de la primera tarjeta\"}, {\"title\":\"Título de la segunda tarjeta\",\"description\":\"Descripción de la segunda tarjeta\"},{\"title\":\"Título de la última tarjeta\",\"description\":\"Descripción de la última tarjeta\"} ]",
"create": "Crear",
"createBoardPopup-title": "Crear tablero",
"chooseBoardSourcePopup-title": "Importar un tablero",
"createLabelPopup-title": "Crear etiqueta",
"createCustomField": "Crear un campo",
"createCustomFieldPopup-title": "Crear un campo",
"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)",
"custom-field-dropdown-options": "Opciones de la lista",
"custom-field-dropdown-options-placeholder": "Pulsa Intro para añadir más opciones",
"custom-field-dropdown-unknown": "(desconocido)",
"custom-field-number": "Número",
"custom-field-text": "Texto",
"custom-fields": "Campos personalizados",
"date": "Fecha",
"decline": "Declinar",
"default-avatar": "Avatar por defecto",
"delete": "Eliminar",
"deleteCustomFieldPopup-title": "¿Eliminar el campo personalizado?",
"deleteLabelPopup-title": "¿Eliminar la etiqueta?",
"description": "Descripción",
"disambiguateMultiLabelPopup-title": "Desambiguar la acción de etiqueta",
"disambiguateMultiMemberPopup-title": "Desambiguar la acción de miembro",
"discard": "Descartarla",
"done": "Hecho",
"download": "Descargar",
"edit": "Editar",
"edit-avatar": "Cambiar el avatar",
"edit-profile": "Editar el perfil",
"edit-wip-limit": "Cambiar el límite del trabajo en proceso",
"soft-wip-limit": "Límite del trabajo en proceso flexible",
"editCardStartDatePopup-title": "Cambiar la fecha de comienzo",
"editCardDueDatePopup-title": "Cambiar la fecha de vencimiento",
"editCustomFieldPopup-title": "Editar el campo",
"editCardSpentTimePopup-title": "Cambiar el tiempo consumido",
"editLabelPopup-title": "Cambiar la etiqueta",
"editNotificationPopup-title": "Editar las notificaciones",
"editProfilePopup-title": "Editar el perfil",
"email": "Correo electrónico",
"email-enrollAccount-subject": "Cuenta creada en __siteName__",
"email-enrollAccount-text": "Hola __user__,\n\nPara empezar a utilizar el servicio, simplemente haz clic en el siguiente enlace.\n\n__url__\n\nGracias.",
"email-fail": "Error al enviar el correo",
"email-fail-text": "Error al intentar enviar el correo",
"email-invalid": "Correo no válido",
"email-invite": "Invitar vía correo electrónico",
"email-invite-subject": "__inviter__ ha enviado una invitación",
"email-invite-text": "Estimado __user__,\n\n__inviter__ te invita a unirte al tablero '__board__' para colaborar.\n\nPor favor, haz clic en el siguiente enlace:\n\n__url__\n\nGracias.",
"email-resetPassword-subject": "Restablecer tu contraseña en __siteName__",
"email-resetPassword-text": "Hola __user__,\n\nPara restablecer tu contraseña, haz clic en el siguiente enlace.\n\n__url__\n\nGracias.",
"email-sent": "Correo enviado",
"email-verifyEmail-subject": "Verifica tu dirección de correo en __siteName__",
"email-verifyEmail-text": "Hola __user__,\n\nPara verificar tu cuenta de correo electrónico, haz clic en el siguiente enlace.\n\n__url__\n\nGracias.",
"enable-wip-limit": "Habilitar el límite del trabajo en proceso",
"error-board-doesNotExist": "El tablero no existe",
"error-board-notAdmin": "Es necesario ser administrador de este tablero para hacer eso",
"error-board-notAMember": "Es necesario ser miembro de este tablero para hacer eso",
"error-json-malformed": "El texto no es un JSON válido",
"error-json-schema": "Sus datos JSON no incluyen la información apropiada en el formato correcto",
"error-csv-schema": "Su CSV(Valores separados por coma)/TSV(Valores separados por tab) no incluyen la información apropiada en el formato correcto",
"error-list-doesNotExist": "La lista no existe",
"error-user-doesNotExist": "El usuario no existe",
"error-user-notAllowSelf": "No puedes invitarte a ti mismo",
"error-user-notCreated": "El usuario no ha sido creado",
"error-username-taken": "Este nombre de usuario ya está en uso",
"error-email-taken": "Esta dirección de correo ya está en uso",
"export-board": "Exportar el tablero",
"export-board-json": "Exportar tablero a JSON",
"export-board-csv": "Exportar tablero a CSV",
"export-board-tsv": "Exportar tablero a TSV",
"export-board-html": "Export board to HTML",
"exportBoardPopup-title": "Exportar el tablero",
"sort": "Ordenar",
"sort-desc": "Click para ordenar la lista",
"list-sort-by": "Ordenar la lista por:",
"list-label-modifiedAt": "Hora de último acceso",
"list-label-title": "Nombre de la lista",
"list-label-sort": "Tu orden manual",
"list-label-short-modifiedAt": "(L)",
"list-label-short-title": "(N)",
"list-label-short-sort": "(M)",
"filter": "Filtrar",
"filter-cards": "Filtrar tarjetas o listas",
"list-filter-label": "Filtrar listas por título",
"filter-clear": "Limpiar el filtro",
"filter-no-label": "Sin etiqueta",
"filter-no-member": "Sin miembro",
"filter-no-assignee": "No asignado",
"filter-no-custom-fields": "Sin campos personalizados",
"filter-show-archive": "Mostrar las listas archivadas",
"filter-hide-empty": "Ocultar las listas vacías",
"filter-on": "Filtrado activado",
"filter-on-desc": "Estás filtrando tarjetas en este tablero. Haz clic aquí para editar el filtro.",
"filter-to-selection": "Filtrar la selección",
"advanced-filter-label": "Filtrado avanzado",
"advanced-filter-description": "El filtrado avanzado permite escribir una cadena que contiene los siguientes operadores: == != <= >= && || ( ) Se utiliza un espacio como separador entre los operadores. Se pueden filtrar todos los campos personalizados escribiendo sus nombres y valores. Por ejemplo: Campo1 == Valor1. Nota: Si los campos o valores contienen espacios, deben encapsularse entre comillas simples. Por ejemplo: 'Campo 1' == 'Valor 1'. Para omitir los caracteres de control único (' \\/), se usa \\. Por ejemplo: Campo1 = I\\'m. También se pueden combinar múltiples condiciones. Por ejemplo: C1 == V1 || C1 == V2. Normalmente todos los operadores se interpretan de izquierda a derecha. Se puede cambiar el orden colocando paréntesis. Por ejemplo: C1 == V1 && ( C2 == V2 || C2 == V3 ). También se puede buscar en campos de texto usando expresiones regulares: C1 == /Tes.*/i",
"fullname": "Nombre completo",
"header-logo-title": "Volver a tu página de tableros",
"hide-system-messages": "Ocultar las notificaciones de actividad",
"headerBarCreateBoardPopup-title": "Crear tablero",
"home": "Inicio",
"import": "Importar",
"link": "Enlace",
"import-board": "importar un tablero",
"import-board-c": "Importar un tablero",
"import-board-title-trello": "Importar un tablero desde Trello",
"import-board-title-wekan": "Importar tablero desde una exportación previa",
"import-board-title-csv": "Importar tablero desde CSV/TSV",
"from-trello": "Desde Trello",
"from-wekan": "Desde exportación previa",
"from-csv": "Desde CSV/TSV",
"import-board-instruction-trello": "En tu tablero de Trello, ve a 'Menú', luego 'Más' > 'Imprimir y exportar' > 'Exportar JSON', y copia el texto resultante.",
"import-board-instruction-csv": "Pegue en sus Valores separados por coma(CSV)/Valores separados por tab(TSV).",
"import-board-instruction-wekan": "En tu tablero, vete a 'Menú', luego 'Exportar tablero', y copia el texto en el archivo descargado.",
"import-board-instruction-about-errors": "Aunque obtengas errores cuando importes el tablero, a veces la importación funciona igualmente, y el tablero se encontrará en la página de tableros.",
"import-json-placeholder": "Pega tus datos JSON válidos aquí",
"import-csv-placeholder": "Paste your valid CSV/TSV data here",
"import-map-members": "Mapa de miembros",
"import-members-map": "Tu tablero importado tiene algunos miembros. Por favor, mapea los miembros que quieres importar con tus usuarios.",
"import-show-user-mapping": "Revisión de la asignación de miembros",
"import-user-select": "Selecciona el miembro existe que quieres usar como este miembro.",
"importMapMembersAddPopup-title": "Seleccionar miembro",
"info": "Versión",
"initials": "Iniciales",
"invalid-date": "Fecha no válida",
"invalid-time": "Tiempo no válido",
"invalid-user": "Usuario no válido",
"joined": "se ha unido",
"just-invited": "Has sido invitado a este tablero",
"keyboard-shortcuts": "Atajos de teclado",
"label-create": "Crear una etiqueta",
"label-default": "etiqueta %s (por defecto)",
"label-delete-pop": "Se eliminará esta etiqueta de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.",
"labels": "Etiquetas",
"language": "Cambiar el idioma",
"last-admin-desc": "No puedes cambiar roles porque debe haber al menos un administrador.",
"leave-board": "Abandonar el tablero",
"leave-board-pop": "¿Seguro que quieres abandonar __boardTitle__? Serás desvinculado de todas las tarjetas en este tablero.",
"leaveBoardPopup-title": "¿Abandonar el tablero?",
"link-card": "Enlazar a esta tarjeta",
"list-archive-cards": "Archivar todas las tarjetas de esta lista",
"list-archive-cards-pop": "Esto eliminará del tablero todas las tarjetas en esta lista. Para ver las tarjetas en el Archivo y recuperarlas al tablero haga click en \"Menu\" > \"Archivo\"",
"list-move-cards": "Mover todas las tarjetas de esta lista",
"list-select-cards": "Seleccionar todas las tarjetas de esta lista",
"set-color-list": "Cambiar el color",
"listActionPopup-title": "Acciones de la lista",
"swimlaneActionPopup-title": "Acciones del carril de flujo",
"swimlaneAddPopup-title": "Añadir un carril de flujo debajo",
"listImportCardPopup-title": "Importar una tarjeta de Trello",
"listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
"listMorePopup-title": "Más",
"link-list": "Enlazar a esta lista",
"list-delete-pop": "Todas las acciones serán eliminadas del historial de actividades y no se podrá recuperar la lista. Esta acción no puede deshacerse.",
"list-delete-suggest-archive": "Puedes mover una lista al Archivo para quitarla del tablero y preservar la actividad.",
"lists": "Listas",
"swimlanes": "Carriles",
"log-out": "Finalizar la sesión",
"log-in": "Iniciar sesión",
"loginPopup-title": "Iniciar sesión",
"memberMenuPopup-title": "Preferencias de miembro",
"members": "Miembros",
"menu": "Menú",
"move-selection": "Mover la selección",
"moveCardPopup-title": "Mover la tarjeta",
"moveCardToBottom-title": "Mover al final",
"moveCardToTop-title": "Mover al principio",
"moveSelectionPopup-title": "Mover la selección",
"multi-selection": "Selección múltiple",
"multi-selection-on": "Selección múltiple activada",
"muted": "Silenciado",
"muted-info": "No serás notificado de ningún cambio en este tablero",
"my-boards": "Mis tableros",
"name": "Nombre",
"no-archived-cards": "No hay tarjetas archivadas.",
"no-archived-lists": "No hay listas archivadas.",
"no-archived-swimlanes": "No hay carriles archivados.",
"no-results": "Sin resultados",
"normal": "Normal",
"normal-desc": "Puedes ver y editar tarjetas. No puedes cambiar la configuración.",
"not-accepted-yet": "La invitación no ha sido aceptada aún",
"notify-participate": "Recibir actualizaciones de cualquier tarjeta en la que participas como creador o miembro",
"notify-watch": "Recibir actuaizaciones de cualquier tablero, lista o tarjeta que estés vigilando",
"optional": "opcional",
"or": "o",
"page-maybe-private": "Esta página puede ser privada. Es posible que puedas verla al <a href='%s'>iniciar sesión</a>.",
"page-not-found": "Página no encontrada.",
"password": "Contraseña",
"paste-or-dragdrop": "pegar o arrastrar y soltar un fichero de imagen (sólo imagen)",
"participating": "Participando",
"preview": "Previsualizar",
"previewAttachedImagePopup-title": "Previsualizar",
"previewClipboardImagePopup-title": "Previsualizar",
"private": "Privado",
"private-desc": "Este tablero es privado. Sólo las personas añadidas al tablero pueden verlo y editarlo.",
"profile": "Perfil",
"public": "Público",
"public-desc": "Este tablero es público. Es visible para cualquiera a través del enlace, y se mostrará en los buscadores como Google. Sólo las personas añadidas al tablero pueden editarlo.",
"quick-access-description": "Destaca un tablero para añadir un acceso directo en esta barra.",
"remove-cover": "Eliminar portada",
"remove-from-board": "Desvincular del tablero",
"remove-label": "Eliminar la etiqueta",
"listDeletePopup-title": "¿Eliminar la lista?",
"remove-member": "Eliminar miembro",
"remove-member-from-card": "Eliminar de la tarjeta",
"remove-member-pop": "¿Eliminar __name__ (__username__) de __boardTitle__? El miembro será eliminado de todas las tarjetas de este tablero. En ellas se mostrará una notificación.",
"removeMemberPopup-title": "¿Eliminar miembro?",
"rename": "Renombrar",
"rename-board": "Renombrar el tablero",
"restore": "Restaurar",
"save": "Añadir",
"search": "Buscar",
"rules": "Reglas",
"search-cards": "Buscar entre los títulos, las descripciones de las tarjetas/listas y los campos personalizados en este tablero.",
"search-example": "¿Texto a buscar?",
"select-color": "Seleccionar el color",
"set-wip-limit-value": "Cambiar el límite para el número máximo de tareas en esta lista.",
"setWipLimitPopup-title": "Cambiar el límite del trabajo en proceso",
"shortcut-assign-self": "Asignarte a ti mismo a la tarjeta actual",
"shortcut-autocomplete-emoji": "Autocompletar emoji",
"shortcut-autocomplete-members": "Autocompletar miembros",
"shortcut-clear-filters": "Limpiar todos los filtros",
"shortcut-close-dialog": "Cerrar el cuadro de diálogo",
"shortcut-filter-my-cards": "Filtrar mis tarjetas",
"shortcut-show-shortcuts": "Mostrar esta lista de atajos",
"shortcut-toggle-filterbar": "Conmutar la barra lateral del filtro",
"shortcut-toggle-sidebar": "Conmutar la barra lateral del tablero",
"show-cards-minimum-count": "Mostrar recuento de tarjetas si la lista contiene más de",
"sidebar-open": "Abrir la barra lateral",
"sidebar-close": "Cerrar la barra lateral",
"signupPopup-title": "Crear una cuenta",
"star-board-title": "Haz clic para destacar este tablero. Se mostrará en la parte superior de tu lista de tableros.",
"starred-boards": "Tableros destacados",
"starred-boards-description": "Los tableros destacados se mostrarán en la parte superior de tu lista de tableros.",
"subscribe": "Suscribirse",
"team": "Equipo",
"this-board": "este tablero",
"this-card": "esta tarjeta",
"spent-time-hours": "Tiempo consumido (horas)",
"overtime-hours": "Tiempo excesivo (horas)",
"overtime": "Tiempo excesivo",
"has-overtime-cards": "Hay tarjetas con el tiempo excedido",
"has-spenttime-cards": "Se ha excedido el tiempo de las tarjetas",
"time": "Hora",
"title": "Título",
"tracking": "Siguiendo",
"tracking-info": "Serás notificado de cualquier cambio en las tarjetas en las que participas como creador o miembro.",
"type": "Tipo",
"unassign-member": "Desvincular al miembro",
"unsaved-description": "Tienes una descripción por añadir.",
"unwatch": "Dejar de vigilar",
"upload": "Cargar",
"upload-avatar": "Cargar un avatar",
"uploaded-avatar": "Avatar cargado",
"username": "Nombre de usuario",
"view-it": "Verla",
"warn-list-archived": "advertencia: esta tarjeta está en una lista en el Archivo",
"watch": "Vigilar",
"watching": "Vigilando",
"watching-info": "Serás notificado de cualquier cambio en este tablero",
"welcome-board": "Tablero de bienvenida",
"welcome-swimlane": "Hito 1",
"welcome-list1": "Básicos",
"welcome-list2": "Avanzados",
"card-templates-swimlane": "Plantilla de tarjeta",
"list-templates-swimlane": "Listar plantillas",
"board-templates-swimlane": "Plantilla de tablero",
"what-to-do": "¿Qué quieres hacer?",
"wipLimitErrorPopup-title": "El límite del trabajo en proceso no es válido.",
"wipLimitErrorPopup-dialog-pt1": "El número de tareas en esta lista es mayor que el límite del trabajo en proceso que has definido.",
"wipLimitErrorPopup-dialog-pt2": "Por favor, mueve algunas tareas fuera de esta lista, o fija un límite del trabajo en proceso más alto.",
"admin-panel": "Panel del administrador",
"settings": "Preferencias",
"people": "Personas",
"registration": "Registro",
"disable-self-registration": "Deshabilitar autoregistro",
"invite": "Invitar",
"invite-people": "Invitar a personas",
"to-boards": "A el(los) tablero(s)",
"email-addresses": "Direcciones de correo electrónico",
"smtp-host-description": "Dirección del servidor SMTP para gestionar tus correos",
"smtp-port-description": "Puerto usado por el servidor SMTP para mandar correos",
"smtp-tls-description": "Habilitar el soporte TLS para el servidor SMTP",
"smtp-host": "Servidor SMTP",
"smtp-port": "Puerto SMTP",
"smtp-username": "Nombre de usuario",
"smtp-password": "Contraseña",
"smtp-tls": "Soporte TLS",
"send-from": "Desde",
"send-smtp-test": "Enviarte un correo de prueba a ti mismo",
"invitation-code": "Código de Invitación",
"email-invite-register-subject": "__inviter__ te ha enviado una invitación",
"email-invite-register-text": "Querido __user__,\n__inviter__ le invita al tablero kanban para colaborar.\n\nPor favor, siga el siguiente enlace:\n__url__\n\nY tu código de invitación es: __icode__\n\nGracias.",
"email-smtp-test-subject": "Prueba de email SMTP",
"email-smtp-test-text": "El correo se ha enviado correctamente",
"error-invitation-code-not-exist": "El código de invitación no existe",
"error-notAuthorized": "No estás autorizado a ver esta página.",
"webhook-title": "Nombre del Webhook",
"webhook-token": "Token (opcional para la autenticación)",
"outgoing-webhooks": "Webhooks salientes",
"bidirectional-webhooks": "Webhooks de doble sentido",
"outgoingWebhooksPopup-title": "Webhooks salientes",
"boardCardTitlePopup-title": "Filtro de títulos de tarjeta",
"disable-webhook": "Deshabilitar este Webhook",
"global-webhook": "Webhooks globales",
"new-outgoing-webhook": "Nuevo webhook saliente",
"no-name": "(Desconocido)",
"Node_version": "Versión de Node",
"Meteor_version": "Versión de Meteor",
"MongoDB_version": "Versión de MongoDB",
"MongoDB_storage_engine": "Motor de almacenamiento de MongoDB",
"MongoDB_Oplog_enabled": "Oplog de MongoDB habilitado",
"OS_Arch": "Arquitectura del sistema",
"OS_Cpus": "Número de CPUs del sistema",
"OS_Freemem": "Memoria libre del sistema",
"OS_Loadavg": "Carga media del sistema",
"OS_Platform": "Plataforma del sistema",
"OS_Release": "Publicación del sistema",
"OS_Totalmem": "Memoria total del sistema",
"OS_Type": "Tipo de sistema",
"OS_Uptime": "Tiempo activo del sistema",
"days": "días",
"hours": "horas",
"minutes": "minutos",
"seconds": "segundos",
"show-field-on-card": "Mostrar este campo en la tarjeta",
"automatically-field-on-card": "Crear campos automáticamente para todas las tarjetas.",
"showLabel-field-on-card": "Mostrar etiquetas de campos en la minitarjeta.",
"yes": "Sí",
"no": "No",
"accounts": "Cuentas",
"accounts-allowEmailChange": "Permitir cambiar el correo electrónico",
"accounts-allowUserNameChange": "Permitir cambiar el nombre de usuario",
"createdAt": "Fecha de alta",
"verified": "Verificado",
"active": "Activo",
"card-received": "Recibido",
"card-received-on": "Recibido el",
"card-end": "Finalizado",
"card-end-on": "Finalizado el",
"editCardReceivedDatePopup-title": "Cambiar la fecha de recepción",
"editCardEndDatePopup-title": "Cambiar la fecha de finalización",
"setCardColorPopup-title": "Cambiar el color",
"setCardActionsColorPopup-title": "Elegir un color",
"setSwimlaneColorPopup-title": "Elegir un color",
"setListColorPopup-title": "Elegir un color",
"assigned-by": "Asignado por",
"requested-by": "Solicitado por",
"board-delete-notice": "Se eliminarán todas las listas, tarjetas y acciones asociadas a este tablero. Esta acción no puede deshacerse.",
"delete-board-confirm-popup": "Se eliminarán todas las listas, tarjetas, etiquetas y actividades, y no podrás recuperar los contenidos del tablero. Esta acción no puede deshacerse.",
"boardDeletePopup-title": "¿Eliminar el tablero?",
"delete-board": "Eliminar el tablero",
"default-subtasks-board": "Subtareas para el tablero __board__",
"default": "Por defecto",
"queue": "Cola",
"subtask-settings": "Preferencias de las subtareas",
"card-settings": "Preferencias de la tarjeta",
"boardSubtaskSettingsPopup-title": "Preferencias de las subtareas del tablero",
"boardCardSettingsPopup-title": "Preferencias de la tarjeta",
"deposit-subtasks-board": "Depositar subtareas en este tablero:",
"deposit-subtasks-list": "Lista de destino para subtareas depositadas aquí:",
"show-parent-in-minicard": "Mostrar el padre en una minitarjeta:",
"prefix-with-full-path": "Prefijo con ruta completa",
"prefix-with-parent": "Prefijo con el padre",
"subtext-with-full-path": "Subtexto con ruta completa",
"subtext-with-parent": "Subtexto con el padre",
"change-card-parent": "Cambiar la tarjeta padre",
"parent-card": "Tarjeta padre",
"source-board": "Tablero de origen",
"no-parent": "No mostrar la tarjeta padre",
"activity-added-label": "añadida etiqueta %s a %s",
"activity-removed-label": "eliminada etiqueta '%s' desde %s",
"activity-delete-attach": "eliminado un adjunto desde %s",
"activity-added-label-card": "añadida etiqueta '%s'",
"activity-removed-label-card": "eliminada etiqueta '%s'",
"activity-delete-attach-card": "eliminado un adjunto",
"activity-set-customfield": "Cambiar el campo personalizado '%s' a '%s' en %s",
"activity-unset-customfield": "Desmarcar el campo personalizado '%s' en %s",
"r-rule": "Regla",
"r-add-trigger": "Añadir disparador",
"r-add-action": "Añadir acción",
"r-board-rules": "Reglas del tablero",
"r-add-rule": "Añadir regla",
"r-view-rule": "Ver regla",
"r-delete-rule": "Eliminar regla",
"r-new-rule-name": "Nueva título de regla",
"r-no-rules": "No hay reglas",
"r-when-a-card": "Cuando una tarjeta",
"r-is": "es",
"r-is-moved": "es movida",
"r-added-to": "Added to",
"r-removed-from": "eliminado de",
"r-the-board": "el tablero",
"r-list": "la lista",
"set-filter": "Filtrar",
"r-moved-to": "Movido a",
"r-moved-from": "Movido desde",
"r-archived": "Se archivó",
"r-unarchived": "Restaurado del archivo",
"r-a-card": "una tarjeta",
"r-when-a-label-is": "Cuando una etiqueta es",
"r-when-the-label": "Cuando la etiqueta es",
"r-list-name": "Nombre de lista",
"r-when-a-member": "Cuando un miembro es",
"r-when-the-member": "Cuando el miembro",
"r-name": "nombre",
"r-when-a-attach": "Cuando un adjunto",
"r-when-a-checklist": "Cuando una lista de verificación es",
"r-when-the-checklist": "Cuando la lista de verificación",
"r-completed": "Completada",
"r-made-incomplete": "Hecha incompleta",
"r-when-a-item": "Cuando un elemento de la lista de verificación es",
"r-when-the-item": "Cuando el elemento de la lista de verificación es",
"r-checked": "Marcado",
"r-unchecked": "Desmarcado",
"r-move-card-to": "Mover la tarjeta",
"r-top-of": "Arriba de",
"r-bottom-of": "Abajo de",
"r-its-list": "su lista",
"r-archive": "Archivar",
"r-unarchive": "Restaurar del Archivo",
"r-card": "la tarjeta",
"r-add": "Añadir",
"r-remove": "Eliminar",
"r-label": "etiqueta",
"r-member": "miembro",
"r-remove-all": "Eliminar todos los miembros de la tarjeta",
"r-set-color": "Cambiar el color a",
"r-checklist": "lista de verificación",
"r-check-all": "Marcar todo",
"r-uncheck-all": "Desmarcar todo",
"r-items-check": "elementos de la lista de verificación",
"r-check": "Marcar",
"r-uncheck": "Desmarcar",
"r-item": "elemento",
"r-of-checklist": "de la lista de verificación",
"r-send-email": "Enviar un email",
"r-to": "a",
"r-of": "of",
"r-subject": "asunto",
"r-rule-details": "Detalle de la regla",
"r-d-move-to-top-gen": "Mover la tarjeta al inicio de su lista",
"r-d-move-to-top-spec": "Mover la tarjeta al inicio de la lista",
"r-d-move-to-bottom-gen": "Mover la tarjeta al final de su lista",
"r-d-move-to-bottom-spec": "Mover la tarjeta al final de la lista",
"r-d-send-email": "Enviar email",
"r-d-send-email-to": "a",
"r-d-send-email-subject": "asunto",
"r-d-send-email-message": "mensaje",
"r-d-archive": "Archivar la tarjeta",
"r-d-unarchive": "Restaurar tarjeta del Archivo",
"r-d-add-label": "Añadir etiqueta",
"r-d-remove-label": "Eliminar etiqueta",
"r-create-card": "Crear una nueva tarjeta",
"r-in-list": "en la lista",
"r-in-swimlane": "en el carril",
"r-d-add-member": "Añadir miembro",
"r-d-remove-member": "Eliminar miembro",
"r-d-remove-all-member": "Eliminar todos los miembros",
"r-d-check-all": "Marcar todos los elementos de una lista",
"r-d-uncheck-all": "Desmarcar todos los elementos de una lista",
"r-d-check-one": "Marcar elemento",
"r-d-uncheck-one": "Desmarcar elemento",
"r-d-check-of-list": "de la lista de verificación",
"r-d-add-checklist": "Añadir una lista de verificación",
"r-d-remove-checklist": "Eliminar lista de verificación",
"r-by": "por",
"r-add-checklist": "Añadir una lista de verificación",
"r-with-items": "con items",
"r-items-list": "item1,item2,item3",
"r-add-swimlane": "Agregar el carril",
"r-swimlane-name": "nombre del carril",
"r-board-note": "Nota: deje un campo vacío para que coincida con todos los valores posibles",
"r-checklist-note": "Nota: los ítems de la lista tienen que escribirse como valores separados por coma.",
"r-when-a-card-is-moved": "Cuando una tarjeta es movida a otra lista",
"r-set": "Cambiar",
"r-update": "Actualizar",
"r-datefield": "campo de fecha",
"r-df-start-at": "comienza",
"r-df-due-at": "vencimiento",
"r-df-end-at": "finalizado",
"r-df-received-at": "recibido",
"r-to-current-datetime": "a la fecha/hora actual",
"r-remove-value-from": "Eliminar el valor de",
"ldap": "LDAP",
"oauth2": "OAuth2",
"cas": "CAS",
"authentication-method": "Método de autenticación",
"authentication-type": "Tipo de autenticación",
"custom-product-name": "Nombre de producto personalizado",
"layout": "Diseño",
"hide-logo": "Ocultar el logo",
"add-custom-html-after-body-start": "Añade HTML personalizado después de <body>",
"add-custom-html-before-body-end": "Añade HTML personalizado después de </body>",
"error-undefined": "Algo no está bien",
"error-ldap-login": "Ocurrió un error al intentar acceder",
"display-authentication-method": "Mostrar el método de autenticación",
"default-authentication-method": "Método de autenticación por defecto",
"duplicate-board": "Duplicar tablero",
"people-number": "El número de personas es:",
"swimlaneDeletePopup-title": "¿Eliminar el carril de flujo?",
"swimlane-delete-pop": "Todas las acciones serán eliminadas del historial de actividades y no se podrá recuperar el carril de flujo. Esta acción no puede deshacerse.",
"restore-all": "Restaurar todas",
"delete-all": "Borrar todas",
"loading": "Cargando. Por favor, espere.",
"previous_as": "el último tiempo fue",
"act-a-dueAt": "cambiada la hora de vencimiento a \nCuándo: __timeValue__\nDónde: __card__\n el vencimiento anterior fue __timeOldValue__",
"act-a-endAt": "cambiada la hora de finalización a __timeValue__ Fecha anterior: (__timeOldValue__)",
"act-a-startAt": "cambiada la hora de comienzo a __timeValue__ Fecha anterior: (__timeOldValue__)",
"act-a-receivedAt": "cambiada la fecha de recepción a __timeValue__ Fecha anterior: (__timeOldValue__)",
"a-dueAt": "cambiada la hora de vencimiento a",
"a-endAt": "cambiada la hora de finalización a",
"a-startAt": "cambiada la hora de comienzo a",
"a-receivedAt": "cambiada la hora de recepción a",
"almostdue": "está próxima la hora de vencimiento actual %s",
"pastdue": "se sobrepasó la hora de vencimiento actual%s",
"duenow": "la hora de vencimiento actual %s es hoy",
"act-newDue": "__list__/__card__ tiene una 1ra notificación de vencimiento [__board__]",
"act-withDue": "__list__/__card__ notificaciones de vencimiento [__board__]",
"act-almostdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ está próximo",
"act-pastdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ se sobrepasó",
"act-duenow": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ es ahora",
"act-atUserComment": "Se te mencionó en [__board__] __list__/__card__",
"delete-user-confirm-popup": "¿Seguro que quieres eliminar esta cuenta? Esta acción no puede deshacerse.",
"accounts-allowUserDelete": "Permitir a los usuarios eliminar su cuenta",
"hide-minicard-label-text": "Ocultar el texto de la etiqueta de la minitarjeta",
"show-desktop-drag-handles": "Mostrar los controles de arrastre del escritorio",
"assignee": "Asignado",
"cardAssigneesPopup-title": "Asignado",
"addmore-detail": "Añadir una descripción detallada",
"show-on-card": "Mostrar en la tarjeta",
"new": "Nuevo",
"editUserPopup-title": "Editar el usuario",
"newUserPopup-title": "Nuevo usuario",
"notifications": "Notificaciones",
"view-all": "Ver todo",
"filter-by-unread": "Filtrar por no leído",
"mark-all-as-read": "Marcar todo como leido",
"remove-all-read": "Remove all read",
"allow-rename": "Permitir renombrar",
"allowRenamePopup-title": "Permitir renombrar",
"start-day-of-week": "Set day of the week start",
"monday": "Lunes",
"tuesday": "Martes",
"wednesday": "Miércoles",
"thursday": "Jueves",
"friday": "Viernes",
"saturday": "Sábado",
"sunday": "Domingo",
"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"
}
Loaded 100 of 202 files, more files were not shown because too many files have changed in this diff. Show more