Compare commits

...
415 Commits
Author SHA1 Message Date
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
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
Bryan Mutai 1742bcd9b1 add: import board/cards/lists using CSV/TSV 2020-05-07 01:31:59 +03: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
Lauri Ojansivu 4bc6ad3b4c v3.95 2020-04-12 18:07:38 +03:00
Lauri Ojansivu 7f558fede0 Update translations. 2020-04-12 17:45:33 +03:00
Lauri Ojansivu 544bc1d6ad Merge branch 'master' of https://github.com/NicoP-S/wekan 2020-04-12 16:58:08 +03:00
Lauri Ojansivu 89d25f52fa Update ChangeLog. 2020-04-12 16:55:26 +03:00
Lauri Ojansivu 2fd71d5be8 Merge pull request #3009 from juniormendonca/juniormendonca/gitpod-setup
Add gitpod config
2020-04-12 16:53:32 +03:00
Lauri Ojansivu dc643f3d43 Merge pull request #3010 from jtbairdsr/master
fix error in notifications cleanup cron
2020-04-12 16:50:19 +03:00
Lauri Ojansivu 4469b12203 Merge pull request #3012 from boeserwolf/master
Fix styling issue in notifications drawer
2020-04-12 16:42:09 +03:00
boeserwolf 9cacaafb8f Fix styling issue in notifications drawer
The header of the notification drawer had a margin to the
right side in Google Chrome caused by overflow: scroll on
the #notifications-drawer element.
2020-04-12 11:57:22 +02:00
Jonathan Baird 269382869e fix error in notifications cleanup cron 2020-04-11 17:28:15 -06:00
Júnior Mendonça 8648e32108 Add gitpod config
this commit adds support for Gitpod.io, a free automated
dev environment that makes contributing and generally working on GitHub
projects much easier. It allows anyone to start a ready-to-code dev
environment for any branch, issue and pull request with a single click.
2020-04-11 23:27:59 +00:00
Nico edf52bc438 Public boards overview 2020-04-12 00:56:35 +02:00
Lauri Ojansivu 58ce859368 v3.94 2020-04-12 00:01:07 +03:00
Lauri Ojansivu e23aacedb3 Update translations. 2020-04-11 22:38:24 +03:00
Lauri Ojansivu 7f02990626 Update ChangeLog. 2020-04-11 22:25:25 +03:00
Lauri Ojansivu c86155f1aa Update ChangeLog. 2020-04-10 12:22:47 +03:00
Lauri Ojansivu 3fae5355d4 Add robots.txt disallow all.
Thanks to xet7 !

Related #3001
2020-04-10 12:19:18 +03:00
Lauri Ojansivu 20e88159ae Update translations. 2020-04-10 12:14:58 +03:00
Lauri Ojansivu a81921382a Merge pull request #3006 from NicoP-S/master
Public vote
2020-04-10 12:05:10 +03:00
Nico 8f28a409c7 Public vote 2020-04-10 01:46:36 +02:00
Lauri Ojansivu 7566f05b97 v3.93 2020-04-09 23:00:00 +03:00
Lauri Ojansivu 268d7fcb32 Upgrade Node to 12.16.2, part 2.
Thanks to Node developers and xet7 !
2020-04-09 22:53:04 +03:00
Lauri Ojansivu 70edd44a01 Update ChangeLog. 2020-04-09 22:50:00 +03:00
Lauri Ojansivu 6db717b9b3 Upgrade to Node v12.16.2
Thanks to Node developers and xet7 !
2020-04-09 22:47:11 +03:00
Lauri Ojansivu f389fd95fb Update translations. 2020-04-09 22:37:36 +03:00
Lauri Ojansivu 363bcab98b Update ChangeLog. 2020-04-09 22:30:31 +03:00
Lauri Ojansivu c4a7e53661 Merge pull request #3005 from jtbairdsr/master
fix bug that prevents editing or deleting comments
2020-04-09 22:30:02 +03:00
Lauri Ojansivu c6afe8d007 Merge pull request #3004 from slvrpdr/master
When adding a user to a board that has subtasks, also add user to the subtask board
2020-04-09 22:26:34 +03:00
Lauri Ojansivu 93eccffd44 Merge pull request #3000 from NicoP-S/master
Trello vote import & hide export button if with_api is disabled
2020-04-09 22:21:43 +03:00
Jonathan Baird fe66169415 fix bug that prevents editing or deleting comments 2020-04-09 12:46:24 -06:00
Pedro Sousa 6ced47aa8c When adding a user to a board that has subtasks, also add user to the subtask board 2020-04-09 19:10:50 +01:00
Nico e661d03e8d Add vote import from Trello 2020-04-09 02:00:15 +02:00
Nico f09219cbfd Remove export button if WITH_API is not enabled
#2938
https://github.com/wekan/wekan/issues/2938#issuecomment-589782402
2020-04-09 01:55:01 +02:00
NicoP-S 7c25542976 Merge pull request #5 from wekan/master
Update
2020-04-08 23:22:49 +02:00
Lauri Ojansivu 121686a928 v3.92 2020-04-08 23:51:38 +03:00
Lauri Ojansivu 3563e0145d Update translations. 2020-04-08 23:41:13 +03:00
Lauri Ojansivu c630714245 Update ChangeLog. 2020-04-08 23:23:45 +03:00
Lauri Ojansivu 5ebb47cb0e Add setting default NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2
to all Wekan platforms https://github.com/wekan/wekan/pull/2998

Thanks to xet7 !
2020-04-08 23:16:48 +03:00
Lauri Ojansivu 115701fa65 Merge branch 'jtbairdsr-master' 2020-04-08 22:29:15 +03:00
Jonathan Baird a182dde11f add a "remove all read" button to notification menu 2020-04-08 13:14:29 -06:00
Jonathan Baird 1e20e2601f add a scheduled notification cleanup job 2020-04-08 11:54:00 -06:00
Jonathan Baird 4d066b1f30 stop notifying users about their own behavior 2020-04-08 11:54:00 -06:00
Lauri Ojansivu 7856692946 v3.91 2020-04-08 20:37:43 +03:00
Lauri Ojansivu 7e7c8fd001 Update translations. 2020-04-08 20:29:31 +03:00
Lauri Ojansivu feb602ecd4 Update translations. 2020-04-08 19:59:23 +03:00
Lauri Ojansivu 6ed26e373a Update ChangeLog. 2020-04-08 19:54:12 +03:00
Lauri Ojansivu 4fba0fb1a1 Clarify translation. 2020-04-08 19:43:25 +03:00
Lauri Ojansivu 968ae122b0 Fix typos. 2020-04-08 19:38:30 +03:00
Lauri Ojansivu 06c32cb497 Update translations. 2020-04-08 19:37:41 +03:00
Lauri Ojansivu d2a4925cae Merge branch 'NicoP-S-master' 2020-04-08 19:17:00 +03:00
Lauri Ojansivu 9c97c301bd Merge branch 'master' of https://github.com/NicoP-S/wekan into NicoP-S-master 2020-04-08 19:15:05 +03:00
Lauri Ojansivu d4a91d30ff Update ChangeLog. 2020-04-08 19:14:13 +03:00
Lauri Ojansivu 6a56a495ad Update translations. 2020-04-08 18:47:17 +03:00
Lauri Ojansivu 3330aff12c Merge branch 'daniel-eder-feature/filter-assignee' 2020-04-08 18:39:32 +03:00
Lauri Ojansivu 43bdd56a41 Merge branch 'feature/filter-assignee' of https://github.com/daniel-eder/wekan into daniel-eder-feature/filter-assignee 2020-04-08 18:39:09 +03:00
Lauri Ojansivu a602f4b9ec Update ChangeLog. 2020-04-08 18:38:02 +03:00
Lauri Ojansivu 053e47173f Merge pull request #2996 from ckavili/master
Route template added to helm chart
2020-04-08 18:30:51 +03:00
Daniel Eder fe285c62e1 Add filter option for assignee
Works exactly like member
2020-04-08 16:59:59 +03:00
Cansu Kavili a3d44b0774 README updated 2020-04-08 14:38:55 +02:00
Cansu Kavili 0e79bd25ed values file updated 2020-04-08 14:33:29 +02:00
Cansu Kavili 71f7a680b6 Openshift Route def added 2020-04-08 13:59:33 +02:00
Nico 411b8d3f56 Merge branch 'vote' 2020-04-07 20:43:55 +02:00
Nico 2bbc312ad0 Voteing feature 2020-04-07 20:43:35 +02:00
NicoP-S 14de17b667 Merge pull request #4 from wekan/master
Update
2020-04-07 20:20:41 +02:00
Lauri Ojansivu 9083c341c1 Merge pull request #2990 from doggy8088/langzhtwfix
Correct zh-TW language name (the right way)
2020-04-07 13:03:21 +03:00
Will e0930c32a6 Correct zh-TW language name (the right way) 2020-04-07 09:43:23 +00:00
Lauri Ojansivu f438d04c42 v3.90 2020-04-06 16:25:12 +03:00
Lauri Ojansivu d798f6e3ef Update dependencies. 2020-04-06 15:46:59 +03:00
Lauri Ojansivu c96ca4e875 Update translations. 2020-04-06 15:37:09 +03:00
Lauri Ojansivu 8a71fe39c5 Update ChangeLog. 2020-04-06 15:35:19 +03:00
Lauri Ojansivu e4ec5c38b7 Merge branch 'doggy8088-patch-1' 2020-04-06 15:27:53 +03:00
Will 保哥 b849d4c2ba Update layouts.js 2020-04-06 18:16:17 +08:00
Lauri Ojansivu db9c895da8 v3.89 2020-04-05 01:20:09 +03:00
Lauri Ojansivu a938c386cf Update translations. 2020-04-05 01:06:49 +03:00
NicoP-S a376fb2fab Merge pull request #3 from wekan/master
Update
2020-04-04 23:14:32 +02:00
Lauri Ojansivu 06c2ff0904 Update ChangeLog. 2020-04-04 01:43:09 +03:00
Lauri Ojansivu 76e832c3a6 Merge branch 'daniel-eder-feature/subtask-use-parent-swimlane' 2020-04-04 01:36:43 +03:00
Lauri Ojansivu b7fd79aef0 Merge branch 'feature/subtask-use-parent-swimlane' of https://github.com/daniel-eder/wekan into daniel-eder-feature/subtask-use-parent-swimlane 2020-04-04 01:26:49 +03:00
Lauri Ojansivu 28a9ab7131 Update translations. 2020-04-04 01:19:47 +03:00
Lauri Ojansivu 8e0b1cc056 Merge branch 'slvrpdr-master' 2020-04-04 01:04:17 +03:00
Lauri Ojansivu b424b34cab Merge branch 'master' of https://github.com/slvrpdr/wekan into slvrpdr-master 2020-04-04 00:42:14 +03:00
Lauri Ojansivu 45a83da7be Update translations. 2020-04-04 00:37:23 +03:00
Pedro Sousa 6e86292b99 Search also a Card's Custom Fields 2020-04-03 16:23:48 +01:00
NicoP-S e47ff25d45 Merge pull request #2 from wekan/master
Merge Wekan
2020-04-02 20:43:36 +02:00
Daniel Eder 89fe030fa3 subtasks now use swimlane with title matching parent lane title
As discussed in #1953 this is required to handle two-board scenarios correctly.
2020-04-02 07:49:26 +02:00
Lauri Ojansivu 1e0b53eb9b v3.88 2020-04-02 02:34:02 +03:00
Lauri Ojansivu a5f2ae497a Update translations. 2020-04-02 02:05:25 +03:00
Lauri Ojansivu 6441ebec83 Update ChangeLog. 2020-04-02 01:34:27 +03:00
Lauri Ojansivu 7c609d402a Update translations. 2020-04-02 01:29:22 +03:00
Lauri Ojansivu e34f240c35 Merge branch 'jtbairdsr-master' 2020-04-02 01:09:35 +03:00
Jonathan Baird 8ea86ae0b9 fix error from notifications subscriptions 2020-04-01 15:04:12 -06:00
Jonathan Baird f4d0791c6c add correct i18n for notifications menu 2020-04-01 14:50:31 -06:00
Jonathan Baird fcb1def355 Merge upstream/master
* commit '56667112d7fd146a9847794207e9a2224b1115d7':
  Update ChangeLog.
  Minicard labels on the top and title on bottom.
  Update translations.
  Update translations.
  Add translations for Notification drawer like Trello.
  Add translations for Board Settings / Allow Rename Board/Swimlane/Card.
  Update ChangeLog.
  Fix start-wekan.sh MongoDB port to 27017.
  v3.87
  Update translations.
  Update ChangeLog.
  Improvements on card details visualization.
  Update ChangeLog.
  Move "Rules" from "Board View" to "Board Settings".
  Update ChangeLog.
  Fix richer editor submit did not clear edit area.
  Update ChangeLog.
  Fix Browser always reload the whole page when I change one of the card color. Fixed by making label colors and text again editable. Regression from [Wekan v3.86 2)](https://github.com/wekan/wekan/commit/b9099a8b7ea6f63c79bdcbb871cb993b2cb7e325).
2020-04-01 14:47:53 -06:00
Lauri Ojansivu 56667112d7 Update ChangeLog. 2020-04-01 19:42:26 +03:00
Lauri Ojansivu d0003a334f Minicard labels on the top and title on bottom.
Thanks to helioguardabaxo and xet7 !

Closes #2980
2020-04-01 19:39:02 +03:00
Lauri Ojansivu 35427063ff Update translations. 2020-04-01 17:11:02 +03:00
Lauri Ojansivu f5182e6328 Update translations. 2020-04-01 17:04:01 +03:00
Lauri Ojansivu 0c911694c8 Add translations for Notification drawer like Trello. 2020-04-01 16:51:54 +03:00
Lauri Ojansivu 2eed1ca0eb Add translations for Board Settings / Allow Rename Board/Swimlane/Card.
Thanks to xet7 !
2020-04-01 16:48:14 +03:00
Lauri Ojansivu 99e5293ef5 Update ChangeLog. 2020-04-01 00:37:26 +03:00
Lauri Ojansivu c60a092fc0 Fix start-wekan.sh MongoDB port to 27017.
Thanks to Keelan and xet7 !

Related #2979
2020-04-01 00:34:21 +03:00
Lauri Ojansivu b33f44e23f v3.87 2020-04-01 00:12:35 +03:00
Lauri Ojansivu a65e39563b Update translations. 2020-04-01 00:04:18 +03:00
Lauri Ojansivu 703efa5216 Update ChangeLog. 2020-04-01 00:01:15 +03:00
Lauri Ojansivu 6b902bdb36 Improvements on card details visualization.
Thanks to helioguardabaxo and xet7 !

Closes #2974
2020-03-31 23:56:37 +03:00
Lauri Ojansivu d599fc12ff Update ChangeLog. 2020-03-31 23:36:47 +03:00
Lauri Ojansivu e7d2343593 Move "Rules" from "Board View" to "Board Settings".
Thanks to helioguardabaxo and xet7 !

Closes #2973
2020-03-31 23:32:32 +03:00
Lauri Ojansivu c8ec5d5f62 Update ChangeLog. 2020-03-31 23:20:07 +03:00
Lauri Ojansivu 033d671047 Fix richer editor submit did not clear edit area.
Thanks to xet7 !
2020-03-31 23:17:58 +03:00
Lauri Ojansivu 6ef3f8a1c8 Update ChangeLog. 2020-03-31 17:05:02 +03:00
Lauri Ojansivu 3546d7aa02 Fix Browser always reload the whole page when I change one of the card color.
Fixed by making label colors and text again editable.
Regression from [Wekan v3.86 2)](https://github.com/wekan/wekan/commit/b9099a8b7ea6f63c79bdcbb871cb993b2cb7e325).

Thanks to javen9881 and xet7 !

Closes #2971
2020-03-31 16:56:32 +03:00
Jonathan Baird 9819c9f801 add a notification drawer like trello 2020-03-27 11:35:03 -06:00
NicoP-S 7fa9603f9d Merge pull request #1 from wekan/master
update
2020-03-26 21:17:17 +01:00
Daniel Eder c41c1e5293 subtasks now use parent task swimlane by default 2020-03-26 10:57:05 +01:00
Lauri Ojansivu 29d62440a5 Update ChangeLog. 2020-03-25 13:41:33 +02:00
Lauri Ojansivu 5bd0459cc2 Hide duplicate "Hide system messages" at Change Settings/Member Settings, because it's also on card slider.
Thanks to notohiro and xet7 !

Closes #2837
2020-03-25 13:35:54 +02:00
Lauri Ojansivu 8b118c1939 v3.86 2020-03-24 21:01:14 +02:00
Lauri Ojansivu d7a0ab3c15 Update translations. 2020-03-24 20:48:58 +02:00
Lauri Ojansivu 8609e0e82d Update ChangeLog. 2020-03-24 20:45:25 +02:00
Lauri Ojansivu b9099a8b7e 1) Fix Pasting text into a card is adding a line before and after
(and multiplies by pasting more) by changing paste "p" to "br".
2) Fixes to summernote and markdown comment editors, related
   to keeping them open when adding comments, having
   @member mention not close card, and disabling clicking of
   @member mention.

Thanks to xet7 !

Closes #2890
2020-03-24 20:39:49 +02:00
Lauri Ojansivu 87a81789d1 Update ChangeLog. 2020-03-24 11:10:15 +02:00
Lauri Ojansivu 12ab8fac5d Fix Rich editor can not be disabled, regression from changes yesterday at Wekan v3.85.
Thanks to uusijani, vjrj and xet7 !

Closes #2967,
closes #104
2020-03-24 11:04:04 +02:00
Lauri Ojansivu 10b09b2da1 Fix typo. 2020-03-24 02:01:08 +02:00
Lauri Ojansivu 7c5f4a3849 v3.85 2020-03-23 23:01:58 +02:00
Lauri Ojansivu ec71849d84 Update ChangeLog. 2020-03-23 22:49:28 +02:00
Lauri Ojansivu 482682e500 SECURITY VULNERABILITY FIX: Fix XSS bug reported today 4 hours ago by Cyb3rjunky.
Logged in users could run javascript in input fields.
This affects Wekan versions v3.12-v3.84.
In [Wekan v3.12](https://github.com/wekan/wekan/blob/master/CHANGELOG.md#v312-2019-08-09-wekan-release)
there was [changes for XSS filter to allow inserting images, videos etc
on comment WYSIWYG editor](https://github.com/wekan/wekan/pull/2593)
so features related to that are now removed.
After this fix, Javascript in input fields is not executed.

Thanks to Cyb3rjunky and xet7 !
2020-03-23 22:29:20 +02:00
Lauri Ojansivu 3a6303e5c2 Update translations. 2020-03-23 22:25:08 +02:00
Lauri Ojansivu 81635d9217 Revert: Fix Pasting text into a card is adding a line before and after
(and multiplies by pasting more) by disabling WYSIWYG comment editor
for everyone.

Thanks to xet7.

TODO later: Fix wysiwyg editor paste bug.

Opens #2890
2020-03-23 22:23:31 +02:00
Lauri Ojansivu e8f89a8c4c Update translations. 2020-03-20 14:03:11 +02:00
Lauri Ojansivu 393482315f Fix typo. 2020-03-20 14:02:29 +02:00
Lauri Ojansivu cb67af02d7 Update ChangeLog. 2020-03-20 13:57:44 +02:00
Lauri Ojansivu a064b8d643 Fix Pasting text into a card is adding a line before and after
(and multiplies by pasting more) by disabling WYSIWYG comment editor
for everyone.

Closes #2890
2020-03-20 13:52:27 +02:00
Lauri Ojansivu 556621e60e Meteor 1.8 only in use at Sandstorm. 2020-03-17 14:04:50 +02:00
Lauri Ojansivu 0f95a513bf Meteor 1.8 only in use at Sandstorm. 2020-03-17 14:02:43 +02:00
Lauri Ojansivu dce624486f Merge branch 'master' of github.com:wekan/wekan 2020-03-17 13:35:00 +02:00
Lauri Ojansivu 64e76af8de Fix snap syntax. 2020-03-17 13:34:34 +02:00
Lauri Ojansivu acf2ede94e Fix typo. 2020-03-16 10:11:20 -04:00
Lauri Ojansivu b3a7c4dc88 For Sandstorm version of Wekan, leep stylus in 1.1.0, because building v2 takes extra 52 minutes.
Thanks to zodern !
2020-03-16 10:01:28 -04:00
Lauri Ojansivu 8d83c641e3 v3.84 2020-03-16 14:04:18 +02:00
Lauri Ojansivu c1aa0047c6 Update ChangeLog. 2020-03-16 13:55:39 +02:00
Lauri Ojansivu 7af4fe75ba Update ChangeLog. 2020-03-16 13:53:12 +02:00
Lauri Ojansivu ea6baa5c2b Update minimist. 2020-03-16 13:42:29 +02:00
Lauri Ojansivu 8183b7bdaa Update prettier-eslint 2020-03-16 13:33:08 +02:00
Lauri Ojansivu 51514ae22c Update prettier-eslint 2020-03-16 13:29:44 +02:00
Lauri Ojansivu 14b8610837 Update ostrio:cookies 2020-03-16 13:24:39 +02:00
Lauri Ojansivu 369a29707b Update acorn. 2020-03-16 13:22:16 +02:00
Lauri Ojansivu ae240af323 Update translations. 2020-03-16 13:16:47 +02:00
Lauri Ojansivu 2a715ec6c6 Update ChangeLog. 2020-03-15 03:02:08 +02:00
Lauri Ojansivu fca4cdcebf Downgrade stylus to v1.1.0 to speed up building Wekan.
https://github.com/meteor/meteor/issues/10881#issuecomment-599152333

Thanks to zodern !
2020-03-15 02:49:53 +02:00
Lauri Ojansivu 49bbfef3ec Update dependencies. 2020-03-15 02:00:11 +02:00
Lauri Ojansivu f968109e73 Add profiling to build script. 2020-03-15 00:44:03 +02:00
Lauri Ojansivu c0349c1ff9 Update dependencies. 2020-03-15 00:06:09 +02:00
Lauri Ojansivu e009bcf06e Update dependencies. 2020-03-14 17:00:21 +02:00
Lauri Ojansivu 8aae812aa4 Merge pull request #2959 from daMihe/add-current-description-ldap-groups
Added some descriptions for ldap-group authentication
2020-03-14 16:59:37 +02:00
Michael Wodniok 0b8f1cabef Added some descriptions for ldap-group authentication 2020-03-14 15:38:19 +01:00
Lauri Ojansivu e80d3ac79a Update dependencies. 2020-03-14 14:30:59 +02:00
Lauri Ojansivu ed9ba169fe Update dependencies. 2020-03-14 14:08:48 +02:00
Lauri Ojansivu 4720a6fbc3 Fix to be valid JSON. 2020-03-14 14:07:15 +02:00
Lauri Ojansivu ad45f82e27 Update ChangeLog. 2020-03-14 13:21:44 +02:00
Lauri Ojansivu 2174936613 Update acorn. 2020-03-14 13:19:54 +02:00
Lauri Ojansivu a6d7388919 Update ChangeLog. 2020-03-14 13:09:29 +02:00
Lauri Ojansivu f4e2bc0d32 Update dependencies. 2020-03-14 13:06:17 +02:00
Lauri Ojansivu 2f13141907 Update dependencies. 2020-03-14 12:36:12 +02:00
Lauri Ojansivu 365eded5c4 Update ChangeLog. 2020-03-14 12:22:28 +02:00
Lauri Ojansivu ed365c3d41 Update translations. 2020-03-14 12:10:33 +02:00
Lauri Ojansivu e16c65babc Upgrade to Meteor 1.10.1 2020-03-14 12:05:48 +02:00
Lauri Ojansivu 6e0fa78022 Update markdown. 2020-03-14 12:03:34 +02:00
Lauri Ojansivu 06fb201d80 Update translations. 2020-03-11 08:48:11 +02:00
Lauri Ojansivu 62e5622523 Update ChangeLog. 2020-03-06 03:59:09 +02:00
Lauri Ojansivu 2b26bbe78a Fix: img tag did not allow width and height.
Removed swipebox from markdown editor img tag and
updated marked markdown to newest version.

Thanks to hradec and xet7 !

Closes #2956
2020-03-06 03:52:12 +02:00
Lauri Ojansivu a6d702d1ab Update ChangeLog. 2020-03-05 22:04:22 +02:00
Lauri Ojansivu aaecac0912 Mouse scroll part 2. 2020-03-05 22:02:52 +02:00
Lauri Ojansivu 08b35e1fe7 Fix typo. 2020-03-05 21:57:48 +02:00
Lauri Ojansivu 78cf4618d5 Update ChangeLog. 2020-03-05 21:56:44 +02:00
Lauri Ojansivu 9d13001b90 Add settings for mouse wheen scroll inertia and scroll amount.
For example:
  sudo snap set scrollinertia='200'
  sudo snap set scrollamount='200'

Thanks to danger89 and xet7 !

Related #2949
2020-03-05 21:51:03 +02:00
Lauri Ojansivu 965c3fb07f Update translations. 2020-03-05 21:09:33 +02:00
Lauri Ojansivu c43366b7ac Update ChangeLog. 2020-03-05 20:51:27 +02:00
Lauri Ojansivu 3a50556656 Merge pull request #2955 from benoitm76/master
Fix: Error when retrieve token from some OIDC due to not necessary sc…
2020-03-05 20:29:37 +02:00
Benoit MOUQUET b26e65c70c Fix: Error when retrieve token from some OIDC due to not necessary scope parameter 2020-03-05 12:52:46 +01:00
Lauri Ojansivu 75073bbdb2 v3.83 2020-03-01 22:12:11 +02:00
Lauri Ojansivu 88573ad2cd Try to revert remaining of the following changes:
- Revert change made at Wekan v3.81, because building did not work: [Try to make Meteor build time shorter
  by excluding legacy and cordova. This was made possible by
  Meteor 1.10-rc.2](https://github.com/wekan/wekan/commit/0d3002f69d97e646fa7368bfdade4f78c51e9884).

Thanks to xet7.
2020-03-01 22:07:55 +02:00
Lauri Ojansivu 058180687a v3.82 2020-03-01 21:44:38 +02:00
Lauri Ojansivu 897506eeac v3.81 2020-03-01 21:30:43 +02:00
Lauri Ojansivu aac7c380c8 - Fix critical and moderate security vulnerabilities reported at 2020-02-26 with
responsible disclosure by [Dejan Zelic](https://twitter.com/dejandayoff),
  Justin Benjamin and others at [Offensive Security](https://twitter.com/offsectraining),
  that follow standard 90 days before public disclosure.
  Thanks to xet7.
- Fix webhook error that prevented some card etc deleting from web UI of board.
  Thanks to xet7.
- Add some more Font Awesome icons.
  Thanks to xet7.
- Remove autofocus from many form input boxes so that they would not cause warnings.
  Thanks to xet7.
2020-03-01 20:59:53 +02:00
Lauri Ojansivu fc35c234a7 Update translations. 2020-03-01 20:38:05 +02:00
Lauri Ojansivu 0d3002f69d Try to make Meteor build time shorter by excluding legacy and cordova.
Thanks to xet7 !
2020-03-01 20:33:23 +02:00
Lauri Ojansivu 7d920e938b Merge branch 'master' of github.com:wekan/wekan 2020-03-01 20:27:22 +02:00
Lauri Ojansivu 26b521e86e Upgrade Meteor to 1.10-rc.2 2020-03-01 20:26:29 +02:00
Lauri Ojansivu 83b042701f Merge pull request #2948 from NicoP-S/master
Make profile.initials available in publications
2020-02-28 23:11:03 +02:00
Nico 21b75edcdd Make profile.initials available in publications 2020-02-28 22:05:33 +01:00
Lauri Ojansivu 493d71a198 Update translations. 2020-02-23 22:45:43 +02:00
Lauri Ojansivu 27f9c6b925 Update translations. 2020-02-22 20:07:16 +02:00
Lauri Ojansivu fa07dcf422 Update ChangeLog. 2020-02-22 16:39:28 +02:00
Lauri Ojansivu 2b9540ce02 Try to fix afterwards loading of cards by adding
fallback when requestIdleCallback is not available.

Thanks to xet7 !

Closes #2878,
closes #2250
2020-02-22 16:32:54 +02:00
Lauri Ojansivu 906c25d5e8 Update translations. 2020-02-22 10:32:48 +02:00
Lauri Ojansivu e9924a2f03 v3.80 2020-02-22 03:10:08 +02:00
Lauri Ojansivu 16e7fc6edc Update translations. 2020-02-22 02:55:26 +02:00
Lauri Ojansivu e0ca960a35 Create New User in Admin Panel. Works, but does not save fullname yet,
so currently it's needed to edit add fullname later.

Thanks to xet7 !

Related #802
2020-02-22 02:49:14 +02:00
Lauri Ojansivu 0e755e021b Update ChangeLog. 2020-02-21 00:48:02 +02:00
Lauri Ojansivu 9be3f3714a Update to Meteor 1.9.2 2020-02-21 00:46:05 +02:00
Lauri Ojansivu 02e425b39d Update ChangeLog. 2020-02-20 22:57:04 +02:00
Lauri Ojansivu ec01206030 Remove alethes:pages package, that had some indentation error.
Package is about pagination, but I did not find any pagination
related code in Wekan yet.

Thanks to xet7 !
2020-02-20 22:00:28 +02:00
Lauri Ojansivu cbbb5deff7 Update to Meteor 1.9.1, Node 12.16.1 etc newest dependencies. 2020-02-20 20:46:25 +02:00
Lauri Ojansivu 4377be8aa4 Merge branch 'master' of github.com:wekan/wekan 2020-02-20 20:20:11 +02:00
Lauri Ojansivu 26010476b8 Update translations. 2020-02-20 20:19:42 +02:00
Lauri Ojansivu aac27d4db3 Merge pull request #2937 from pbek/patch-1
Fix docker-compose link
2020-02-20 17:13:55 +02:00
Patrizio Bekerle 9762030920 Fix docker-compose link 2020-02-20 15:25:05 +01:00
Lauri Ojansivu cb1ffea5d6 Update translations. 2020-02-19 10:36:12 +02:00
Lauri Ojansivu a4ff6cc0af Update Sandstorm release build script. 2020-02-13 20:04:18 -05:00
Lauri Ojansivu 84acc65d04 v3.79 2020-02-13 15:16:07 +02:00
Lauri Ojansivu 2b1727c6e1 Update translations. 2020-02-13 15:12:46 +02:00
Lauri Ojansivu 178f376e21 Fix Card Opened Webhook can not be disabled.
Thanks to mvanvoorden and xet7 !

Closes #2932,
closes #2889,
closes #2666
2020-02-13 14:58:19 +02:00
Lauri Ojansivu 92538a6ac3 Update translations. 2020-02-13 14:34:53 +02:00
436 changed files with 18104 additions and 28734 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
+10
View File
@@ -0,0 +1,10 @@
FROM gitpod/workspace-mongodb
USER gitpod
# Install custom tools, runtime, etc. using apt-get
# For example, the command below would install "bastet" - a command line tetris clone:
#
# RUN sudo apt-get -q update && # sudo apt-get install -yq bastet && # sudo rm -rf /var/lib/apt/lists/*
#
# More information: https://www.gitpod.io/docs/config-docker/
+4
View File
@@ -0,0 +1,4 @@
tasks:
- init: npm install
image:
file: .gitpod.Dockerfile
+9 -8
View File
@@ -6,10 +6,11 @@
meteor-base@1.4.0
# Build system
ecmascript@0.14.0
ecmascript@0.14.3
standard-minifier-css@1.6.0
standard-minifier-js@2.6.0
mquandalle:jade
coffeescript@2.4.1!
# Polyfills
es5-shim@4.8.0
@@ -22,7 +23,7 @@ dburles:collection-helpers
idmontie:migrations
matb33:collection-hooks
matteodem:easy-search
mongo@1.8.0
mongo@1.10.0
mquandalle:collection-mutations
# Account system
@@ -37,13 +38,12 @@ wekan-accounts-oidc
# Utilities
check@1.3.1
jquery@1.11.10
random@1.1.0
random@1.2.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
@@ -67,15 +67,15 @@ templates:tabs
verron:autosize
simple:json-routes
rajit:bootstrap3-datepicker
shell-server@0.4.0
shell-server@0.5.0
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.5.2
accounts-password@1.6.0
cfs:gridfs
rzymek:fullcalendar
momentjs:moment@2.22.2
@@ -85,7 +85,8 @@ msavin:usercache
wekan-scrollbar
mquandalle:perfect-scrollbar
mdg:meteor-apm-agent@3.2.0-rc.0!
coagmano:stylus
# 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
+1 -1
View File
@@ -1 +1 @@
METEOR@1.9
METEOR@1.10.2
+27 -27
View File
@@ -1,27 +1,26 @@
3stack:presence@1.1.2
accounts-base@1.5.0
accounts-oauth@1.1.16
accounts-password@1.5.3
accounts-base@1.6.0
accounts-oauth@1.2.0
accounts-password@1.6.0
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.5.1
babel-compiler@7.5.3
babel-runtime@1.5.0
base64@1.0.12
binary-heap@1.0.11
blaze@2.3.4
blaze-tools@1.0.10
boilerplate-generator@1.6.0
boilerplate-generator@1.7.0
browser-policy-common@1.0.11
browser-policy-framing@1.1.0
caching-compiler@1.2.1
caching-compiler@1.2.2
caching-html-compiler@1.1.3
callback-hook@1.3.0
cfs:access-point@0.1.49
@@ -44,8 +43,9 @@ cfs:upload-http@0.0.20
cfs:worker@0.1.5
check@1.3.1
chuangbo:cookie@1.1.0
coagmano:stylus@2.0.0
coffeescript@1.0.17
coagmano:stylus@1.1.0
coffeescript@2.4.1
coffeescript-compiler@2.4.1
cottz:publish-relations@2.0.8
dburles:collection-helpers@1.1.0
ddp@1.4.0
@@ -55,9 +55,9 @@ ddp-rate-limiter@1.0.7
ddp-server@2.3.1
deps@1.0.12
diff-sequence@1.1.1
dynamic-import@0.5.1
dynamic-import@0.5.2
easylogic:summernote@0.8.8
ecmascript@0.14.1
ecmascript@0.14.3
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.10.0
ecmascript-runtime-server@0.9.0
@@ -75,7 +75,7 @@ htmljs@1.0.11
http@1.4.2
id-map@1.1.0
idmontie:migrations@1.0.3
inter-process-messaging@0.1.0
inter-process-messaging@0.1.1
jquery@1.11.11
kadira:blaze-layout@2.3.0
kadira:dochead@1.5.0
@@ -84,7 +84,7 @@ 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
launch-screen@1.2.0
livedata@1.0.18
localstorage@1.2.0
logging@1.1.20
@@ -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.4.5
mobile-status-bar@1.0.14
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.8.1
momentjs:moment@2.25.2
mongo@1.10.0
mongo-decimal@0.1.1
mongo-dev-server@1.1.0
mongo-id@1.0.7
@@ -127,13 +127,13 @@ 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.3.0
oauth@1.2.8
oauth2@1.2.1
npm-mongo@3.7.1
oauth@1.3.0
oauth2@1.3.0
observe-sequence@1.0.16
ongoworks:speakingurl@1.1.0
ordered-dict@1.1.0
ostrio:cookies@2.5.0
ostrio:cookies@2.6.0
peerlibrary:assert@0.3.0
peerlibrary:base-component@0.16.0
peerlibrary:blaze-components@0.15.1
@@ -144,7 +144,7 @@ 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
random@1.2.0
rate-limit@1.0.9
reactive-dict@1.3.0
reactive-var@1.0.11
@@ -156,17 +156,17 @@ server-render@0.3.1
service-configuration@1.0.11
session@1.2.0
sha@1.0.9
shell-server@0.4.0
shell-server@0.5.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.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,12 +181,12 @@ 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
verron:autosize@3.0.8
webapp@1.8.2
webapp@1.9.1
webapp-hashing@1.0.9
wekan-accounts-cas@0.1.0
wekan-accounts-oidc@1.0.10
@@ -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.
-99
View File
@@ -1,99 +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!
coagmano:stylus
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@2.0.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'] = {};
})();
-584
View File
@@ -1,584 +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();
}
}
-149
View File
@@ -1,149 +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);
};
-5184
View File
File diff suppressed because it is too large. Load diff
-73
View File
@@ -1,73 +0,0 @@
{
"name": "wekan",
"version": "v3.78.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": "^5.16.0",
"eslint-config-meteor": "0.0.9",
"eslint-config-prettier": "^3.6.0",
"eslint-import-resolver-meteor": "^0.4.0",
"eslint-plugin-import": "^2.18.0",
"eslint-plugin-meteor": "^5.1.0",
"eslint-plugin-prettier": "^3.1.0",
"lint-staged": "^7.3.0",
"pre-commit": "^1.2.2",
"prettier": "^1.18.2",
"prettier-eslint": "^8.8.2"
},
"dependencies": {
"@babel/runtime": "^7.6.2",
"ajv": "^5.0.0",
"babel-runtime": "^6.26.0",
"bcrypt": "^3.0.2",
"bson": "^4.0.0",
"bunyan": "^1.8.12",
"es6-promise": "^4.2.4",
"gridfs-stream": "^0.5.3",
"ldapjs": "^1.0.2",
"meteor-node-stubs": "^0.4.1",
"mongodb": "^3.3.3",
"os": "^0.1.1",
"page": "^1.8.6",
"qs": "^6.8.0",
"source-map-support": "^0.5.12",
"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.15.0
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
+526 -1
View File
@@ -1,3 +1,528 @@
# 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:
- [Add gitpod config](https://github.com/wekan/wekan/pull/3009).
This adds support for Gitpod.io, a free automated
dev environment that makes contributing and generally working on GitHub
projects much easier. It allows anyone to start a ready-to-code dev
environment for any branch, issue and pull request with a single click.
Thanks to juniormendonca.
- [Public boards overview](https://github.com/wekan/wekan/pull/3008).
Thanks to NicoP-S.
and fixes the following bugs:
- [Fix styling issue in notifications drawer](https://github.com/wekan/wekan/pull/3012).
Thanks to boeserwolf.
- [Fix error in notifications cleanup cron](https://github.com/wekan/wekan/pull/3010).
Thanks to jtbairdsr.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.94 2020-04-12 Wekan release
This release adds the following new features:
- [Public vote](https://github.com/wekan/wekan/pull/3006).
Thanks to NicoP-S.
- [Add robots.txt disallow all](https://github.com/wekan/wekan/commit/3fae5355d40055757bf4a5f0c503581195609720).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.93 2020-04-10 Wekan release
This release adds the following new features:
- [Trello vote import & hide export button if with_api is
disabled](https://github.com/wekan/wekan/pull/3000).
Thanks to NicoP-S.
- [When adding a user to a board that has subtasks, also add user to the subtask
board](https://github.com/wekan/wekan/pull/3004).
Thanks to slvrpdr.
and adds the following updates:
- Upgrade to Node v12.16.2 [Part1](https://github.com/wekan/wekan/commit/6db717b9b384fe1491063e507b80e67791a07e3a)
and [Part2](https://github.com/wekan/wekan/commit/268d7fcb32186a902a84e7f6d80c50b1f3790bad).
Thanks to Node developers and xet7.
and fixes the following bugs:
- [Fix bug that prevents editing or deleting
comments](https://github.com/wekan/wekan/pull/3005).
Thanks to jtbairdsr.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.92 2020-04-09 Wekan release
This release adds the following new features:
- [Scheduler to clean up read notifications. Also added a button to manually remove all
read notifications, and a fix to prevent users form getting notifications for their own
actions](https://github.com/wekan/wekan/pull/2998).
Thanks to jtbairdsr.
- [Add setting](https://github.com/wekan/wekan/commit/5ebb47cb0ec7272894a37d99579ede872251f55c)
default [NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2](https://github.com/wekan/wekan/pull/2998)
to all Wekan platforms.
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.91 2020-04-08 Wekan release
This release adds the following new features:
- [OpenShift: Route template added to helm chart for Openshift v4x
cluster](https://github.com/wekan/wekan/pull/2996).
Thanks to ckavili.
- [Filter by Assignee](https://github.com/wekan/wekan/pull/2997).
Thanks to daniel-eder.
- [Vote on Card](https://github.com/wekan/wekan/pull/2994).
Thanks to NicoP-S and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.90 2020-04-06 Wekan release
This release makes the following updates:
- [Update dependencies](https://github.com/wekan/wekan/commit/d798f6e3ef09595ce4f1d1fbc053eec70fc91fb9).
and updates the following translations:
- [Update layouts.js for zh-TW language name](https://github.com/wekan/wekan/pull/2988).
Thanks to doggy8088.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.89 2020-04-05 Wekan release
This release adds the following new features:
- [Create subtasks in parenttask swimlane](https://github.com/wekan/wekan/issues/1953).
Thanks to TOSCom-DanielEder.
- [When searching cards in a board, also search from Custom Fields](https://github.com/wekan/wekan/pull/2985).
Thanks to slvrpdr.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.88 2020-04-02 Wekan release
This release adds the following new features:
- [Notification drawer](https://github.com/wekan/wekan/pull/2975) [like Trello](https://github.com/wekan/wekan/issues/2471).
Thanks to jtbairdsr and xet7.
and makes the following UI changes:
- [Minicard labels on the top and title on bottom](https://github.com/wekan/wekan/issues/2980).
Thanks to helioguardabaxo and xet7.
and fixes the following bugs:
- [Fix start-wekan.sh MongoDB port to 27017](https://github.com/wekan/wekan/commit/c60a092fc0ed9fe15c417bcb443b1e3e3aaedf7e).
Thanks to Keelan and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.87 2020-04-01 Wekan release
This release makes the following UI changes:
- [Move "Rules" from "Board View" to "Board Settings"](https://github.com/wekan/wekan/issues/2973).
Thanks to helioguardabaxo and xet7.
- [Improvements on card details visualization](https://github.com/wekan/wekan/issues/2974).
Thanks to helioguardabaxo and xet7.
- [Hide duplicate "Hide system messages" at Change Settings/Member Settings, because it's also on card
slider](https://github.com/wekan/wekan/issues/2837).
Thanks to notohiro and xet7.
and fixes the following bugs:
- [Fix Browser always reload the whole page when I change one of the card
color](https://github.com/wekan/wekan/commit/3546d7aa02bc65cf1183cb493adeb543ba51945d).
Fixed by making label colors and text again editable.
Regression from [Wekan v3.86 2)](https://github.com/wekan/wekan/commit/b9099a8b7ea6f63c79bdcbb871cb993b2cb7e325).
Thanks to javen9881 and xet7.
- [Fix richer editor submit did not clear edit area](https://github.com/wekan/wekan/commit/033d6710470b2ecd7a0ec0b2f0741ff459e68b32).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.86 2020-03-24 Wekan release
This release fixes the following bugs:
- [Fix Rich editor can not be disabled, regression from changes yesterday at Wekan v3.85](https://github.com/wekan/wekan/commit/12ab8fac5db9c5ac8069d0ca2bca340d6004a25b).
Thanks to uusijani, vjrj and xet7.
- [1) Fix Pasting text into a card is adding a line before and after
(and multiplies by pasting more) by changing paste "p" to "br".
2) Fixes to summernote and markdown comment editors, related
to keeping them open when adding comments, having
@member mention not close card, and disabling clicking of
@member mention](https://github.com/wekan/wekan/commit/b9099a8b7ea6f63c79bdcbb871cb993b2cb7e325).
Thanks to xet7 !
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.85 2020-03-23 Wekan release
This release fixes the following CRITICAL SECURITY VULNERABILITIES:
- [Fix XSS bug reported today 4 hours ago by Cyb3rjunky](https://github.com/wekan/wekan/commit/482682e50079d70c5113169020d6834013b57c11).
Logged in users could run javascript in input fields.
This affects Wekan versions v3.12-v3.84.
In [Wekan v3.12](https://github.com/wekan/wekan/blob/master/CHANGELOG.md#v312-2019-08-09-wekan-release)
there was [changes for XSS filter to allow inserting images, videos etc
on comment WYSIWYG editor](https://github.com/wekan/wekan/pull/2593)
so features related to that are now removed.
After this fix, Javascript in input fields is not executed.
Thanks to Cyb3rjunky and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.84 2020-03-16 Wekan release
This release adds the following features:
- Add settings for mouse wheel scroll inertia and scroll
amount [Part1](https://github.com/wekan/wekan/commit/9d13001b903f9ec50f5fa3a4bdbacae32b27ac65)
and [Part2](https://github.com/wekan/wekan/commit/aaecac091209e90c0c2123830728f5e7a835ccb4).
For example: sudo snap set wekan scrollinertia='200' , sudo snap set wekan scrollamount='200' .
Thanks to danger89 and xet7.
and adds the following updates:
- [Upgrade to Meteor 1.10.1](https://github.com/wekan/wekan/commit/e16c65babc1f021c35a3d46bc61e649ec94d1e82).
Thanks to xet7.
- [Update markdown](https://github.com/wekan/wekan/commit/6e0fa78022ea487176eb0a32ec5a4a441f8e0c3c).
Thanks to xet7.
- [Update minimist](https://github.com/wekan/wekan/commit/ea6baa5c2b956ee28b0a7e63f988e2fc1998201a).
Thanks to xet7.
- [Update acorn](https://github.com/wekan/wekan/commit/369a29707bbec3bf89717c16e8b698fb4666087a).
Thanks to xet7.
- [Update prettier-eslint](https://github.com/wekan/wekan/commit/8183b7bdaa01d2ce53ac7215beafd5efe21373e8).
Thanks to xet7.
- [Update ostrio:cookies](https://github.com/wekan/wekan/commit/14b8610837117616d436e2bac6a9dc653e315662).
Thanks to xet7.
- [Add build time profiling to build script](https://github.com/wekan/wekan/commit/f968109e7390139e50375ee29bc7bc3cf1e1ab41).
Thanks to zodern.
and fixes the following bugs:
- [Downgrade stylus to v1.1.0 to speed up building Wekan](https://github.com/wekan/wekan/commit/fca4cdcebf1cc6642aefeb78b911cb5b95ebe473).
This is because building newer stylus v2 takes 52 minutes. After this change, building Wekan takes 3 minutes.
Thanks to zodern.
- [Fix: Error when retrieve token from some OIDC due to not necessary scope
parameter](https://github.com/wekan/wekan/pull/2955).
Thanks to benoitm76.
- [Fix: img tag did not allow width and height. Removed swipebox from markdown editor
img tag and updated marked markdown to newest version](https://github.com/wekan/wekan/commit/2b26bbe78a1a2b8b427963a6c44c3853efdb737e).
Thanks to hradec and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.83 2020-03-01 Wekan release
This release tries to revert remaining the following changes:
- [Revert](https://github.com/wekan/wekan/88573ad2cdb8596b795a82ef40a0662180e8a7d7) change made at Wekan v3.81,
because building did not work: [Try to make Meteor build time shorter
by excluding legacy and cordova. This was made possible by
Meteor 1.10-rc.2](https://github.com/wekan/wekan/commit/0d3002f69d97e646fa7368bfdade4f78c51e9884).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.82 2020-03-01 Wekan release
This release reverts the following changes:
- Revert change made at Wekan v3.81, because building did not work: [Try to make Meteor build time shorter
by excluding legacy and cordova. This was made possible by
Meteor 1.10-rc.2](https://github.com/wekan/wekan/commit/0d3002f69d97e646fa7368bfdade4f78c51e9884).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.81 2020-03-01 Wekan release
This release [fixes](https://github.com/wekan/wekan/commit/aac7c380c8c389b0683b2bd64e2cc856993f0e30) the following CRITICAL SECURITY VULNERABILITIES and other bugs:
- Fix critical and moderate security vulnerabilities reported at 2020-02-26 with
responsible disclosure by [Dejan Zelic](https://twitter.com/dejandayoff),
Justin Benjamin and others at [Offensive Security](https://twitter.com/offsectraining),
that follow standard 90 days before public disclosure.
Thanks to xet7.
- Fix webhook error that prevented some card etc deleting from web UI of board.
Thanks to xet7.
- Add missing Font Awesome icon to Board Settings Menu.
Thanks to xet7.
- Remove autofocus from many form input boxes so that they would not cause warnings.
Thanks to xet7.
and does the following upgrades:
- [Upgrade Meteor to 1.10-rc.2](https://github.com/wekan/wekan/commit/26b521e86e6ac40b7ba25bbe8dac7bf4d48d43ce).
Thanks to xet7.
- [Try to make Meteor build time shorter by excluding legacy and cordova. This was made possible by
Meteor 1.10-rc.2](https://github.com/wekan/wekan/commit/0d3002f69d97e646fa7368bfdade4f78c51e9884).
Thanks to xet7.
and fixes the following bugs:
- [Try to fix afterwards loading of cards by adding fallback when requestIdleCallback is not
available](https://github.com/wekan/wekan/commit/2b9540ce02de604bf84ea082f2dcb1d01673708c).
Thanks to xet7.
- [Make profile.initials available in publications](https://github.com/wekan/wekan/pull/2948).
Thanks to NicoP-S.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.80 2020-02-22 Wekan release
This release adds the following features:
- [Create New User in Admin Panel](https://github.com/wekan/wekan/commit/e0ca960a35cf006880019ba28fc82aa30f289a71).
Works, but does not save fullname yet, so currently it's needed to edit add fullname later.
Thanks to xet7.
and adds the following updates:
- [Update to Meteor 1.9.1, Node 12.16.1 etc newest dependencies](https://github.com/wekan/wekan/commit/cbbb5deff7d84a91c40becc9caaf70f5b6738b63).
Thanks to xet7.
- [Update to Meteor 1.9.2](https://github.com/wekan/wekan/commit/9be3f3714ae680ff9fc1855c960c9831e84c2b07).
Thanks to xet7.
and fixes the following bugs:
- [Update Sandstorm release build script](https://github.com/wekan/wekan/commit/a4ff6cc0af8545ca4d3e97fa2cabbe7981c025b2).
Thanks to xet7.
- [Fix docker-compose link](https://github.com/wekan/wekan/pull/2937).
Thanks to pbek.
- [Remove alethes:pages package, that had some indentation error.
Package is about pagination, but I did not find any pagination related code in Wekan
yet](https://github.com/wekan/wekan/commit/ec012060305bc16fbf8d2ac218f5c847e02c4301).
Thanks to xet7 !
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.79 2020-02-13 Wekan release
This release fixes the following bugs:
- [Fix Card Opened Webhook can not be disabled](https://github.com/wekan/wekan/commit/178f376e2138b5522c2e92ddfd2babb113df8d9f).
Thanks to mvanvoorden and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.78 2020-02-12 Wekan release
This release adds the following features:
@@ -25,7 +550,7 @@ and fixes the following bugs:
work [Part 1](https://github.com/wekan/wekan/commit/9a21b0a1c933e7f778e4e57a8258e150ccea1620)
and [Part2](https://github.com/wekan/wekan/commit/4467a68b97a3fbf0fbae7f05177d978f2aa80287).
Thanks to 2020product and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v3.76 2020-02-07 Wekan release
+11 -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.15.0 \
METEOR_RELEASE=1.9.0 \
NODE_VERSION=v12.16.3 \
METEOR_RELEASE=1.10.2 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
NPM_VERSION=latest \
@@ -26,6 +28,7 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-
ATTACHMENTS_STORE_PATH="" \
MAX_IMAGE_PIXEL="" \
IMAGE_COMPRESS_RATIO="" \
NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="" \
BIGEVENTS_PATTERN=NONE \
NOTIFY_DUE_DAYS_BEFORE_AND_AFTER="" \
NOTIFY_DUE_AT_HOUR_OF_DAY="" \
@@ -110,7 +113,10 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-
CORS="" \
CORS_ALLOW_HEADERS="" \
CORS_EXPOSE_HEADERS="" \
DEFAULT_AUTHENTICATION_METHOD=""
DEFAULT_AUTHENTICATION_METHOD="" \
SCROLLINERTIA="0" \
SCROLLAMOUNT="auto" \
PASSWORD_LOGIN_ENABLED=true
# Copy the app to the image
COPY ${SRC_PATH} /home/wekan/app
@@ -267,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"]
+4 -2
View File
@@ -1,3 +1,5 @@
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/wekan/wekan)
# Wekan - Open Source kanban
[![Contributors](https://img.shields.io/github/contributors/wekan/wekan.svg "Contributors")](https://github.com/wekan/wekan/graphs/contributors)
@@ -32,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
@@ -61,7 +63,7 @@ that by providing one-click installation on various platforms.
[Mac](https://github.com/wekan/wekan/wiki/Mac) / [Windows](https://github.com/wekan/wekan/wiki/Install-Wekan-from-source-on-Windows).
[More Platforms](https://github.com/wekan/wekan/wiki/Platforms), bundle for RasPi3 ARM and other CPUs where Node.js and MongoDB exists.
- 1 GB RAM minimum free for Wekan. Production server should have minimum total 4 GB RAM.
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/devel/docker-compose.yml): 3 frontend servers,
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/master/docker-compose.yml): 3 frontend servers,
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
- Enough disk space and alerts about low disk space. If you run out disk space, MongoDB database gets corrupted.
- SECURITY: Updating to newest Wekan version very often. Please check you do not have automatic updates of Sandstorm or Snap turned off.
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v3.78.0"
appVersion: "v4.02.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');
});
}
+167 -200
View File
@@ -8,234 +8,201 @@ template(name="activities")
+cardActivities
template(name="boardActivities")
each currentBoard.activities
.activity
+userAvatar(userId=user._id)
p.activity-desc
+memberName(user=user)
if($eq activityType 'deleteAttachment')
| {{{_ 'activity-delete-attach' cardLink}}}.
if($eq activityType 'addAttachment')
| {{{_ 'activity-attached' attachmentLink cardLink}}}.
if($eq activityType 'addBoardMember')
| {{{_ 'activity-added' memberLink boardLabel}}}.
if($eq activityType 'addComment')
| {{{_ 'activity-on' cardLink}}}
a.activity-comment(href="{{ card.absoluteUrl }}")
+viewer
= comment.text
if($eq activityType 'addChecklist')
| {{{_ 'activity-checklist-added' cardLink}}}.
.activity-checklist(href="{{ card.absoluteUrl }}")
+viewer
= checklist.title
if($eq activityType 'removeChecklist')
| {{{_ 'activity-checklist-removed' cardLink}}}.
if($eq activityType 'checkedItem')
| {{{_ 'activity-checked-item' checkItem checklist.title cardLink}}}.
if($eq activityType 'uncheckedItem')
| {{{_ 'activity-unchecked-item' checkItem checklist.title cardLink}}}.
if($eq activityType 'checklistCompleted')
| {{{_ 'activity-checklist-completed' checklist.title cardLink}}}.
if($eq activityType 'checklistUncompleted')
| {{{_ 'activity-checklist-uncompleted' checklist.title cardLink}}}.
if($eq activityType 'addChecklistItem')
| {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}.
.activity-checklist(href="{{ card.absoluteUrl }}")
+viewer
= checklistItem.title
if($eq activityType 'removedChecklistItem')
| {{{_ 'activity-checklist-item-removed' checklist.title cardLink}}}.
if($eq activityType 'archivedCard')
| {{{_ 'activity-archived' cardLink}}}.
if($eq activityType 'archivedList')
| {{_ 'activity-archived' list.title}}.
if($eq activityType 'archivedSwimlane')
| {{_ 'activity-archived' swimlane.title}}.
if($eq activityType 'createBoard')
| {{_ 'activity-created' boardLabel}}.
if($eq activityType 'createCard')
| {{{_ 'activity-added' cardLink boardLabel}}}.
if($eq activityType 'createCustomField')
| {{_ 'activity-customfield-created' customField}}.
if($eq activityType 'createList')
| {{_ 'activity-added' list.title boardLabel}}.
if($eq activityType 'createSwimlane')
| {{_ 'activity-added' swimlane.title boardLabel}}.
if($eq activityType 'removeList')
| {{_ 'activity-removed' title boardLabel}}.
if($eq activityType 'importBoard')
| {{{_ 'activity-imported-board' boardLabel sourceLink}}}.
if($eq activityType 'importCard')
| {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}.
if($eq activityType 'importList')
| {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}.
if($eq activityType 'joinMember')
if($eq user._id member._id)
| {{{_ 'activity-joined' cardLink}}}.
else
| {{{_ 'activity-added' memberLink cardLink}}}.
if($eq activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
if($eq activityType 'moveCard')
| {{{_ 'activity-moved' cardLink oldList.title list.title}}}.
if($eq activityType 'removeBoardMember')
| {{{_ 'activity-excluded' memberLink boardLabel}}}.
if($eq activityType 'restoredCard')
| {{{_ 'activity-sent' cardLink boardLabel}}}.
if($eq activityType 'addedLabel')
| {{{_ 'activity-added-label' lastLabel cardLink}}}.
if($eq activityType 'removedLabel')
| {{{_ 'activity-removed-label' lastLabel cardLink}}}.
if($eq activityType 'setCustomField')
| {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}.
if($eq activityType 'unsetCustomField')
| {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}.
if($eq activityType 'unjoinMember')
if($eq user._id member._id)
| {{{_ 'activity-unjoined' cardLink}}}.
else
| {{{_ 'activity-removed' memberLink cardLink}}}.
span(title=createdAt).activity-meta {{ moment createdAt }}
each activityData in currentBoard.activities
+activity(activity=activityData card=card mode=mode)
template(name="cardActivities")
each currentCard.activities
.activity
+userAvatar(userId=user._id)
p.activity-desc
+memberName(user=user)
if($eq activityType 'createCard')
| {{_ 'activity-added' cardLabel listName}}.
if($eq activityType 'importCard')
| {{{_ 'activity-imported' cardLabel list.title sourceLink}}}.
if($eq activityType 'joinMember')
if($eq user._id member._id)
| {{_ 'activity-joined' cardLabel}}.
else
| {{{_ 'activity-added' memberLink cardLabel}}}.
if($eq activityType 'unjoinMember')
if($eq user._id member._id)
| {{_ 'activity-unjoined' cardLabel}}.
else
| {{{_ 'activity-removed' cardLabel memberLink}}}.
if($eq activityType 'archivedCard')
| {{_ 'activity-archived' cardLabel}}.
each activityData in currentCard.activities
+activity(activity=activityData card=card mode=mode)
if($eq activityType 'addedLabel')
| {{{_ 'activity-added-label-card' lastLabel }}}.
template(name="activity")
.activity
+userAvatar(userId=activity.user._id)
p.activity-desc
+memberName(user=activity.user)
if($eq activityType 'removedLabel')
| {{{_ 'activity-removed-label-card' lastLabel }}}.
//- attachment activity -------------------------------------------------
if($eq activity.activityType 'deleteAttachment')
| {{{_ 'activity-delete-attach' cardLink}}}.
if($eq activityType 'removeChecklist')
| {{{_ 'activity-checklist-removed' cardLabel}}}.
if($eq activity.activityType 'addAttachment')
| {{{_ 'activity-attached' attachmentLink cardLink}}}.
if($neq mode 'board')
if activity.attachment.isImage
img.attachment-image-preview(src=activity.attachment.url)
if($eq activityType 'checkedItem')
| {{{_ 'activity-checked-item-card' checkItem checklist.title }}}.
//- board activity ------------------------------------------------------
if($eq mode 'board')
if($eq activity.activityType 'createBoard')
| {{_ 'activity-created' boardLabel}}.
if($eq activityType 'uncheckedItem')
| {{{_ 'activity-unchecked-item-card' checkItem checklist.title }}}.
if($eq activity.activityType 'importBoard')
| {{{_ 'activity-imported-board' boardLabel sourceLink}}}.
if($eq activityType 'checklistCompleted')
| {{{_ 'activity-checklist-completed-card' checklist.title }}}.
if($eq activity.activityType 'addBoardMember')
| {{{_ 'activity-added' memberLink boardLabel}}}.
if($eq activityType 'checklistUncompleted')
| {{{_ 'activity-checklist-uncompleted-card' checklist.title }}}.
if($eq activity.activityType 'removeBoardMember')
| {{{_ 'activity-excluded' memberLink boardLabel}}}.
if($eq activityType 'restoredCard')
| {{_ 'activity-sent' cardLabel boardLabel}}.
if($eq activityType 'moveCard')
| {{_ 'activity-moved' cardLabel oldList.title list.title}}.
//- card activity -------------------------------------------------------
if($eq activity.activityType 'createCard')
if($eq mode 'card')
| {{{_ 'activity-added' cardLabel activity.listName}}}.
else
| {{{_ 'activity-added' cardLabel boardLabel}}}.
if($eq activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
if($eq activity.activityType 'importCard')
| {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}.
if($eq activityType 'addAttachment')
| {{{_ 'activity-attached' attachmentLink cardLabel}}}.
if attachment.isImage
img.attachment-image-preview(src=attachment.url)
if($eq activityType 'deleteAttachment')
| {{{_ 'activity-delete-attach' cardLabel}}}.
if($eq activityType 'removedChecklist')
| {{{_ 'activity-checklist-removed' cardLabel}}}.
if($eq activityType 'addChecklist')
| {{{_ 'activity-checklist-added' cardLabel}}}.
if($eq activity.activityType 'moveCard')
| {{{_ 'activity-moved' cardLabel activity.oldList.title activity.list.title}}}.
if($eq activity.activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink activity.oldBoardName activity.boardName}}}.
if($eq activity.activityType 'archivedCard')
| {{{_ 'activity-archived' cardLink}}}.
if($eq activity.activityType 'restoredCard')
| {{{_ 'activity-sent' cardLink boardLabel}}}.
//- checklist activity --------------------------------------------------
if($eq activity.activityType 'addChecklist')
| {{{_ 'activity-checklist-added' cardLink}}}.
if($eq mode 'card')
.activity-checklist
+viewer
= checklist.title
if($eq activityType 'addChecklistItem')
| {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}.
.activity-checklist(href="{{ card.absoluteUrl }}")
= activity.checklist.title
else
a.activity-checklist(href="{{ activity.card.absoluteUrl }}")
+viewer
= checklistItem.title
= activity.checklist.title
if(currentData.timeKey)
| {{{_ activityType }}}
= ' '
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
if (currentData.timeOldValue)
= ' '
| {{{_ "previous_as" }}}
= ' '
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
= ' @'
else if(currentData.timeValue)
| {{{_ activityType currentData.timeValue}}}
if($eq activity.activityType 'removedChecklist')
| {{{_ 'activity-checklist-removed' cardLink}}}.
if($eq activity.activityType 'completeChecklist')
| {{{_ 'activity-checklist-completed' activity.checklist.title cardLink}}}.
if($eq activityType 'deleteComment')
| {{{_ 'activity-deleteComment' currentData.commentId}}}.
if($eq activityType 'editComment')
| {{{_ 'activity-editComment' currentData.commentId}}}.
if($eq activityType 'addComment')
if($eq activity.activityType 'uncompleteChecklist')
| {{{_ 'activity-checklist-uncompleted' activity.checklist.title cardLink}}}.
if($eq activity.activityType 'checkedItem')
| {{{_ 'activity-checked-item' checkItem activity.checklist.title cardLink}}}.
if($eq activity.activityType 'uncheckedItem')
| {{{_ 'activity-unchecked-item' checkItem activity.checklist.title cardLink}}}.
if($eq activity.activityType 'addChecklistItem')
| {{{_ 'activity-checklist-item-added' activity.checklist.title cardLink}}}.
.activity-checklist(href="{{ activity.card.absoluteUrl }}")
+viewer
= activity.checklistItem.title
if($eq activity.activityType 'removedChecklistItem')
| {{{_ 'activity-checklist-item-removed' activity.checklist.title cardLink}}}.
//- comment activity ----------------------------------------------------
if($eq mode 'card')
//- if we are in card mode we display the comment in a way that it
//- can be edited by the owner
if($eq activity.activityType 'addComment')
+inlinedForm(classNames='js-edit-comment')
+editor(autofocus=true)
= comment.text
= activity.comment.text
.edit-controls
button.primary(type="submit") {{_ 'edit'}}
else
.activity-comment
+viewer
= comment.text
span(title=createdAt).activity-meta {{ moment createdAt }}
if ($eq currentUser._id comment.userId)
= activity.comment.text
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
if ($eq currentUser._id activity.comment.userId)
= ' - '
a.js-open-inlined-form {{_ "edit"}}
= ' - '
a.js-delete-comment {{_ "delete"}}
if($eq activity.activityType 'deleteComment')
| {{{_ 'activity-deleteComment' currentData.commentId}}}.
if($eq activity.activityType 'editComment')
| {{{_ 'activity-editComment' currentData.commentId}}}.
else
//- if we are not in card mode we only display a summary of the comment
if($eq activity.activityType 'addComment')
| {{{_ 'activity-on' cardLink}}}
a.activity-comment(href="{{ activity.card.absoluteUrl }}")
+viewer
= activity.comment.text
//- customField activity ------------------------------------------------
if($eq mode 'board')
if($eq activity.activityType 'createCustomField')
| {{_ 'activity-customfield-created' customField}}.
if($eq activity.activityType 'setCustomField')
| {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}.
if($eq activity.activityType 'unsetCustomField')
| {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}.
//- label activity ------------------------------------------------------
if($eq activity.activityType 'addedLabel')
| {{{_ 'activity-added-label' lastLabel cardLink}}}.
if($eq activity.activityType 'removedLabel')
| {{{_ 'activity-removed-label' lastLabel cardLink}}}.
//- list activity -------------------------------------------------------
if($neq mode 'card')
if($eq activity.activityType 'createList')
| {{{_ 'activity-added' listLabel boardLabel}}}.
if($eq activity.activityType 'importList')
| {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}.
if($eq activity.activityType 'removeList')
| {{{_ 'activity-removed' activity.title boardLabel}}}.
if($eq activity.activityType 'archivedList')
| {{_ 'activity-archived' listLabel}}.
//- member activity ----------------------------------------------------
if($eq activity.activityType 'joinMember')
if($eq user._id activity.member._id)
| {{{_ 'activity-joined' cardLink}}}.
else
span(title=createdAt).activity-meta {{ moment createdAt }}
| {{{_ 'activity-added' memberLink cardLink}}}.
if($eq activity.activityType 'unjoinMember')
if($eq user._id activity.member._id)
| {{{_ 'activity-unjoined' cardLink}}}.
else
| {{{_ 'activity-removed' memberLink cardLink}}}.
//- swimlane activity --------------------------------------------------
if($neq mode 'card')
if($eq activity.activityType 'createSwimlane')
| {{{_ 'activity-added' activity.swimlane.title boardLabel}}}.
if($eq activity.activityType 'archivedSwimlane')
| {{_ 'activity-archived' activity.swimlane.title}}.
//- I don't understand this part ----------------------------------------
if(currentData.timeKey)
| {{{_ activity.activityType }}}
= ' '
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
if (currentData.timeOldValue)
= ' '
| {{{_ "previous_as" }}}
= ' '
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
= ' @'
else if(currentData.timeValue)
| {{{_ activity.activityType currentData.timeValue}}}
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
+74 -41
View File
@@ -41,7 +41,9 @@ BlazeComponent.extendComponent({
});
});
},
}).register('activities');
BlazeComponent.extendComponent({
loadNextPage() {
if (this.loadNextPageLocked === false) {
this.page.set(this.page.get() + 1);
@@ -50,41 +52,37 @@ BlazeComponent.extendComponent({
},
checkItem() {
const checkItemId = this.currentData().checklistItemId;
const checkItemId = this.currentData().activity.checklistItemId;
const checkItem = ChecklistItems.findOne({ _id: checkItemId });
return checkItem.title;
return checkItem && checkItem.title;
},
boardLabel() {
const data = this.currentData();
if (data.mode !== 'board') {
return createBoardLink(data.activity.board(), data.activity.listName);
}
return TAPi18n.__('this-board');
},
cardLabel() {
const data = this.currentData();
if (data.mode !== 'card') {
return createCardLink(this.currentData().activity.card());
}
return TAPi18n.__('this-card');
},
cardLink() {
const card = this.currentData().card();
return (
card &&
Blaze.toHTML(
HTML.A(
{
href: card.absoluteUrl(),
class: 'action-card',
},
card.title,
),
)
);
return createCardLink(this.currentData().activity.card());
},
lastLabel() {
const lastLabelId = this.currentData().labelId;
const lastLabelId = this.currentData().activity.labelId;
if (!lastLabelId) return null;
const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(
lastLabelId,
);
const lastLabel = Boards.findOne(
this.currentData().activity.boardId,
).getLabelById(lastLabelId);
if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
return lastLabel.color;
} else {
@@ -94,7 +92,7 @@ BlazeComponent.extendComponent({
lastCustomField() {
const lastCustomField = CustomFields.findOne(
this.currentData().customFieldId,
this.currentData().activity.customFieldId,
);
if (!lastCustomField) return null;
return lastCustomField.name;
@@ -102,10 +100,10 @@ BlazeComponent.extendComponent({
lastCustomFieldValue() {
const lastCustomField = CustomFields.findOne(
this.currentData().customFieldId,
this.currentData().activity.customFieldId,
);
if (!lastCustomField) return null;
const value = this.currentData().value;
const value = this.currentData().activity.value;
if (
lastCustomField.settings.dropdownItems &&
lastCustomField.settings.dropdownItems.length > 0
@@ -122,11 +120,13 @@ BlazeComponent.extendComponent({
},
listLabel() {
return this.currentData().list().title;
const activity = this.currentData().activity;
const list = activity.list();
return (list && list.title) || activity.title;
},
sourceLink() {
const source = this.currentData().source;
const source = this.currentData().activity.source;
if (source) {
if (source.url) {
return Blaze.toHTML(
@@ -146,30 +146,31 @@ BlazeComponent.extendComponent({
memberLink() {
return Blaze.toHTMLWithData(Template.memberName, {
user: this.currentData().member(),
user: this.currentData().activity.member(),
});
},
attachmentLink() {
const attachment = this.currentData().attachment();
const attachment = this.currentData().activity.attachment();
// trying to display url before file is stored generates js errors
return (
attachment &&
attachment.url({ download: true }) &&
Blaze.toHTML(
HTML.A(
{
href: attachment.url({ download: true }),
target: '_blank',
},
attachment.name(),
),
)
(attachment &&
attachment.url({ download: true }) &&
Blaze.toHTML(
HTML.A(
{
href: attachment.url({ download: true }),
target: '_blank',
},
attachment.name(),
),
)) ||
this.currentData().activity.attachmentName
);
},
customField() {
const customField = this.currentData().customField();
const customField = this.currentData().activity.customField();
if (!customField) return null;
return customField.name;
},
@@ -179,7 +180,7 @@ BlazeComponent.extendComponent({
{
// XXX We should use Popup.afterConfirmation here
'click .js-delete-comment'() {
const commentId = this.currentData().commentId;
const commentId = this.currentData().activity.commentId;
CardComments.remove(commentId);
},
'submit .js-edit-comment'(evt) {
@@ -187,7 +188,7 @@ BlazeComponent.extendComponent({
const commentText = this.currentComponent()
.getValue()
.trim();
const commentId = Template.parentData().commentId;
const commentId = Template.parentData().activity.commentId;
if (commentText) {
CardComments.update(commentId, {
$set: {
@@ -199,4 +200,36 @@ BlazeComponent.extendComponent({
},
];
},
}).register('activities');
}).register('activity');
function createCardLink(card) {
return (
card &&
Blaze.toHTML(
HTML.A(
{
href: card.absoluteUrl(),
class: 'action-card',
},
card.title,
),
)
);
}
function createBoardLink(board, list) {
let text = board.title;
if (list) text += `: ${list}`;
return (
board &&
Blaze.toHTML(
HTML.A(
{
href: board.absoluteUrl(),
class: 'action-board',
},
text,
),
)
);
}
+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() {
-14
View File
@@ -193,20 +193,6 @@ template(name="boardChangeViewPopup")
| {{_ 'board-view-cal'}}
if $eq Utils.boardView "board-view-cal"
i.fa.fa-check
if currentUser.isAdmin
hr
li
with "board-view-rules"
a.js-open-rules-view(title="{{_ 'rules'}}")
i.fa.fa-magic
| {{_ 'rules'}}
else if currentUser.isBoardAdmin
hr
li
with "board-view-rules"
a.js-open-rules-view(title="{{_ 'rules'}}")
i.fa.fa-magic
| {{_ 'rules'}}
template(name="createBoard")
form
-20
View File
@@ -33,22 +33,6 @@ Template.boardMenuPopup.events({
'click .js-card-settings': Popup.open('boardCardSettings'),
});
Template.boardMenuPopup.helpers({
exportUrl() {
const params = {
boardId: Session.get('currentBoard'),
};
const queryParams = {
authToken: Accounts._storedLoginToken(),
};
return FlowRouter.path('/api/boards/:boardId/export', params, queryParams);
},
exportFilename() {
const boardId = Session.get('currentBoard');
return `wekan-export-board-${boardId}.json`;
},
});
Template.boardChangeTitlePopup.events({
submit(event, templateInstance) {
const newTitle = templateInstance
@@ -191,10 +175,6 @@ Template.boardChangeViewPopup.events({
Utils.setBoardView('board-view-cal');
Popup.close();
},
'click .js-open-rules-view'() {
Modal.openWide('rulesMain');
Popup.close();
},
});
const CreateBoard = BlazeComponent.extendComponent({
+4 -4
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
@@ -39,7 +39,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'}}")
@@ -55,7 +55,7 @@ template(name="boardList")
title="{{_ 'archive-board'}}")
template(name="boardListHeaderBar")
h1 {{_ 'my-boards'}}
h1 {{_ title }}
.board-header-btns.right
a.board-header-btn.js-open-archived-board
i.fa.fa-archive
+71 -8
View File
@@ -1,4 +1,5 @@
const subManager = new SubsManager();
const { calculateIndex, enableClickOnTouch } = Utils;
Template.boardListHeaderBar.events({
'click .js-open-archived-board'() {
@@ -7,6 +8,9 @@ Template.boardListHeaderBar.events({
});
Template.boardListHeaderBar.helpers({
title() {
return FlowRouter.getRouteName() === 'home' ? 'my-boards' : 'public';
},
templatesBoardId() {
return Meteor.user() && Meteor.user().getTemplatesBoardId();
},
@@ -20,20 +24,79 @@ BlazeComponent.extendComponent({
Meteor.subscribe('setting');
},
boards() {
return Boards.find(
{
archived: false,
'members.userId': Meteor.userId(),
type: 'board',
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');
},
{ sort: ['title'] },
);
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(() => {
$boards.sortable('option', 'disabled', !userIsAllowedToMove());
});
},
boards() {
const query = {
archived: false,
type: 'board',
};
if (FlowRouter.getRouteName() === 'home')
query['members.userId'] = Meteor.userId();
else query.permission = 'public';
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);
+17 -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
+1 -1
View File
@@ -55,5 +55,5 @@ template(name="attachmentsGalery")
unless currentUser.isWorker
//li.attachment-item.add-attachment
a.js-add-attachment
i.fa.fa-paperclip
i.fa.fa-plus
| {{_ 'add-attachment' }}
+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');
+97 -15
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}}
@@ -199,10 +199,35 @@ template(name="cardDetails")
+viewer
= getAssignedBy
if getVoteQuestion
hr
.vote-title
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 }}
a.card-label.card-label-red.js-show-negative-votes {{ voteCountNegative }}
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
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
unless currentUser.isWorker
if currentBoard.allowsDescriptionTitle
hr
h3
i.fa.fa-align-left
card-details-item-title {{_ 'description'}}
@@ -229,6 +254,7 @@ template(name="cardDetails")
a.js-close-inlined-form {{_ 'discard'}}
else if getDescription
if currentBoard.allowsDescriptionTitle
hr
h3.card-details-item-title {{_ 'description'}}
if currentBoard.allowsDescriptionText
+viewer
@@ -237,15 +263,16 @@ template(name="cardDetails")
.card-checklist-attachmentGalerys
.card-checklist-attachmentGalery.card-checklists
if currentBoard.allowsChecklists
hr
+checklists(cardId = _id)
if currentBoard.allowsSubtasks
hr
+subtasks(cardId = _id)
if currentBoard.allowsAttachments
//- hr
//- h3
//- i.fa.fa-paperclip
//- | {{_ 'attachments'}}
hr
h3
i.fa.fa-paperclip
| {{_ 'attachments'}}
.card-checklist-attachmentGalery.card-attachmentGalery
+attachmentsGalery
@@ -312,6 +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'}}
li
a.js-start-voting
i.fa.fa-thumbs-up
| {{_ 'card-edit-voting'}}
li
a.js-custom-fields
i.fa.fa-list-alt
@@ -434,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}})")
@@ -488,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'}}
@@ -533,5 +564,56 @@ 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="{{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.js-submit {{_ 'save'}}
if getVoteQuestion
button.js-remove-vote.negate.wide.right {{_ 'delete'}}
template(name="positiveVoteMembersPopup")
ul.pop-over-list.js-card-member-list
each m in voteMemberPositive
li.item
a.name
+userAvatar(userId=m._id)
span.full-name
= m.profile.fullname
| (<span class="username">{{ m.username }}</span>)
template(name="negativeVoteMembersPopup")
ul.pop-over-list.js-card-member-list
each m in voteMemberNegative
li.item
a.name
+userAvatar(userId=m._id)
span.full-name
= m.profile.fullname
| (<span class="username">{{ m.username }}</span>)
+167 -20
View File
@@ -1,5 +1,5 @@
const subManager = new SubsManager();
const { calculateIndexData, enableClickOnTouch } = Utils;
const { calculateIndexData } = Utils;
let cardColors;
Meteor.startup(() => {
@@ -38,6 +38,22 @@ BlazeComponent.extendComponent({
Meteor.subscribe('unsaved-edits');
},
voteState() {
const card = this.currentData();
const userId = Meteor.userId();
let state;
if (card.vote) {
if (card.vote.positive) {
state = _.contains(card.vote.positive, userId);
if (state === true) return true;
}
if (card.vote.negative) {
state = _.contains(card.vote.negative, userId);
if (state === true) return false;
}
}
return null;
},
isWatching() {
const card = this.currentData();
return card.findWatcher(Meteor.userId());
@@ -117,6 +133,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 ---
@@ -200,9 +225,6 @@ BlazeComponent.extendComponent({
},
});
// ugly touch event hotfix
enableClickOnTouch('.card-checklist-items .js-checklist');
const $subtasksDom = this.$('.card-subtasks-items');
$subtasksDom.sortable({
@@ -238,26 +260,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);
}
});
},
@@ -347,6 +364,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'),
@@ -356,6 +376,8 @@ 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-negative-votes': Popup.open('negativeVoteMembers'),
'mouseenter .js-card-details'() {
const parentComponent = this.parentComponent().parentComponent();
//on mobile view parent is Board, not BoardBody.
@@ -379,6 +401,18 @@ BlazeComponent.extendComponent({
'click #toggleButton'() {
Meteor.call('toggleSystemMessages');
},
'click .js-vote'(e) {
const forIt = $(e.target).hasClass('js-vote-positive');
let newState = null;
if (
this.voteState() === null ||
(this.voteState() === false && forIt) ||
(this.voteState() === true && !forIt)
) {
newState = forIt;
}
this.data().setVote(Meteor.userId(), newState);
},
},
];
},
@@ -560,6 +594,7 @@ Template.cardDetailsActionsPopup.events({
'click .js-assignees': Popup.open('cardAssignees'),
'click .js-labels': Popup.open('cardLabels'),
'click .js-attachments': Popup.open('cardAttachments'),
'click .js-start-voting': Popup.open('cardStartVoting'),
'click .js-custom-fields': Popup.open('cardCustomFields'),
'click .js-received-date': Popup.open('editCardReceivedDate'),
'click .js-start-date': Popup.open('editCardStartDate'),
@@ -672,7 +707,7 @@ BlazeComponent.extendComponent({
_id: { $ne: Meteor.user().getTemplatesBoardId() },
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -848,7 +883,7 @@ BlazeComponent.extendComponent({
},
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -921,7 +956,23 @@ BlazeComponent.extendComponent({
},
'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) {
@@ -945,6 +996,102 @@ BlazeComponent.extendComponent({
},
}).register('cardMorePopup');
BlazeComponent.extendComponent({
onCreated() {
this.currentCard = this.currentData();
this.voteQuestion = new ReactiveVar(this.currentCard.voteQuestion);
},
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');
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',
+24 -3
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,17 +99,18 @@ 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
.ps-scrollbar-y-rail
pointer-event: all
position: absolute;
position: absolute
.card-details-canvas
width: 470px
padding-left: 20px;
padding-left: 20px
.card-details-header
margin: 0 -20px 5px
@@ -241,7 +247,7 @@ input[type="submit"].attachment-add-link-submit
.card-details-canvas
width: 100%
padding-left: 0px;
padding-left: 0px
.card-details-header
.close-card-details
@@ -330,3 +336,18 @@ card-details-color(background, color...)
.card-details-indigo
card-details-color(#4b0082, #ffffff) //White text for better visibility
.voted
opacity: .7
.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
cursor: pointer
+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
+8 -2
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
@@ -100,6 +100,12 @@ template(name="minicard")
if getDescription
.badge.badge-state-image-only(title=getDescription)
span.badge-icon.fa.fa-align-left
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
+1 -1
View File
@@ -79,7 +79,7 @@
border-radius: top 2px
.minicard-labels
float: right
float: none
display: flex
flex-wrap: wrap
+16 -1
View File
@@ -20,7 +20,22 @@ BlazeComponent.extendComponent({
const crtBoard = Boards.findOne(card.boardId);
const targetBoard = crtBoard.getDefaultSubtasksBoard();
const listId = targetBoard.getDefaultSubtasksListId();
const swimlaneId = targetBoard.getDefaultSwimline()._id;
//Get the full swimlane data for the parent task.
const parentSwimlane = Swimlanes.findOne({
boardId: crtBoard._id,
_id: card.swimlaneId,
});
//find the swimlane of the same name in the target board.
const targetSwimlane = Swimlanes.findOne({
boardId: targetBoard._id,
title: parentSwimlane.title,
});
//If no swimlane with a matching title exists in the target board, fall back to the default swimlane.
const swimlaneId =
targetSwimlane === undefined
? targetBoard.getDefaultSwimline()._id
: targetSwimlane._id;
if (title) {
const _id = Cards.insert({
@@ -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'),
);
},
},
];
+4 -18
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
@@ -114,9 +114,6 @@ BlazeComponent.extendComponent({
},
});
// ugly touch event hotfix
enableClickOnTouch(itemsSelector);
this.autorun(() => {
let showDesktopDragHandles = false;
currentUser = Meteor.user();
@@ -129,7 +126,7 @@ BlazeComponent.extendComponent({
showDesktopDragHandles = false;
}
if (!Utils.isMiniScreen() && showDesktopDragHandles) {
if (Utils.isMiniScreen() || showDesktopDragHandles) {
$cards.sortable({
handle: '.handle',
});
@@ -139,27 +136,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
+22 -9
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);
@@ -743,9 +740,25 @@ BlazeComponent.extendComponent({
},
updateList() {
// Use fallback when requestIdleCallback is not available on iOS and Safari
// https://www.afasterweb.com/2017/11/20/utilizing-idle-moments/
checkIdleTime =
window.requestIdleCallback ||
function(handler) {
const startTime = Date.now();
return setTimeout(function() {
handler({
didTimeout: false,
timeRemaining() {
return Math.max(0, 50.0 - (Date.now() - startTime));
},
});
}, 1);
};
if (this.spinnerInView()) {
this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter);
window.requestIdleCallback(() => this.updateList());
checkIdleTime(() => this.updateList());
}
},
+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);
}),
});
+65 -129
View File
@@ -1,87 +1,3 @@
import _sanitizeXss from 'xss';
const ASIS = 'asis';
const sanitizeXss = (input, options) => {
const defaultAllowedIframeSrc = /^(https:){0,1}\/\/.*?(youtube|vimeo|dailymotion|youku)/i;
const allowedIframeSrcRegex = (function() {
let reg = defaultAllowedIframeSrc;
const SAFE_IFRAME_SRC_PATTERN =
Meteor.settings.public.SAFE_IFRAME_SRC_PATTERN;
try {
if (SAFE_IFRAME_SRC_PATTERN !== undefined) {
reg = new RegExp(SAFE_IFRAME_SRC_PATTERN, 'i');
}
} catch (e) {
/*eslint no-console: ["error", { allow: ["warn", "error"] }] */
console.error('Wrong pattern specified', SAFE_IFRAM_SRC_PATTERN, e);
}
return reg;
})();
const targetWindow = '_blank';
const getHtmlDOM = html => {
const i = document.createElement('i');
i.innerHTML = html;
return i.firstChild;
};
options = {
onTag(tag, html, options) {
const htmlDOM = getHtmlDOM(html);
const getAttr = attr => {
return htmlDOM && attr && htmlDOM.getAttribute(attr);
};
if (tag === 'iframe') {
const clipCls = 'note-vide-clip';
if (!options.isClosing) {
const iframeCls = getAttr('class');
let safe = iframeCls.indexOf(clipCls) > -1;
const src = getAttr('src');
if (allowedIframeSrcRegex.exec(src)) {
safe = true;
}
if (safe)
return `<iframe src='${src}' class="${clipCls}" width=100% height=auto allowfullscreen></iframe>`;
} else {
// remove </iframe> tag
return '';
}
} else if (tag === 'a') {
if (!options.isClosing) {
if (getAttr(ASIS) === 'true') {
// if has a ASIS attribute, don't do anything, it's a member id
return html;
} else {
const href = getAttr('href');
if (href.match(/^((http(s){0,1}:){0,1}\/\/|\/)/)) {
// a valid url
return `<a href=${href} target=${targetWindow}>`;
}
}
}
} else if (tag === 'img') {
if (!options.isClosing) {
const src = getAttr('src');
if (src) {
return `<a href='${src}' class='swipebox'><img src='${src}' class="attachment-image-preview mCS_img_loaded"></a>`;
}
}
}
return undefined;
},
onTagAttr(tag, name, value) {
if (tag === 'img' && name === 'src') {
if (value && value.substr(0, 5) === 'data:') {
// allow image with dataURI src
return `${name}='${value}'`;
}
} else if (tag === 'a' && name === 'target') {
return `${name}='${targetWindow}'`; // always change a href target to a new window
}
return undefined;
},
...options,
};
return _sanitizeXss(input, options);
};
Template.editor.onRendered(() => {
const textareaSelector = 'textarea';
const mentions = [
@@ -94,13 +10,7 @@ Template.editor.onRendered(() => {
currentBoard
.activeMembers()
.map(member => {
const user = Users.findOne(member.userId);
if (user._id === Meteor.userId()) {
return null;
}
const value = user.username;
const username =
value && value.match(/\s+/) ? `"${value}"` : value;
const username = Users.findOne(member.userId).username;
return username.includes(term) ? username : null;
})
.filter(Boolean),
@@ -126,10 +36,9 @@ Template.editor.onRendered(() => {
? [
['view', ['fullscreen']],
['table', ['table']],
['font', ['bold']],
['color', ['color']],
['insert', ['video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
['font', ['bold', 'underline']],
//['fontsize', ['fontsize']],
['color', ['color']],
]
: [
['style', ['style']],
@@ -139,11 +48,47 @@ Template.editor.onRendered(() => {
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
//['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
//['insert', ['link', 'picture']], // modal popup has issue somehow :(
['view', ['fullscreen', 'help']],
];
const cleanPastedHTML = sanitizeXss;
const cleanPastedHTML = function(input) {
const badTags = [
'style',
'script',
'applet',
'embed',
'noframes',
'noscript',
'meta',
'link',
'button',
'form',
].join('|');
const badPatterns = new RegExp(
`(?:${[
`<(${badTags})s*[^>][\\s\\S]*?<\\/\\1>`,
`<(${badTags})[^>]*?\\/>`,
].join('|')})`,
'gi',
);
let output = input;
// remove bad Tags
output = output.replace(badPatterns, '');
// remove attributes ' style="..."'
const badAttributes = new RegExp(
`(?:${[
'on\\S+=([\'"]?).*?\\1',
'href=([\'"]?)javascript:.*?\\2',
'style=([\'"]?).*?\\3',
'target=\\S+',
].join('|')})`,
'gi',
);
output = output.replace(badAttributes, '');
output = output.replace(/(<a )/gi, '$1target=_ '); // always to new target
return output;
};
const editor = '.editor';
const selectors = [
`.js-new-comment-form ${editor}`,
@@ -163,37 +108,14 @@ Template.editor.onRendered(() => {
}
return undefined;
};
let popupShown = false;
inputs.each(function(idx, input) {
mSummernotes[idx] = $(input).summernote({
placeholder,
callbacks: {
onKeydown(e) {
if (popupShown) {
e.preventDefault();
}
},
onKeyup(e) {
if (popupShown) {
e.preventDefault();
}
},
onInit(object) {
const originalInput = this;
const setAutocomplete = function(jEditor) {
if (jEditor !== undefined) {
jEditor.escapeableTextComplete(mentions).on({
'textComplete:show'() {
popupShown = true;
},
'textComplete:hide'() {
popupShown = false;
},
});
}
};
$(originalInput).on('submitted', function() {
// resetCommentInput has been called
// when comment is submitted, the original textarea will be set to '', so shall we
if (!this.value) {
const sn = getSummernote(this);
sn && sn.summernote('code', '');
@@ -201,7 +123,9 @@ Template.editor.onRendered(() => {
});
const jEditor = object && object.editable;
const toolbar = object && object.toolbar;
setAutocomplete(jEditor);
if (jEditor !== undefined) {
jEditor.escapeableTextComplete(mentions);
}
if (toolbar !== undefined) {
const fBtn = toolbar.find('.btn-fullscreen');
fBtn.on('click', function() {
@@ -211,6 +135,7 @@ Template.editor.onRendered(() => {
});
}
},
onImageUpload(files) {
const $summernote = getSummernote(this);
if (files && files.length > 0) {
@@ -289,6 +214,12 @@ Template.editor.onRendered(() => {
const thisNote = this;
const updatePastedText = function(object) {
const someNote = getSummernote(object);
// Fix Pasting text into a card is adding a line before and after
// (and multiplies by pasting more) by changing paste "p" to "br".
// Fixes https://github.com/wekan/wekan/2890 .
// == Fix Start ==
someNote.execCommand('defaultParagraphSeparator', false, 'br');
// == Fix End ==
const original = someNote.summernote('code');
const cleaned = cleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML.
someNote.summernote('code', ''); //clear original
@@ -331,6 +262,8 @@ Template.editor.onRendered(() => {
}
});
import sanitizeXss from 'xss';
// XXX I believe we should compute a HTML rendered field on the server that
// would handle markdown and user mentions. We can simply have two
// fields, one source, and one compiled version (in HTML) and send only the
@@ -352,7 +285,7 @@ Blaze.Template.registerHelper(
}
return member;
});
const mentionRegex = /\B@(?:(?:"([\w.\s]*)")|([\w.]+))/gi; // including space in username
const mentionRegex = /\B@([\w.]*)/gi;
let currentMention;
while ((currentMention = mentionRegex.exec(content)) !== null) {
@@ -368,7 +301,12 @@ Blaze.Template.registerHelper(
if (knowedUser.userId === Meteor.userId()) {
linkClass += ' me';
}
const link = HTML.A(
// This @user mention link generation did open same Wekan
// window in new tab, so now A is changed to U so it's
// underlined and there is no link popup. This way also
// text can be selected more easily.
//const link = HTML.A(
const link = HTML.U(
{
class: linkClass,
// XXX Hack. Since we stringify this render function result below with
@@ -376,31 +314,29 @@ Blaze.Template.registerHelper(
// `userId` to the popup as usual, and we need to store it in the DOM
// using a data attribute.
'data-userId': knowedUser.userId,
[ASIS]: 'true',
},
linkValue,
);
content = content.replace(fullMention, Blaze.toHTML(link));
}
return HTML.Raw(sanitizeXss(content));
}),
);
Template.viewer.events({
// Viewer sometimes have click-able wrapper around them (for instance to edit
// 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);
} else {
const href = event.currentTarget.href;
const child = event.currentTarget.firstElementChild;
if (child && child.tagName === 'IMG') {
prevent = false;
} else if (href) {
if (href) {
window.open(href, '_blank');
}
}
+7
View File
@@ -24,6 +24,11 @@ template(name="header")
a(href="{{pathFor 'home'}}")
span.fa.fa-home
| {{_ 'all-boards'}}
li.separator -
li
a(href="{{pathFor 'public'}}")
span.fa.fa-globe
| {{_ 'public'}}
each currentUser.starredBoards
li.separator -
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
@@ -35,6 +40,8 @@ template(name="header")
a#header-new-board-icon.js-create-board
i.fa.fa-plus(title="Create a new board")
+notifications
+headerUserBar
#header(class=currentBoard.colorClass)
+3 -3
View File
@@ -99,7 +99,7 @@
height: 28px
font-size: 12px
display: flex
z-index: 17
z-index: 21
#header-user-bar,
#header-new-board-icon,
@@ -127,7 +127,7 @@
&.current
color: darken(white, 5%)
&:first-child .fa-home
&:first-child .fa-home,&:nth-child(3) .fa-globe
margin-right: 5px
a.js-create-board
@@ -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
+7
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(() => {
@@ -73,6 +78,8 @@ Template.userFormsLayout.helpers({
name = 'Igbo';
} else if (lang.name === 'oc') {
name = 'Occitan';
} else if (lang.name === '繁体中文(台湾)') {
name = '繁體中文(台灣)';
}
return { tag, name };
}).sort(function(a, b) {
+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
@@ -0,0 +1,10 @@
template(name='notification')
li.notification(class="{{#if read}}read{{/if}}")
.read-status
.materialCheckBox(class="{{#if read}}is-checked{{/if}}")
+notificationIcon(activityData)
.details
+activity(activity=activityData mode='none')
if read
.remove
a.fa.fa-trash
@@ -0,0 +1,28 @@
Template.notification.events({
'click .read-status .materialCheckBox'() {
const update = {};
update[`profile.notifications.${this.index}.read`] = this.read
? null
: Date.now();
Users.update(Meteor.userId(), { $set: update });
},
'click .remove a'() {
Meteor.user().removeNotification(this.activityData._id);
},
});
Template.notification.helpers({
mode: 'board',
isOfActivityType(activityId, type) {
const activity = Activities.findOne(activityId);
return activity && activity.activityType === type;
},
activityType(activityId) {
const activity = Activities.findOne(activityId);
return activity ? activity.activityType : '';
},
activityUser(activityId) {
const activity = Activities.findOne(activityId);
return activity && activity.userId;
},
});
@@ -0,0 +1,57 @@
#notifications-drawer
&.show-read .notification.read
display: flex
.notification
display: flex
float: none
padding: 12px 8px 8px
color: black
border-bottom: 1px solid #dbdbdb
&.read
display: none
.read-status
width: 30px
input
width: 24px
height: 24px
.activity-type
margin: 16px 0 0
width: 17px
height: 17px
font-size: 17px
display: block
color: #bbb
.details
width: calc(100% - 30px)
.activity
display: flex
.activity-desc
width: 100%;
.activity-comment
display: block
width: 100%
border-radius: 3px
background: #fff
text-decoration: none
box-shadow: 0 1px 2px rgba(0,0,0,0.2)
margin-top: 5px
padding: 5px
.activity-meta
display: block
font-size: 0.8em
color: #999
font-style: italic
.remove
a:hover
color #eb4646 !important
@@ -0,0 +1,53 @@
template(name='notificationIcon')
if($in activityType 'deleteAttachment' 'addAttachment')
i.fa.fa-paperclip.activity-type(title="attachment")
else if($in activityType 'createBoard' 'importBoard')
i.fa.fa-chalkboard.activity-type(title="board")
else if($in activityType 'createCard' 'importCard' 'moveCard')
+cardNotificationIcon
else if($in activityType 'moveCardBoard' 'archivedCard' 'restoredCard')
+cardNotificationIcon
//- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
//- DRY and consistant
else if($in activityType 'addChecklist' 'removedChecklist' 'completeChecklist')
+checklistNotificationIcon
else if($in activityType 'uncompleteChecklist')
+checklistNotificationIcon
//- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
//- DRY and consistant
else if($in activityType 'checkedItem' 'uncheckedItem' 'addChecklistItem' 'removedChecklistItem')
i.fa.fa-check-square.activity-type(title="checklist item")
else if($in activityType 'addComment')
i.fa.fa-comment-o.activity-type(title="comment")
else if($in activityType 'createCustomField' 'setCustomField' 'unsetCustomField')
i.fa.fa-code.activity-type(title="custom field")
else if($in activityType 'addedLabel' 'removedLabel')
i.fa.fa-tag.activity-type(title="label")
else if($in activityType 'createList' 'removeList' 'archivedList')
+listNotificationIcon
else if($in activityType 'importList')
+listNotificationIcon
//- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
//- DRY and consistant
//- elswhere in the app we use fa-trello to indicate lists...
//- i personally like fa-columns a bit better
else if($in activityType 'unjoinMember' 'addBoardMember' 'joinMember' 'removeBoardMember')
i.fa.fa-user.activity-type(title="member")
else if($in activityType 'createSwimlane' 'archivedSwimlane')
i.fa.fa-th-large.activity-type(title="swimlane")
else
i.fa.fa-bug.activity-type(title="can't find icon for #{activityType}")
template(name='cardNotificationIcon')
i.fa.fa-clone.activity-type(title="card")
template(name='checklistNotificationIcon')
i.fa.fa-list.activity-type(title="checklist")
template(name='listNotificationIcon')
i.fa.fa-columns.activity-type(title="list")
@@ -0,0 +1,5 @@
template(name='notifications')
#notifications.board-header-btns.right
a.notifications-drawer-toggle.fa.fa-bell(class="{{#if $gt unreadNotifications 0}}alert{{/if}}")
if $.Session.get 'showNotificationsDrawer'
+notificationsDrawer(unreadNotifications=unreadNotifications)
@@ -0,0 +1,32 @@
// this hides the notifications drawer if anyone clicks off of the panel
Template.body.events({
click(event) {
if (
!$(event.target).is('#notifications *') &&
Session.get('showNotificationsDrawer')
) {
toggleNotificationsDrawer();
}
},
});
Template.notifications.helpers({
unreadNotifications() {
const notifications = Users.findOne(Meteor.userId()).notifications();
const unreadNotifications = _.filter(notifications, v => !v.read);
return unreadNotifications.length;
},
});
Template.notifications.events({
'click .notifications-drawer-toggle'() {
toggleNotificationsDrawer();
},
});
export function toggleNotificationsDrawer() {
Session.set(
'showNotificationsDrawer',
!Session.get('showNotificationsDrawer'),
);
}
@@ -0,0 +1,17 @@
#notifications
position: relative
.notifications-drawer-toggle
display: block
line-height: 28px
color: #f2f2f2
margin: 0 10px
width: 28px
height: 28px
text-align: center
border: 0
padding: 0
&.alert
background-color: #eb4646;
@@ -0,0 +1,20 @@
template(name='notificationsDrawer')
section#notifications-drawer(class="{{#if $.Session.get 'showReadNotifications'}}show-read{{/if}}")
.header
if $.Session.get 'showReadNotifications'
a.toggle-read {{_ 'filter-by-unread'}}
else
a.toggle-read {{_ 'view-all'}}
h5 {{_ 'notifications'}}
if($gt unreadNotifications 0)
|(#{unreadNotifications})
a.fa.fa-times-thin.close
ul.notifications
each transformedProfile.notifications
+notification(activityData=activity index=dbIndex read=read)
if($gt unreadNotifications 0)
a.all-read {{_ 'mark-all-as-read'}}
if ($and ($.Session.get 'showReadNotifications') ($gt readNotifications 0))
a.remove-read
i.fa.fa-trash
| {{_ 'remove-all-read'}}
@@ -0,0 +1,53 @@
import { toggleNotificationsDrawer } from './notifications.js';
Template.notificationsDrawer.onCreated(function() {
Meteor.subscribe('notificationActivities');
Meteor.subscribe('notificationCards');
Meteor.subscribe('notificationUsers');
Meteor.subscribe('notificationsAttachments');
Meteor.subscribe('notificationChecklistItems');
Meteor.subscribe('notificationChecklists');
Meteor.subscribe('notificationComments');
Meteor.subscribe('notificationLists');
Meteor.subscribe('notificationSwimlanes');
});
Template.notificationsDrawer.helpers({
transformedProfile() {
return Users.findOne(Meteor.userId());
},
readNotifications() {
const readNotifications = _.filter(
Meteor.user().profile.notifications,
v => !!v.read,
);
return readNotifications.length;
},
});
Template.notificationsDrawer.events({
'click .all-read'() {
const notifications = Meteor.user().profile.notifications;
for (const index in notifications) {
if (notifications.hasOwnProperty(index) && !notifications[index].read) {
const update = {};
update[`profile.notifications.${index}.read`] = Date.now();
Users.update(Meteor.userId(), { $set: update });
}
}
},
'click .close'() {
toggleNotificationsDrawer();
},
'click .toggle-read'() {
Session.set('showReadNotifications', !Session.get('showReadNotifications'));
},
'click .remove-read'() {
const user = Meteor.user();
for (const notification of user.profile.notifications) {
if (notification.read) {
user.removeNotification(notification.activity);
}
}
},
});
@@ -0,0 +1,69 @@
belize = #2980b9
section#notifications-drawer
position: fixed
top: 28px
right: 0
width: 400px
background-color: #fafafa
box-shadow: 0 1px 2px rgba(0,0,0,0.15)
border-radius: 2px
max-height: calc(100vh - 28px - 36px)
color: black
padding-top 36px
a:hover
color: belize !important
.header
position: fixed
top 28px
right 0
width calc(400px - 32px)
padding: 8px 16px
background: #ededed
border-bottom: 1px solid #dbdbdb
z-index 2
.toggle-read
position absolute
left 16px
top calc(50% - 8px)
color belize
h5
text-align: center
margin: 0
.close
position: absolute
top: calc(50% - 12px)
right: 12px
font-size: 24px
height: 24px
line-height: 24px
opacity 1
.all-read,
.remove-read
color belize
background-color: #fafafa
margin 8px 16px 12px
display inline-block
.remove-read
float right
&:hover
color #eb4646 !important
i.fa
color inherit
ul.notifications
display: block
padding: 0px 16px
margin: 0
height: calc(100vh - 102px)
overflow-y: scroll
@@ -11,7 +11,7 @@ BlazeComponent.extendComponent({
},
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
+53 -1
View File
@@ -40,9 +40,15 @@ template(name="peopleGeneral")
th {{_ 'active'}}
th {{_ 'authentication-method'}}
th
+newUserRow
each user in peopleList
+peopleRow(userId=user._id)
template(name="newUserRow")
a.new-user
i.fa.fa-edit
| {{_ 'new'}}
template(name="peopleRow")
tr
if userData.loginDisabled
@@ -104,7 +110,7 @@ template(name="editUserPopup")
label.hide.userId(type="text" value=user._id)
label
| {{_ 'fullname'}}
input.js-profile-fullname(type="text" value=user.profile.fullname autofocus)
input.js-profile-fullname(type="text" value=user.profile.fullname)
label
| {{_ 'username'}}
span.error.hide.username-taken
@@ -148,3 +154,49 @@ template(name="editUserPopup")
// div
// input#deleteButton.primary.wide(type="button" value="{{_ 'delete'}}")
template(name="newUserPopup")
form
//label.hide.userId(type="text" value=user._id)
label
| {{_ 'fullname'}}
input.js-profile-fullname(type="text" value="")
label
| {{_ 'username'}}
span.error.hide.username-taken
| {{_ 'error-username-taken'}}
//if isLdap
// input.js-profile-username(type="text" value=user.username readonly)
//else
input.js-profile-username(type="text" value="")
label
| {{_ 'email'}}
span.error.hide.email-taken
| {{_ 'error-email-taken'}}
//if isLdap
// input.js-profile-email(type="email" value="{{user.emails.[0].address}}" readonly)
//else
input.js-profile-email(type="email" value="")
label
| {{_ 'admin'}}
select.select-role.js-profile-isadmin
option(value="false" selected="selected") {{_ 'no'}}
option(value="true") {{_ 'yes'}}
label
| {{_ 'active'}}
select.select-active.js-profile-isactive
option(value="false" selected="selected") {{_ 'yes'}}
option(value="true") {{_ 'no'}}
label
| {{_ 'authentication-type'}}
select.select-authenticationMethod.js-authenticationMethod
each authentications
if isSelected value
option(value="{{value}}" selected) {{_ value}}
else
option(value="{{value}}") {{_ value}}
hr
label
| {{_ 'password'}}
input.js-profile-password(type="password")
div.buttonsContainer
input.primary.wide(type="submit" value="{{_ 'save'}}")
+95
View File
@@ -39,6 +39,9 @@ BlazeComponent.extendComponent({
this.filterPeople();
}
},
'click #newUserButton'() {
Popup.open('newUser');
},
},
];
},
@@ -141,6 +144,47 @@ Template.editUserPopup.helpers({
},
});
Template.newUserPopup.onCreated(function() {
this.authenticationMethods = new ReactiveVar([]);
this.errorMessage = new ReactiveVar('');
Meteor.call('getAuthenticationsEnabled', (_, result) => {
if (result) {
// TODO : add a management of different languages
// (ex {value: ldap, text: TAPi18n.__('ldap', {}, T9n.getLanguage() || 'en')})
this.authenticationMethods.set([
{ value: 'password' },
// Gets only the authentication methods availables
...Object.entries(result)
.filter(e => e[1])
.map(e => ({ value: e[0] })),
]);
}
});
});
Template.newUserPopup.helpers({
//user() {
// return Users.findOne(this.userId);
//},
authentications() {
return Template.instance().authenticationMethods.get();
},
//isSelected(match) {
// const userId = Template.instance().data.userId;
// const selected = Users.findOne(userId).authenticationMethod;
// return selected === match;
//},
//isLdap() {
// const userId = Template.instance().data.userId;
// const selected = Users.findOne(userId).authenticationMethod;
// return selected === 'ldap';
//},
errorMessage() {
return Template.instance().errorMessage.get();
},
});
BlazeComponent.extendComponent({
onCreated() {},
user() {
@@ -155,6 +199,16 @@ BlazeComponent.extendComponent({
},
}).register('peopleRow');
BlazeComponent.extendComponent({
events() {
return [
{
'click a.new-user': Popup.open('newUser'),
},
];
},
}).register('newUserRow');
Template.editUserPopup.events({
submit(event, templateInstance) {
event.preventDefault();
@@ -248,3 +302,44 @@ Template.editUserPopup.events({
Popup.close();
}),
});
Template.newUserPopup.events({
submit(event, templateInstance) {
event.preventDefault();
const fullname = templateInstance.find('.js-profile-fullname').value.trim();
const username = templateInstance.find('.js-profile-username').value.trim();
const password = templateInstance.find('.js-profile-password').value;
const isAdmin = templateInstance.find('.js-profile-isadmin').value.trim();
const isActive = templateInstance.find('.js-profile-isactive').value.trim();
const email = templateInstance.find('.js-profile-email').value.trim();
Meteor.call(
'setCreateUser',
fullname,
username,
password,
isAdmin,
isActive,
email.toLowerCase(),
function(error) {
const usernameMessageElement = templateInstance.$('.username-taken');
const emailMessageElement = templateInstance.$('.email-taken');
if (error) {
const errorElement = error.error;
if (errorElement === 'username-already-taken') {
usernameMessageElement.show();
emailMessageElement.hide();
} else if (errorElement === 'email-already-taken') {
usernameMessageElement.hide();
emailMessageElement.show();
}
} else {
usernameMessageElement.hide();
emailMessageElement.hide();
Popup.close();
}
},
);
Popup.close();
},
});
+1 -1
View File
@@ -48,7 +48,7 @@ BlazeComponent.extendComponent({
'members.isAdmin': true,
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
},
+38 -12
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'}}
@@ -245,7 +247,7 @@ template(name="outgoingWebhooksPopup")
b &nbsp;
.materialCheckBox(class="{{#unless enabled}}is-checked{{/unless}}")
input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" value=title)
input.js-outgoing-webhooks-url(type="text" name="url" value=url autofocus)
input.js-outgoing-webhooks-url(type="text" name="url" value=url)
input.js-outgoing-webhooks-token(placeholder="{{_ 'webhook-token' }}" type="text" value=token name="token")
select.js-outgoing-webhooks-type(name="type")
each _type in types
@@ -257,7 +259,7 @@ template(name="outgoingWebhooksPopup")
input(type="hidden" value=_id name="id")
input.primary.wide(type="submit" value="{{_ 'save'}}")
form.integration-form
input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" autofocus)
input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title")
input.js-outgoing-webhooks-url(placeholder="{{_ 'URL' }}" type="text" name="url")
input.js-outgoing-webhooks-token(placeholder="{{_ 'webhook-token' }}" type="text" name="token")
select.js-outgoing-webhooks-type(name="type")
@@ -267,7 +269,14 @@ template(name="outgoingWebhooksPopup")
template(name="boardMenuPopup")
ul.pop-over-list
li: a.js-custom-fields {{_ 'custom-fields'}}
li
a.js-open-rules-view(title="{{_ 'rules'}}")
i.fa.fa-magic
| {{_ 'rules'}}
li
a.js-custom-fields
i.fa.fa-list-alt
| {{_ 'custom-fields'}}
li
a.js-open-archives
i.fa.fa-archive
@@ -291,10 +300,11 @@ template(name="boardMenuPopup")
if currentUser.isBoardAdmin
hr
ul.pop-over-list
li
a(href="{{exportUrl}}", download="{{exportFilename}}")
i.fa.fa-share-alt
| {{_ 'export-board'}}
if withApi
li
a.js-export-board
i.fa.fa-share-alt
| {{_ 'export-board'}}
li
a.js-outgoing-webhooks
i.fa.fa-globe
@@ -319,11 +329,12 @@ template(name="boardMenuPopup")
if isSandstorm
hr
ul.pop-over-list
li
a(href="{{exportUrl}}", download="{{exportFilename}}")
i.fa.fa-share-alt
i.fa.fa-sign-out
| {{_ 'export-board'}}
if withApi
li
a(href="{{exportUrl}}", download="{{exportFilename}}")
i.fa.fa-share-alt
i.fa.fa-sign-out
| {{_ 'export-board'}}
li
a.js-import-board
i.fa.fa-share-alt
@@ -351,6 +362,21 @@ template(name="boardMenuPopup")
i.fa.fa-sitemap
| {{_ 'subtask-settings'}}
template(name="exportBoard")
ul.pop-over-list
li
a(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'}}
template(name="labelsWidget")
.board-widget.board-widget-labels
h3
+74 -2
View File
@@ -182,6 +182,10 @@ Template.memberPopup.helpers({
Template.boardMenuPopup.events({
'click .js-rename-board': Popup.open('boardChangeTitle'),
'click .js-open-rules-view'() {
Modal.openWide('rulesMain');
Popup.close();
},
'click .js-custom-fields'() {
Sidebar.setView('customFields');
Popup.close();
@@ -209,9 +213,20 @@ 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() {
this.apiEnabled = new ReactiveVar(false);
Meteor.call('_isApiEnabled', (e, result) => {
this.apiEnabled.set(result);
});
});
Template.boardMenuPopup.helpers({
withApi() {
return Template.instance().apiEnabled.get();
},
exportUrl() {
const params = {
boardId: Session.get('currentBoard'),
@@ -391,6 +406,63 @@ 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.labelsWidget.events({
'click .js-label': Popup.open('editLabel'),
'click .js-add-label': Popup.open('createLabel'),
@@ -495,7 +567,7 @@ BlazeComponent.extendComponent({
'members.userId': Meteor.userId(),
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
},
@@ -673,7 +745,7 @@ BlazeComponent.extendComponent({
'members.userId': Meteor.userId(),
},
{
sort: ['title'],
sort: { sort: 1 /* boards default sorting */ },
},
);
},
@@ -45,6 +45,24 @@ template(name="filterSidebar")
if Filter.members.isSelected _id
i.fa.fa-check
hr
ul.sidebar-list
li(class="{{#if Filter.assignees.isSelected undefined}}active{{/if}}")
a.name.js-toggle-assignee-filter
span.sidebar-list-item-description
| {{_ 'filter-no-assignee'}}
if Filter.assignees.isSelected undefined
i.fa.fa-check
each currentBoard.activeMembers
with getUser userId
li(class="{{#if Filter.assignees.isSelected _id}}active{{/if}}")
a.name.js-toggle-assignee-filter
+userAvatar(userId=this._id)
span.sidebar-list-item-description
= profile.fullname
| (<span class="username">{{ username }}</span>)
if Filter.assignees.isSelected _id
i.fa.fa-check
hr
ul.sidebar-list
li(class="{{#if Filter.customFields.isSelected undefined}}active{{/if}}")
a.name.js-toggle-custom-fields-filter
@@ -18,6 +18,11 @@ BlazeComponent.extendComponent({
Filter.members.toggle(this.currentData()._id);
Filter.resetExceptions();
},
'click .js-toggle-assignee-filter'(evt) {
evt.preventDefault();
Filter.assignees.toggle(this.currentData()._id);
Filter.resetExceptions();
},
'click .js-toggle-archive-filter'(evt) {
evt.preventDefault();
Filter.archive.toggle(this.currentData()._id);
+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
+17 -8
View File
@@ -98,12 +98,12 @@ template(name="changeLanguagePopup")
template(name="changeSettingsPopup")
ul.pop-over-list
li
a.js-toggle-system-messages
i.fa.fa-comments-o
| {{_ 'hide-system-messages'}}
if hiddenSystemMessages
i.fa.fa-check
//li
// a.js-toggle-system-messages
// i.fa.fa-comments-o
// | {{_ 'hide-system-messages'}}
// if hiddenSystemMessages
// i.fa.fa-check
li
a.js-toggle-desktop-drag-handles
i.fa.fa-arrows
@@ -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
+37 -3
View File
@@ -166,6 +166,8 @@ Template.changeLanguagePopup.helpers({
name = 'Igbo';
} else if (lang.name === 'oc') {
name = 'Occitan';
} else if (lang.name === '繁体中文(台湾)') {
name = '繁體中文(台灣)';
}
return { tag, name };
}).sort(function(a, b) {
@@ -222,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({
@@ -245,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',
+9 -1
View File
@@ -459,13 +459,21 @@ Filter = {
// before changing the schema.
labelIds: new SetFilter(),
members: new SetFilter(),
assignees: new SetFilter(),
archive: new SetFilter(),
hideEmpty: new SetFilter(),
customFields: new SetFilter('_id'),
advanced: new AdvancedFilter(),
lists: new AdvancedFilter(), // we need the ability to filter list by name as well
_fields: ['labelIds', 'members', 'archive', 'hideEmpty', 'customFields'],
_fields: [
'labelIds',
'members',
'assignees',
'archive',
'hideEmpty',
'customFields',
],
// We don't filter cards that have been added after the last filter change. To
// implement this we keep the id of these cards in this `_exceptions` fields
+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',
},
],
+21
View File
@@ -26,6 +26,27 @@ FlowRouter.route('/', {
},
});
FlowRouter.route('/public', {
name: 'public',
triggersEnter: [AccountsTemplates.ensureSignedIn],
action() {
Session.set('currentBoard', null);
Session.set('currentList', null);
Session.set('currentCard', null);
Filter.reset();
EscapeActions.executeAll();
Utils.manageCustomUI();
Utils.manageMatomo();
BlazeLayout.render('defaultLayout', {
headerBar: 'boardListHeaderBar',
content: 'boardList',
});
},
});
FlowRouter.route('/b/:id/:slug', {
name: 'board',
action(params) {
+20 -3
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
@@ -93,7 +93,7 @@ services:
#-------------------------------------------------------------------------------------
# ==== MONGODB AND METEOR VERSION ====
# a) For Wekan Meteor 1.8.x version at master branch, use mongo 4.x
image: mongo:4.2.2
image: mongo:latest
# b) For Wekan Meteor 1.6.x version at devel branch.
# Only for Snap and Sandstorm while they are not upgraded yet to Meteor 1.8.x
#image: mongo:3.2.21
@@ -240,6 +240,11 @@ services:
# https://github.com/wekan/wekan/pull/2560
- RICHER_CARD_COMMENT_EDITOR=false
#---------------------------------------------------------------
# ==== MOUSE SCROLL ====
# https://github.com/wekan/wekan/issues/2949
- SCROLLINERTIA=0
- SCROLLAMOUNT=auto
#---------------------------------------------------------------
# ==== CARD OPENED, SEND WEBHOOK MESSAGE ====
# https://github.com/wekan/wekan/issues/2518
- CARD_OPENED_WEBHOOK_ENABLED=false
@@ -249,6 +254,11 @@ services:
#-MAX_IMAGE_PIXEL=1024
#-IMAGE_COMPRESS_RATIO=80
#---------------------------------------------------------------
# ==== NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE =====
# Number of days after a notification is read before we remove it.
# Default: 2
#- NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2
#---------------------------------------------------------------
# ==== BIGEVENTS DUE ETC NOTIFICATIONS =====
# https://github.com/wekan/wekan/pull/2541
# Introduced a system env var BIGEVENTS_PATTERN default as "NONE",
@@ -504,18 +514,22 @@ services:
# The limit number of entries (0=unlimited)
#- LDAP_SEARCH_SIZE_LIMIT=0
#
# Enable group filtering
# Enable group filtering. Note the authenticated ldap user must be able to query all relevant group data with own login data from ldap.
#- LDAP_GROUP_FILTER_ENABLE=false
#
# The object class for filtering. Example: group
#- LDAP_GROUP_FILTER_OBJECTCLASS=
#
# The attribute of a group identifying it. Example: cn
#- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=
#
# The attribute inside a group object listing its members. Example: member
#- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=
#
# The format of the value of LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE. Example: 'dn' if the users dn ist saved as value into the attribute.
#- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=
#
# The group name (id) that matches all users.
#- LDAP_GROUP_FILTER_GROUP_NAME=
#
# LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier). Example: guid
@@ -584,6 +598,9 @@ services:
# example : LOGOUT_ON_MINUTES=55
#- LOGOUT_ON_MINUTES=
#-------------------------------------------------------------------
# Hide password login form
# - PASSWORD_LOGIN_ENABLED=true
#-------------------------------------------------------------------
depends_on:
- wekandb
+7
View File
@@ -56,3 +56,10 @@ mongodb-replicaset:
This section controls the scale of the MongoDB redundant Replica Set.
**replicas:** This is the number of MongoDB instances to include in the set. You can set this to 1 for a single server - this will still allow you to scale-up later with a helm upgrade.
### Install OCP route
If you use this chart to deploy Wekan on an OCP cluster, you can create route instead of ingress with following command:
``` bash
$ helm template --set route.enabled=true,ingress.enabled=false values.yaml . | oc apply -f-
```
+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: /
+23
View File
@@ -0,0 +1,23 @@
{{- if .Values.route.enabled -}}
{{- $fullName := include "wekan.fullname" . -}}
apiVersion: route.openshift.io/v1
kind: Route
metadata:
annotations:
haproxy.router.openshift.io/timeout: 4m
openshift.io/host.generated: "true"
labels:
app: {{ template "wekan.name" . }}
service: {{ template "wekan.name" . }}
name: {{ template "wekan.name" . }}
spec:
port:
targetPort: http
tls:
termination: edge
to:
kind: Service
name: {{ template "wekan.name" . }}
weight: 100
wildcardPolicy: None
{{- end }}
+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" . }}
+8 -2
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,7 +62,10 @@ ingress:
# hosts:
# - wekan-example.local
resources:
route:
enabled: false
resources:
requests:
memory: 128Mi
cpu: 300m
+82 -32
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__",
@@ -64,7 +64,7 @@
"activity-unchecked-item": "ازالة تحقق %s من قائمة التحقق %s من %s",
"activity-checklist-added": "أضاف قائمة تحقق إلى %s",
"activity-checklist-removed": "ازالة قائمة التحقق من %s",
"activity-checklist-completed": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
"activity-checklist-completed": "completed checklist %s of %s",
"activity-checklist-uncompleted": "لم يتم انجاز قائمة التحقق %s من %s",
"activity-checklist-item-added": "تم اضافة عنصر قائمة التحقق الى '%s' في %s",
"activity-checklist-item-removed": "تم ازالة عنصر قائمة التحقق الى '%s' في %s",
@@ -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": "مرفق",
@@ -161,6 +161,18 @@
"cardAttachmentsPopup-title": "إرفاق من",
"cardCustomField-datePopup-title": "تغير التاريخ",
"cardCustomFieldsPopup-title": "تعديل الحقل المعدل",
"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": "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": "علامات",
@@ -183,7 +195,7 @@
"changePasswordPopup-title": "تغيير كلمة المرور",
"changePermissionsPopup-title": "تعديل الصلاحيات",
"changeSettingsPopup-title": "تغيير الاعدادات",
"subtasks": "Subtasks",
"subtasks": "المهمات الفرعية",
"checklists": "قوائم التّدقيق",
"click-to-star": "اضغط لإضافة اللوحة للمفضلة.",
"click-to-unstar": "اضغط لحذف اللوحة من المفضلة.",
@@ -194,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",
@@ -211,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.",
@@ -239,8 +251,8 @@
"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",
@@ -250,8 +262,8 @@
"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",
@@ -297,6 +309,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": "لا يمكنك دعوة نفسك",
@@ -304,6 +317,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:",
@@ -319,6 +337,7 @@
"filter-clear": "مسح التصفية",
"filter-no-label": "لا يوجد ملصق",
"filter-no-member": "ليس هناك أي عضو",
"filter-no-assignee": "No assignee",
"filter-no-custom-fields": "No Custom Fields",
"filter-show-archive": "Show archived lists",
"filter-hide-empty": "Hide empty lists",
@@ -333,19 +352,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",
@@ -378,6 +399,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.",
@@ -440,7 +462,7 @@
"save": "حفظ",
"search": "بحث",
"rules": "Rules",
"search-cards": "Search from card/list titles and descriptions on this board",
"search-cards": "Search from card/list titles, descriptions and custom fields on this board",
"search-example": "Text to search for?",
"select-color": "اختيار اللون",
"set-wip-limit-value": "Set a limit for the maximum number of tasks in this list",
@@ -466,15 +488,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": "غير مُشاهد",
@@ -548,7 +570,7 @@
"OS_Totalmem": "الذاكرة الكلية لنظام التشغيل",
"OS_Type": "نوع نظام التشغيل",
"OS_Uptime": "مدة تشغيل نظام التشغيل",
"days": "days",
"days": "أيام",
"hours": "الساعات",
"minutes": "الدقائق",
"seconds": "الثواني",
@@ -562,17 +584,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.",
@@ -617,7 +639,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",
@@ -665,6 +687,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",
@@ -756,5 +779,32 @@
"assignee": "Assignee",
"cardAssigneesPopup-title": "Assignee",
"addmore-detail": "Add a more detailed description",
"show-on-card": "Show on Card"
"show-on-card": "Show on Card",
"new": "New",
"editUserPopup-title": "Edit User",
"newUserPopup-title": "New User",
"notifications": "Notifications",
"view-all": "View All",
"filter-by-unread": "Filter by Unread",
"mark-all-as-read": "Mark all as read",
"remove-all-read": "Remove all read",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename",
"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"
}
Loaded 100 of 436 files, more files were not shown because too many files have changed in this diff. Show more