Compare commits

...
120 Commits
Author SHA1 Message Date
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
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
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
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
343 changed files with 49535 additions and 59062 deletions

No files matched your search

+6 -5
View File
@@ -23,7 +23,7 @@ dburles:collection-helpers
idmontie:migrations
matb33:collection-hooks
matteodem:easy-search
mongo@1.8.0
mongo@1.9.0
mquandalle:collection-mutations
# Account system
@@ -38,7 +38,7 @@ 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
@@ -67,7 +67,7 @@ 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
@@ -75,7 +75,7 @@ horka:swipebox
dynamic-import@0.5.1
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.2
METEOR@1.10.1
+17 -17
View File
@@ -1,7 +1,7 @@
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
@@ -17,7 +17,7 @@ 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
@@ -43,7 +43,7 @@ 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
coagmano:stylus@1.1.0
coffeescript@2.4.1
coffeescript-compiler@2.4.1
cottz:publish-relations@2.0.8
@@ -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.5.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
mongo@1.9.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.0
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,7 +156,7 @@ 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
@@ -186,7 +186,7 @@ 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.0
webapp-hashing@1.0.9
wekan-accounts-cas@0.1.0
wekan-accounts-oidc@1.0.10
File renamed without changes.
File renamed without changes.
@@ -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
File renamed without changes.
File renamed without changes.
@@ -44,7 +44,7 @@ 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
coagmano:stylus@1.1.0
coffeescript@1.0.17
cottz:publish-relations@2.0.8
dburles:collection-helpers@1.1.0
File renamed without changes.
@@ -1,8 +1,7 @@
import ldapjs from 'ldapjs';
import util from 'util';
import Bunyan from 'bunyan';
import {log_debug, log_info, log_warn, log_error} from './logger';
import { log_debug, log_info, log_warn, log_error } from './logger';
export default class LDAP {
constructor() {
@@ -11,35 +10,66 @@ export default class LDAP {
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'),
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',
),
};
}
@@ -58,14 +88,13 @@ export default class LDAP {
}
connectSync(...args) {
if (!this._connectSync) {
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);
}
@@ -78,19 +107,19 @@ export default class LDAP {
let replied = false;
const connectionOptions = {
url : `${this.options.host}:${this.options.port}`,
timeout : this.options.timeout,
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,
idleTimeout: this.options.idle_timeout,
reconnect: this.options.Reconnect,
};
if (this.options.Internal_Log_Level !== 'disabled') {
connectionOptions.log = new Bunyan({
name : 'ldapjs',
name: 'ldapjs',
component: 'client',
stream : process.stderr,
level : this.options.Internal_Log_Level,
stream: process.stderr,
level: this.options.Internal_Log_Level,
});
}
@@ -100,10 +129,12 @@ export default class LDAP {
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) => {
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'));
@@ -114,7 +145,7 @@ export default class LDAP {
}
if (this.options.encryption === 'ssl') {
connectionOptions.url = `ldaps://${connectionOptions.url}`;
connectionOptions.url = `ldaps://${connectionOptions.url}`;
connectionOptions.tlsOptions = tlsOptions;
} else {
connectionOptions.url = `ldap://${connectionOptions.url}`;
@@ -127,7 +158,7 @@ export default class LDAP {
this.bindSync = Meteor.wrapAsync(this.client.bind, this.client);
this.client.on('error', (error) => {
this.client.on('error', error => {
log_error('connection', error);
if (replied === false) {
replied = true;
@@ -171,7 +202,7 @@ export default class LDAP {
}
});
} else {
this.client.on('connect', (response) => {
this.client.on('connect', response => {
log_info('LDAP connected');
this.connected = true;
if (replied === false) {
@@ -201,7 +232,9 @@ export default class LDAP {
}
}
const usernameFilter = this.options.User_Search_Field.split(',').map((item) => `(${item}=${username})`);
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');
@@ -215,7 +248,6 @@ export default class LDAP {
}
bindUserIfNecessary(username, password) {
if (this.domainBinded === true) {
return;
}
@@ -224,7 +256,6 @@ export default class LDAP {
return;
}
if (!this.options.BaseDN) throw new Error('BaseDN is not provided');
const userDn = `${this.options.User_Authentication_Field}=${username},${this.options.BaseDN}`;
@@ -244,23 +275,27 @@ export default class LDAP {
log_info('Binding UserDN', this.options.Authentication_UserDN);
this.bindSync(this.options.Authentication_UserDN, this.options.Authentication_Password);
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',
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.User_Attributes)
searchOptions.attributes = this.options.User_Attributes.split(',');
if (this.options.Search_Page_Size > 0) {
searchOptions.paged = {
pageSize : this.options.Search_Page_Size,
pageSize: this.options.Search_Page_Size,
pagePause: !!page,
};
}
@@ -279,7 +314,9 @@ export default class LDAP {
getUserByIdSync(id, attribute) {
this.bindIfNecessary();
const Unique_Identifier_Field = this.constructor.settings_get('LDAP_UNIQUE_IDENTIFIER_FIELD').split(',');
const Unique_Identifier_Field = this.constructor
.settings_get('LDAP_UNIQUE_IDENTIFIER_FIELD')
.split(',');
let filter;
@@ -290,11 +327,13 @@ export default class LDAP {
});
} else {
const filters = [];
Unique_Identifier_Field.forEach((item) => {
filters.push(new this.ldapjs.filters.EqualityFilter({
attribute: item,
value : new Buffer(id, 'hex'),
}));
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 });
@@ -327,7 +366,7 @@ export default class LDAP {
const searchOptions = {
filter: this.getUserFilter(username),
scope : this.options.User_Search_Scope || 'sub',
scope: this.options.User_Search_Scope || 'sub',
};
log_info('Searching user', username);
@@ -341,7 +380,13 @@ export default class LDAP {
}
if (result.length > 1) {
log_error('Search by username', username, 'returned', result.length, 'records');
log_error(
'Search by username',
username,
'returned',
result.length,
'records',
);
}
return result[0];
@@ -359,9 +404,12 @@ export default class LDAP {
}
if (this.options.group_filter_group_member_attribute !== '') {
const format_value = ldapUser[this.options.group_filter_group_member_format];
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(
`(${this.options.group_filter_group_member_attribute}=${format_value})`,
);
}
}
@@ -369,7 +417,7 @@ export default class LDAP {
const searchOptions = {
filter: filter.join('').replace(/#{username}/g, username),
scope : 'sub',
scope: 'sub',
};
log_debug('Group list filter LDAP:', searchOptions.filter);
@@ -381,13 +429,12 @@ export default class LDAP {
}
const grp_identifier = this.options.group_filter_group_id_attribute || 'cn';
const groups = [];
result.map((item) => {
const groups = [];
result.map(item => {
groups.push(item[grp_identifier]);
});
log_debug(`Groups: ${groups.join(', ')}`);
return groups;
}
isUserInGroup(username, ldapUser) {
@@ -404,20 +451,25 @@ export default class LDAP {
}
if (this.options.group_filter_group_member_attribute !== '') {
const format_value = ldapUser[this.options.group_filter_group_member_format];
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(
`(${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(
`(${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',
scope: 'sub',
};
log_debug('Group filter LDAP:', searchOptions.filter);
@@ -435,7 +487,7 @@ export default class LDAP {
_raw: entry.raw,
};
Object.keys(values._raw).forEach((key) => {
Object.keys(values._raw).forEach(key => {
const value = values._raw[key];
if (!['thumbnailPhoto', 'jpegPhoto'].includes(key)) {
@@ -458,11 +510,12 @@ export default class LDAP {
// Force LDAP idle to wait the record processing
this.client._updateIdle(true);
page(null, entries, {
end, next: () => {
end,
next: () => {
// Reset idle timer
this.client._updateIdle();
next && next();
}
},
});
};
@@ -473,7 +526,7 @@ export default class LDAP {
return;
}
res.on('error', (error) => {
res.on('error', error => {
log_error(error);
page(error);
return;
@@ -481,16 +534,19 @@ export default class LDAP {
let entries = [];
const internalPageSize = options.paged && options.paged.pageSize > 0 ? options.paged.pageSize * 2 : 500;
const internalPageSize =
options.paged && options.paged.pageSize > 0
? options.paged.pageSize * 2
: 500;
res.on('searchEntry', (entry) => {
res.on('searchEntry', entry => {
entries.push(this.extractLdapEntryData(entry));
if (entries.length >= internalPageSize) {
processPage({
entries,
title: 'Internal Page',
end : false,
end: false,
});
entries = [];
}
@@ -502,14 +558,14 @@ export default class LDAP {
processPage({
entries,
title: 'Final Page',
end : true,
end: true,
});
} else if (entries.length) {
log_info('Page');
processPage({
entries,
title: 'Page',
end : false,
end: false,
next,
});
entries = [];
@@ -521,7 +577,7 @@ export default class LDAP {
processPage({
entries,
title: 'Final Page',
end : true,
end: true,
});
entries = [];
}
@@ -539,7 +595,7 @@ export default class LDAP {
return;
}
res.on('error', (error) => {
res.on('error', error => {
log_error(error);
callback(error);
return;
@@ -547,7 +603,7 @@ export default class LDAP {
const entries = [];
res.on('searchEntry', (entry) => {
res.on('searchEntry', entry => {
entries.push(this.extractLdapEntryData(entry));
});
@@ -576,7 +632,7 @@ export default class LDAP {
}
disconnect() {
this.connected = false;
this.connected = false;
this.domainBinded = false;
log_info('Disconecting');
this.client.unbind();
@@ -1,13 +1,12 @@
Oidc = {};
OAuth.registerService('oidc', 2, null, function (query) {
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 expiresAt = +new Date() + 1000 * parseInt(token.expires_in, 10);
var userinfo = getUserInfo(accessToken);
if (debug) console.log('XXX: userinfo:', userinfo);
@@ -22,12 +21,14 @@ OAuth.registerService('oidc', 2, null, function (query) {
if (accessToken) {
var tokenContent = getTokenContent(accessToken);
var fields = _.pick(tokenContent, getConfiguration().idTokenWhitelistFields);
var fields = _.pick(
tokenContent,
getConfiguration().idTokenWhitelistFields,
);
_.extend(serviceData, fields);
}
if (token.refresh_token)
serviceData.refreshToken = token.refresh_token;
if (token.refresh_token) serviceData.refreshToken = token.refresh_token;
if (debug) console.log('XXX: serviceData:', serviceData);
var profile = {};
@@ -37,88 +38,101 @@ OAuth.registerService('oidc', 2, null, function (query) {
return {
serviceData: serviceData,
options: { profile: profile }
options: { profile: profile },
};
});
var userAgent = "Meteor";
var userAgent = 'Meteor';
if (Meteor.release) {
userAgent += "/" + Meteor.release;
userAgent += '/' + Meteor.release;
}
var getToken = function (query) {
var getToken = function(query) {
var debug = process.env.DEBUG || false;
var config = getConfiguration();
if(config.tokenEndpoint.includes('https://')){
if (config.tokenEndpoint.includes('https://')) {
var serverTokenEndpoint = config.tokenEndpoint;
}else{
} 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
}
}
);
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 });
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);
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 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://")) {
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
}
}
);
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});
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 getConfiguration = function() {
var config = ServiceConfiguration.configurations.findOne({ service: 'oidc' });
if (!config) {
throw new ServiceConfiguration.ConfigError('Service oidc not configured.');
@@ -126,7 +140,7 @@ var getConfiguration = function () {
return config;
};
var getTokenContent = function (token) {
var getTokenContent = function(token) {
var content = null;
if (token) {
try {
@@ -137,13 +151,13 @@ var getTokenContent = function (token) {
var signed = parts[0] + '.' + parts[1];
} catch (err) {
this.content = {
exp: 0
exp: 0,
};
}
}
return content;
}
};
Oidc.retrieveCredential = function (credentialToken, credentialSecret) {
Oidc.retrieveCredential = function(credentialToken, credentialSecret) {
return OAuth.retrieveCredential(credentialToken, credentialSecret);
};
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,6 @@
{
"name": "wekan",
"version": "v3.80.0",
"version": "v3.89.0",
"description": "Open-Source kanban",
"private": true,
"scripts": {
@@ -40,34 +40,34 @@
},
"homepage": "https://wekan.github.io",
"devDependencies": {
"eslint": "^5.16.0",
"eslint-config-meteor": "0.0.9",
"eslint-config-prettier": "^3.6.0",
"eslint": "^6.8.0",
"eslint-config-meteor": "^0.1.1",
"eslint-config-prettier": "^6.10.0",
"eslint-import-resolver-meteor": "^0.4.0",
"eslint-plugin-import": "^2.18.0",
"eslint-plugin-meteor": "^5.1.0",
"eslint-plugin-prettier": "^3.1.0",
"lint-staged": "^7.3.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-meteor": "^6.0.0",
"eslint-plugin-prettier": "^3.1.2",
"lint-staged": "^10.0.8",
"pre-commit": "^1.2.2",
"prettier": "^1.18.2",
"prettier-eslint": "^8.8.2"
"prettier": "^1.19.1",
"prettier-eslint": "^9.0.1"
},
"dependencies": {
"@babel/runtime": "^7.6.2",
"ajv": "^5.0.0",
"@babel/runtime": "^7.8.7",
"ajv": "^6.12.0",
"babel-runtime": "^6.26.0",
"bcrypt": "^3.0.2",
"bson": "^4.0.0",
"bcrypt": "^4.0.1",
"bson": "^4.0.3",
"bunyan": "^1.8.12",
"es6-promise": "^4.2.4",
"gridfs-stream": "^0.5.3",
"es6-promise": "^4.2.8",
"gridfs-stream": "^1.1.1",
"ldapjs": "^1.0.2",
"meteor-node-stubs": "^0.4.1",
"mongodb": "^3.3.3",
"meteor-node-stubs": "^1.0.0",
"mongodb": "^3.5.5",
"os": "^0.1.1",
"page": "^1.8.6",
"qs": "^6.8.0",
"source-map-support": "^0.5.12",
"page": "^1.11.5",
"qs": "^6.9.1",
"source-map-support": "^0.5.16",
"xss": "^1.0.6"
}
}
File renamed without changes.
File renamed without changes.
+184
View File
@@ -1,3 +1,187 @@
# 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:
+4 -2
View File
@@ -7,7 +7,7 @@ LABEL maintainer="wekan"
ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates python3" \
DEBUG=false \
NODE_VERSION=v12.16.1 \
METEOR_RELEASE=1.9.0 \
METEOR_RELEASE=1.10-rc.2 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
NPM_VERSION=latest \
@@ -110,7 +110,9 @@ 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"
# Copy the app to the image
COPY ${SRC_PATH} /home/wekan/app
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v3.80.0"
appVersion: "v3.89.0"
files:
userUploads:
- README.md
+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 }}
+72 -39
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;
},
@@ -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,
),
)
);
}
-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
-4
View File
@@ -191,10 +191,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({
+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' }}
+7 -4
View File
@@ -203,6 +203,7 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
if currentBoard.allowsDescriptionTitle
hr
h3
i.fa.fa-align-left
card-details-item-title {{_ 'description'}}
@@ -229,6 +230,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 +239,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
+1 -1
View File
@@ -79,7 +79,7 @@
border-radius: top 2px
.minicard-labels
float: right
float: none
display: flex
flex-wrap: wrap
+7 -1
View File
@@ -20,7 +20,13 @@ 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({
+17 -1
View File
@@ -743,9 +743,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());
}
},
+64 -128
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,16 +314,17 @@ 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
@@ -397,10 +336,7 @@ Template.viewer.events({
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');
}
}
+2
View File
@@ -35,6 +35,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)
+1 -1
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,
@@ -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,16 @@
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'}}
@@ -0,0 +1,38 @@
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());
},
});
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'));
},
});
@@ -0,0 +1,57 @@
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;
overflow: scroll
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
color belize
background-color: #fafafa
margin 8px 16px 12px
display inline-block
ul.notifications
display: block
padding: 0px 16px
margin: 0
+2 -2
View File
@@ -110,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
@@ -159,7 +159,7 @@ template(name="newUserPopup")
//label.hide.userId(type="text" value=user._id)
label
| {{_ 'fullname'}}
input.js-profile-fullname(type="text" value="" autofocus)
input.js-profile-fullname(type="text" value="")
label
| {{_ 'username'}}
span.error.hide.username-taken
+10 -3
View File
@@ -245,7 +245,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 +257,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 +267,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
+4
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();
+6 -6
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
+10 -1
View File
@@ -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
@@ -504,18 +509,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
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+9 -3
View File
@@ -64,7 +64,7 @@
"activity-unchecked-item": "unchecked %s in checklist %s of %s",
"activity-checklist-added": "added checklist to %s",
"activity-checklist-removed": "removed a checklist from %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": "uncompleted the checklist %s of %s",
"activity-checklist-item-added": "added checklist item to '%s' in %s",
"activity-checklist-item-removed": "removed a checklist item from '%s' in %s",
@@ -440,7 +440,7 @@
"save": "Save",
"search": "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": "Select Color",
"set-wip-limit-value": "Set a limit for the maximum number of tasks in this list",
@@ -762,5 +762,11 @@
"show-on-card": "Show on Card",
"new": "New",
"editUserPopup-title": "Edit User",
"newUserPopup-title": "New 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",
"allow-rename": "Allow Rename",
"allowRenamePopup-title": "Allow Rename"
}
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
+768 -762
View File
File diff suppressed because it is too large. Load diff
Loaded 100 of 343 files, more files were not shown because too many files have changed in this diff. Show more