Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
843ff8eaaa | ||
|
|
c5143e2c54 | ||
|
|
0596f5e864 | ||
|
|
8ca1b25daf | ||
|
|
1f76159bfe | ||
|
|
39ece35f52 | ||
|
|
63c1bb9e17 | ||
|
|
354622c5ad | ||
|
|
df7fd8c900 | ||
|
|
7957690fca | ||
|
|
63d19447a0 | ||
|
|
018a84ec59 | ||
|
|
e5c0e623a0 | ||
|
|
9a2d3e15e8 | ||
|
|
c07e262e68 | ||
|
|
cacb2b91ee | ||
|
|
6f269d598f | ||
|
|
57ea254bff | ||
|
|
7e183c838f | ||
|
|
3ffdfd9736 | ||
|
|
621c701bef | ||
|
|
3c24c9966c | ||
|
|
995de525d9 | ||
|
|
ae30b1220a | ||
|
|
e380ad26c2 | ||
|
|
9bb31d2380 | ||
|
|
8ec1e4f3fa | ||
|
|
9f0f6841b0 | ||
|
|
5d18f98b44 | ||
|
|
e738177e08 | ||
|
|
bb201935b9 | ||
|
|
bcfe6bd616 | ||
|
|
9c6352a7bc | ||
|
|
9110e41f20 | ||
|
|
7ae871d3a6 | ||
|
|
0ddf027d43 | ||
|
|
45124a39f3 | ||
|
|
4940af0573 | ||
|
|
924b5d1733 | ||
|
|
2119a3a51e | ||
|
|
dcda99ddf3 | ||
|
|
0e35dcd5ad | ||
|
|
991d42ca0b | ||
|
|
ac242c96b7 | ||
|
|
79d45c8e33 | ||
|
|
8e9f7102f9 | ||
|
|
1952dc309b | ||
|
|
5e7767247f | ||
|
|
676bf686c7 | ||
|
|
5cd6985368 | ||
|
|
807e72dcce | ||
|
|
71702b5821 | ||
|
|
40c56248f1 | ||
|
|
74ac7d8d3f | ||
|
|
e7186b47b0 | ||
|
|
12aa7ea2cb | ||
|
|
ef584e2826 | ||
|
|
f902be10b6 | ||
|
|
29b835de59 | ||
|
|
6ff9c5b58d | ||
|
|
1b86aeb4f5 | ||
|
|
dce99c00be | ||
|
|
e47c700c36 | ||
|
|
09506c78f3 | ||
|
|
9ec1fc0902 | ||
|
|
0b3ec2c19e | ||
|
|
a94bec921c | ||
|
|
3884da3c06 | ||
|
|
d3fa177c7f | ||
|
|
d2176e2c08 | ||
|
|
8d9c59486d | ||
|
|
0e7c2b4b94 | ||
|
|
5c9bc1c745 | ||
|
|
5e7efa0098 | ||
|
|
424862e313 | ||
|
|
9b80ad5a3c | ||
|
|
96c6fa0276 | ||
|
|
69305e1833 | ||
|
|
8aa74ab65e | ||
|
|
aee12c2494 | ||
|
|
83976b3229 | ||
|
|
39f8a138d7 | ||
|
|
120e4af7b2 | ||
|
|
15bddbc86a | ||
|
|
4e8b193801 | ||
|
|
37b65996a0 | ||
|
|
9cd0d3488f | ||
|
|
eaf07cef07 | ||
|
|
02a8775537 | ||
|
|
f2d5698acb | ||
|
|
b0c0e456af | ||
|
|
0857a2ea91 | ||
|
|
1f2e6d33ce | ||
|
|
250e0690d1 | ||
|
|
e709094533 | ||
|
|
4a5b16ee2e | ||
|
|
378d64f99a | ||
|
|
4395760a40 | ||
|
|
8417fae89c | ||
|
|
6c8fae944c | ||
|
|
decab9256b | ||
|
|
183ed35ce8 | ||
|
|
2fcf85fd61 | ||
|
|
3613c1c75e | ||
|
|
e7813046f6 | ||
|
|
9871bf1963 | ||
|
|
b32e7b659b | ||
|
|
2201372744 | ||
|
|
7431a082e2 | ||
|
|
6fe3edebb1 | ||
|
|
4b6406c264 | ||
|
|
86f6bdf9af | ||
|
|
1eeed6645d | ||
|
|
bb8c4325c6 | ||
|
|
07a3301414 | ||
|
|
1590701fc9 | ||
|
|
1d4a65d0b4 | ||
|
|
c845d25ae5 | ||
|
|
ceec27bf90 | ||
|
|
4b90424773 | ||
|
|
31eb9b26b4 | ||
|
|
a8a3c9ad19 | ||
|
|
5cfa03ec07 | ||
|
|
17ab5978ad | ||
|
|
d4c69c3932 | ||
|
|
3778a9ebcf | ||
|
|
880d94e220 | ||
|
|
37607f72b2 | ||
|
|
b1c26e6f64 | ||
|
|
8164af281d | ||
|
|
af676c8f26 | ||
|
|
b8dc7ff18e | ||
|
|
4aee129cdc | ||
|
|
03d29dd674 | ||
|
|
5a6f84ce34 | ||
|
|
0e593c7d81 | ||
|
|
4de830d663 | ||
|
|
769bb7a55d | ||
|
|
ed6c6e5a25 | ||
|
|
302ba75729 | ||
|
|
69dc8f304c | ||
|
|
e43002d5ad | ||
|
|
edd07befe2 | ||
|
|
5cc6a2f7d0 | ||
|
|
926f39c96b | ||
|
|
abd38082de | ||
|
|
5ca90f4d22 | ||
|
|
c9586e579a | ||
|
|
4cadc5f4f3 | ||
|
|
8dfb6916c5 | ||
|
|
6066be944a | ||
|
|
e9f8279dbe | ||
|
|
454d3b5bbe | ||
|
|
fe185dd38a | ||
|
|
522b14d96d | ||
|
|
d98b489263 | ||
|
|
47ecc654b8 | ||
|
|
b9a4ae654e | ||
|
|
c40668be3f | ||
|
|
a9d77b975e | ||
|
|
085bf3b6e6 | ||
|
|
91dae9795e | ||
|
|
42081db0d8 | ||
|
|
3af417209c | ||
|
|
b6f60c08e5 | ||
|
|
03d7940829 | ||
|
|
0b263cf582 | ||
|
|
8ef60a06ad | ||
|
|
91eb4b941d | ||
|
|
2171690d22 | ||
|
|
6819303047 | ||
|
|
88e19bd011 | ||
|
|
09b9f690b1 | ||
|
|
7a9c397264 | ||
|
|
c2b7525864 | ||
|
|
e2ecd490ff | ||
|
|
1d3673e9d3 | ||
|
|
5bd85f975c | ||
|
|
89ab99b551 | ||
|
|
01de3f187c | ||
|
|
dcd7925571 | ||
|
|
34b6aa0858 | ||
|
|
73fb62e941 | ||
|
|
55ced412a0 | ||
|
|
04a8d85836 | ||
|
|
392b701956 | ||
|
|
dee098c125 | ||
|
|
56e26a65ae | ||
|
|
69509d597f | ||
|
|
bc7b18abe7 | ||
|
|
4a95cb42cd | ||
|
|
db311f677b | ||
|
|
685fb98e59 | ||
|
|
6dd98285a6 | ||
|
|
e225d29d7a | ||
|
|
eff7d123f2 | ||
|
|
4d62fe83f4 | ||
|
|
a37ca01703 | ||
|
|
ba05f383ca | ||
|
|
4ea4913d72 | ||
|
|
856f3bf864 | ||
|
|
238b0f0725 | ||
|
|
a79a5c7299 | ||
|
|
59580e4b0f | ||
|
|
7e05e02137 | ||
|
|
d4a1611b86 | ||
|
|
77abde7836 | ||
|
|
4c60916191 | ||
|
|
634eec7eb6 | ||
|
|
4ccbbc78cf | ||
|
|
4e1c0fdce8 | ||
|
|
9fa1d846e6 | ||
|
|
969dca292e | ||
|
|
84fde1ecfc | ||
|
|
db9ee59d9f | ||
|
|
20a2ea3e7a | ||
|
|
39b4ada26d | ||
|
|
3ddfe7c1fd | ||
|
|
f7955d5fd7 | ||
|
|
f115afb64e | ||
|
|
3145ec65a3 | ||
|
|
e2f690d0db | ||
|
|
e6c8c04394 | ||
|
|
a7741df5e4 | ||
|
|
912577cab3 | ||
|
|
cec43a63a7 | ||
|
|
10c9fc25a9 | ||
|
|
16665bccf9 | ||
|
|
103cfb936d | ||
|
|
4bb8f3ab27 | ||
|
|
36a4740429 | ||
|
|
ed54c09b48 | ||
|
|
6eb90238b1 | ||
|
|
aad300613d | ||
|
|
a2f8e54b67 | ||
|
|
c9a10efe5c | ||
|
|
7e2f802169 | ||
|
|
9df5a0f530 | ||
|
|
16971ff1d8 | ||
|
|
bef544ddc8 | ||
|
|
2c1f40357d | ||
|
|
ea544caf83 | ||
|
|
be9e98b2a1 | ||
|
|
59a3ac1f37 | ||
|
|
be22ca332b | ||
|
|
983e34ef80 | ||
|
|
c9830bc262 | ||
|
|
fc21c6d1d6 | ||
|
|
960f332dce | ||
|
|
d532a3591f | ||
|
|
03a9f4766a | ||
|
|
96f3e3a958 | ||
|
|
a249ffc805 | ||
|
|
5f53f8cc01 | ||
|
|
ca3f621572 | ||
|
|
a0e1b6f918 | ||
|
|
c76d44b5c7 | ||
|
|
ccc9efb270 | ||
|
|
2ebc34e901 | ||
|
|
04566d3f40 | ||
|
|
1083a92816 | ||
|
|
bb7490a32b | ||
|
|
9e8399612b | ||
|
|
df54863e72 | ||
|
|
8f340326b0 | ||
|
|
42975bc8ab | ||
|
|
adb5487cdc | ||
|
|
4aad9ca498 | ||
|
|
0ebb427a00 | ||
|
|
299410f8b4 | ||
|
|
07aa20a3cd | ||
|
|
06cae36199 | ||
|
|
183404a5f8 | ||
|
|
6def7d6f70 | ||
|
|
097cae1f8c | ||
|
|
ba00311dd4 | ||
|
|
849b608933 | ||
|
|
4ed5c3e4b8 | ||
|
|
9061180983 | ||
|
|
be970e4cea | ||
|
|
8f978c23b4 | ||
|
|
5f53c71b0c | ||
|
|
58020863a8 | ||
|
|
28aebaa06b | ||
|
|
329256712b | ||
|
|
e0b544fc7e | ||
|
|
a1bda1169e |
No files matched your search
@@ -10,7 +10,7 @@ ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ENV \
|
||||
DEBUG=false \
|
||||
NODE_VERSION=v12.21.0 \
|
||||
NODE_VERSION=v12.22.1 \
|
||||
METEOR_RELEASE=1.10.2 \
|
||||
USE_EDGE=false \
|
||||
METEOR_EDGE=1.5-beta.17 \
|
||||
@@ -19,6 +19,7 @@ ENV \
|
||||
ARCHITECTURE=linux-x64 \
|
||||
SRC_PATH=./ \
|
||||
WITH_API=true \
|
||||
RESULTS_PER_PAGE="" \
|
||||
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
|
||||
ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \
|
||||
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \
|
||||
|
||||
@@ -81,7 +81,7 @@ parts:
|
||||
wekan:
|
||||
source: .
|
||||
plugin: nodejs
|
||||
node-engine: 12.21.0
|
||||
node-engine: 12.22.1
|
||||
node-packages:
|
||||
- node-gyp
|
||||
- node-pre-gyp
|
||||
|
||||
@@ -83,7 +83,7 @@ parts:
|
||||
wekan:
|
||||
source: .
|
||||
plugin: nodejs
|
||||
node-engine: 12.21.0
|
||||
node-engine: 12.22.1
|
||||
node-packages:
|
||||
- node-gyp
|
||||
- node-pre-gyp
|
||||
|
||||
@@ -31,5 +31,4 @@ Thumbs.db
|
||||
ehthumbs.db
|
||||
.eslintcache
|
||||
.meteor/local
|
||||
.meteor-1.6-snap/.meteor/local
|
||||
.devcontainer/docker-compose.extend.yml
|
||||
+4
-4
@@ -6,7 +6,7 @@
|
||||
meteor-base@1.4.0
|
||||
|
||||
# Build system
|
||||
ecmascript@0.15.0
|
||||
ecmascript@0.15.1
|
||||
standard-minifier-css@1.7.2
|
||||
standard-minifier-js@2.6.0
|
||||
mquandalle:jade
|
||||
@@ -23,7 +23,7 @@ dburles:collection-helpers
|
||||
idmontie:migrations
|
||||
matb33:collection-hooks
|
||||
matteodem:easy-search
|
||||
mongo@1.10.1
|
||||
mongo@1.11.0
|
||||
mquandalle:collection-mutations
|
||||
|
||||
# Account system
|
||||
@@ -52,6 +52,7 @@ mquandalle:autofocus
|
||||
ongoworks:speakingurl
|
||||
raix:handlebar-helpers
|
||||
tap:i18n
|
||||
http@1.4.2
|
||||
|
||||
# UI components
|
||||
blaze
|
||||
@@ -90,7 +91,6 @@ meteorhacks:aggregate@1.3.0
|
||||
wekan-markdown
|
||||
konecty:mongo-counter
|
||||
percolate:synced-cron
|
||||
easylogic:summernote
|
||||
cfs:filesystem
|
||||
tmeasday:check-npm-versions
|
||||
steffo:meteor-accounts-saml
|
||||
@@ -144,4 +144,4 @@ rajit:bootstrap3-datepicker-zh-cn
|
||||
rajit:bootstrap3-datepicker-zh-tw
|
||||
staringatlights:fast-render
|
||||
spacebars
|
||||
jkuester:http
|
||||
easylogic:summernote
|
||||
+1
-1
@@ -1 +1 @@
|
||||
METEOR@2.1
|
||||
METEOR@2.2
|
||||
+22
-23
@@ -1,5 +1,5 @@
|
||||
3stack:presence@1.1.2
|
||||
accounts-base@1.8.0
|
||||
accounts-base@1.9.0
|
||||
accounts-oauth@1.2.0
|
||||
accounts-password@1.7.0
|
||||
aldeed:collection2@2.10.0
|
||||
@@ -11,17 +11,17 @@ allow-deny@1.1.0
|
||||
arillo:flow-router-helpers@0.5.2
|
||||
audit-argument-checks@1.0.7
|
||||
autoupdate@1.7.0
|
||||
babel-compiler@7.6.0
|
||||
babel-compiler@7.6.1
|
||||
babel-runtime@1.5.0
|
||||
base64@1.0.12
|
||||
binary-heap@1.0.11
|
||||
blaze@2.3.4
|
||||
blaze-tools@1.0.10
|
||||
blaze@2.4.0
|
||||
blaze-tools@1.1.1
|
||||
boilerplate-generator@1.7.1
|
||||
browser-policy-common@1.0.11
|
||||
browser-policy-framing@1.1.0
|
||||
caching-compiler@1.2.2
|
||||
caching-html-compiler@1.1.3
|
||||
caching-html-compiler@1.2.0
|
||||
callback-hook@1.3.0
|
||||
cfs:access-point@0.1.49
|
||||
cfs:base-package@0.0.30
|
||||
@@ -52,12 +52,12 @@ ddp@1.4.0
|
||||
ddp-client@2.4.0
|
||||
ddp-common@1.4.0
|
||||
ddp-rate-limiter@1.0.9
|
||||
ddp-server@2.3.2
|
||||
ddp-server@2.3.3
|
||||
deps@1.0.12
|
||||
diff-sequence@1.1.1
|
||||
dynamic-import@0.6.0
|
||||
easylogic:summernote@0.8.8
|
||||
ecmascript@0.15.0
|
||||
ecmascript@0.15.1
|
||||
ecmascript-runtime@0.7.0
|
||||
ecmascript-runtime-client@0.11.0
|
||||
ecmascript-runtime-server@0.10.0
|
||||
@@ -70,13 +70,12 @@ fortawesome:fontawesome@4.7.0
|
||||
geojson-utils@1.0.10
|
||||
horka:swipebox@1.0.2
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.0.11
|
||||
htmljs@1.0.11
|
||||
html-tools@1.1.1
|
||||
htmljs@1.1.0
|
||||
http@1.4.3
|
||||
id-map@1.1.0
|
||||
idmontie:migrations@1.0.3
|
||||
inter-process-messaging@0.1.1
|
||||
jkuester:http@2.0.1
|
||||
jquery@1.11.11
|
||||
kadira:blaze-layout@2.3.0
|
||||
kadira:dochead@1.5.0
|
||||
@@ -85,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.2.0
|
||||
launch-screen@1.2.1
|
||||
livedata@1.0.18
|
||||
localstorage@1.2.0
|
||||
logging@1.2.0
|
||||
@@ -101,16 +100,16 @@ meteorhacks:collection-utils@1.2.0
|
||||
meteorhacks:picker@1.0.3
|
||||
meteorhacks:subs-manager@1.6.4
|
||||
meteorspark:util@0.2.0
|
||||
minifier-css@1.5.3
|
||||
minifier-css@1.5.4
|
||||
minifier-js@2.6.0
|
||||
minifiers@1.1.8-faster-rebuild.0
|
||||
minimongo@1.6.1
|
||||
minimongo@1.6.2
|
||||
mobile-status-bar@1.1.0
|
||||
modern-browsers@0.1.5
|
||||
modules@0.16.0
|
||||
modules-runtime@0.12.0
|
||||
momentjs:moment@2.29.1
|
||||
mongo@1.10.1
|
||||
mongo@1.11.0
|
||||
mongo-decimal@0.1.2
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.7
|
||||
@@ -126,7 +125,7 @@ mquandalle:moment@1.0.1
|
||||
mquandalle:mousetrap-bindglobal@0.0.1
|
||||
msavin:usercache@1.8.0
|
||||
npm-bcrypt@0.9.3
|
||||
npm-mongo@3.8.1
|
||||
npm-mongo@3.9.0
|
||||
oauth@1.3.2
|
||||
oauth2@1.3.0
|
||||
observe-sequence@1.0.16
|
||||
@@ -209,10 +208,10 @@ simple:json-routes@2.1.0
|
||||
simple:rest-accounts-password@1.1.2
|
||||
simple:rest-bearer-token-parser@1.0.1
|
||||
simple:rest-json-error-handler@1.0.1
|
||||
socket-stream-client@0.3.1
|
||||
socket-stream-client@0.3.2
|
||||
softwarerero:accounts-t9n@1.3.11
|
||||
spacebars@1.0.15
|
||||
spacebars-compiler@1.1.3
|
||||
spacebars@1.1.0
|
||||
spacebars-compiler@1.2.1
|
||||
srp@1.1.0
|
||||
standard-minifier-css@1.7.2
|
||||
standard-minifier-js@2.6.0
|
||||
@@ -221,10 +220,10 @@ staringatlights:inject-data@2.3.0
|
||||
steffo:meteor-accounts-saml@0.0.18
|
||||
tap:i18n@1.8.2
|
||||
templates:tabs@2.3.0
|
||||
templating@1.3.2
|
||||
templating-compiler@1.3.3
|
||||
templating-runtime@1.3.2
|
||||
templating-tools@1.1.2
|
||||
templating@1.4.0
|
||||
templating-compiler@1.4.0
|
||||
templating-runtime@1.4.0
|
||||
templating-tools@1.2.0
|
||||
tmeasday:check-npm-versions@0.3.2
|
||||
tracker@1.2.0
|
||||
twbs:bootstrap@3.3.6
|
||||
@@ -235,7 +234,7 @@ useraccounts:core@1.14.2
|
||||
useraccounts:flow-routing@1.14.2
|
||||
useraccounts:unstyled@1.14.2
|
||||
verron:autosize@3.0.8
|
||||
webapp@1.10.0
|
||||
webapp@1.10.1
|
||||
webapp-hashing@1.1.0
|
||||
wekan-accounts-cas@0.1.0
|
||||
wekan-accounts-oidc@1.0.10
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ sudo: required
|
||||
|
||||
env:
|
||||
TRAVIS_DOCKER_COMPOSE_VERSION: 1.24.0
|
||||
TRAVIS_NODE_VERSION: 12.21.0
|
||||
TRAVIS_NODE_VERSION: 12.22.1
|
||||
TRAVIS_NPM_VERSION: latest
|
||||
|
||||
before_install:
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ host = https://www.transifex.com
|
||||
# tap:i18n requires us to use `-` separator in the language identifiers whereas
|
||||
# Transifex uses a `_` separator, without an option to customize it on one side
|
||||
# or the other, so we need to do a Manual mapping.
|
||||
lang_map = ar_EG:ar-EG, bg_BG:bg, en_GB:en-GB, es_AR:es-AR, es_CL:es-CL, es_PE:es-PE, es_MX:es-MX, es_TX:es-TX, es_PY:es-PY, el_GR:el, fa_IR:fa-IR, fi_FI:fi, hu_HU:hu, id_ID:id, mn_MN:mn, no:nb, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, sl_SI:sl, zh_CN:zh-CN, zh_TW:zh-TW, zh_HK:zh-HK
|
||||
lang_map = ar_EG:ar-EG, bg_BG:bg, de_CH:de-CH, en_GB:en-GB, es_AR:es-AR, es_CL:es-CL, es_419:es-LA, es_PE:es-PE, es_MX:es-MX, es_TX:es-TX, es_PY:es-PY, el_GR:el, fa_IR:fa-IR, fi_FI:fi, hu_HU:hu, id_ID:id, mn_MN:mn, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, sl_SI:sl, zh_CN:zh-CN, zh_TW:zh-TW, zh_HK:zh-HK
|
||||
|
||||
[wekan.application]
|
||||
file_filter = i18n/<lang>.i18n.json
|
||||
|
||||
+362
@@ -1,3 +1,365 @@
|
||||
# v5.26 2021-04-28 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Feature/mini card subtask count](https://github.com/wekan/wekan/pull/3765).
|
||||
Thanks to ryanMushy.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Bring back Almost-Due for Start Date](https://github.com/wekan/wekan/commit/8ca1b25daf3be60c2dc64f03830dea8437bbd8ad).
|
||||
Thanks to darren-teo.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.25 2021-04-27 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Swimlane in Export to Excel](https://github.com/wekan/wekan/pull/3764).
|
||||
Thanks to Keelan and ryanMushy.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Updated release scripts](https://github.com/wekan/wekan/commit/9f0f6841b01b88f5559724b047d5e245617a02c8).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Added missing PostgreSQL password to ToroDB](https://github.com/wekan/wekan/commit/995de525d96946702536f0cdcb98ef281b9df94e).
|
||||
Thanks to xet7.
|
||||
- [Fixed language name of Deutsch (Schweiz)](https://github.com/wekan/wekan/commit/621c701bef1d09d4ddfc93be411cfad98869f0ae).
|
||||
Thanks to urmel1960.
|
||||
- [Bugfix/Summernote on paste](https://github.com/wekan/wekan/pull/3761).
|
||||
Thanks to ryanMushy.
|
||||
- [OpenAPI: Better handle nested schemas](https://github.com/wekan/wekan/pull/3762).
|
||||
Thanks to bentiss.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.24 2021-04-24 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Copy Swimlane](https://github.com/wekan/wekan/pull/3753).
|
||||
Thanks to jrsupplee.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Updated dependencies](https://github.com/wekan/wekan/commit/e738177e081e4a7e83fed3389f47847403551fc2).
|
||||
Thanks to developers of dependencies.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix Snap: Delete extra symlink that prevented building Snap](https://github.com/wekan/wekan/commit/45124a39f34a918b251a4a36fb016639b558f119).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.23 2021-04-22 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Filtering by due date](https://github.com/wekan/wekan/pull/3731).
|
||||
Thanks to mcrute.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Updated dependencies](https://github.com/wekan/wekan/commit/676bf686c7a121b0da744afce5911807a6be48fe).
|
||||
Thanks to developers of dependencies.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix: Trello data without labels definition](https://github.com/wekan/wekan/pull/3733).
|
||||
Thanks to jrsupplee.
|
||||
- [Bug fix for Due Cards](https://github.com/wekan/wekan/pull/3741).
|
||||
Thanks to jrsupplee.
|
||||
- [Fix: The bg color of start at button is almost-due](https://github.com/wekan/wekan/pull/3749).
|
||||
Thanks to listenerri.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.22 2021-04-16 Wekan release
|
||||
|
||||
This release adds the following new translations:
|
||||
|
||||
- Added German (Switzerland) (de_CH) (Schwiizerdütsch). Updated translations.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/09506c78f3c3439db622574eb851fa0c20d3a066),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/dce99c00be80cceba686fd73b4b78b6c778d78a6),
|
||||
[Part 3](https://github.com/wekan/wekan/commit/6ff9c5b58d25ba52b11e5429c9cfe6ed6a97000e).
|
||||
Thanks to translators.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Remove allowedValues from Cards.type schema](https://github.com/wekan/wekan/pull/3724).
|
||||
Thanks to jrsupplee.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.21 2021-04-16 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Summernote Rich Text Editor](https://github.com/wekan/wekan/pull/3720):
|
||||
1) Add new button to insert a URL link.
|
||||
2) Add new popover allowing you to edit existing URL links.
|
||||
3) Enable spell check.
|
||||
4) Allow client side grammerly extension.
|
||||
Thanks to ryanMushy.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Upgraded to Meteor 2.2](https://github.com/wekan/wekan/commit/0e7c2b4b94b1c48e8839cfba635b53cdc1a797b1).
|
||||
Thanks to Meteor developers.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Bugfix, date format not changed to local format](https://github.com/wekan/wekan/pull/3723).
|
||||
Thanks to mfilser.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.20 2021-04-14 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [OpenAPI: rework the allowedValues to allow for imported variables](https://github.com/wekan/wekan/pull/3715).
|
||||
Thanks to bentiss.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.19 2021-04-14 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Custom Field "String Template"](https://github.com/wekan/wekan/pull/3701).
|
||||
Thanks to tod31.
|
||||
- [1) Admin reports. An option added to the admin panel that has reports an admin can run.
|
||||
Right now it has two reports for attachments and broken cards.
|
||||
2) Add the creator avatar to `cardDetails` and `minicard`. Avatar is only shown if it is selected in card settings.
|
||||
3) Added a new search operator `creator`.
|
||||
4) Bug fix for multiple label predicates](https://github.com/wekan/wekan/pull/3705).
|
||||
Thanks to jrsupplee.
|
||||
- [Update Admin Panel Rules report icon and add missing translations](https://github.com/wekan/wekan/commit/8417fae89cc89adb4559874050ff7c56cc08eb00).
|
||||
Thanks to xet7.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Upgraded to Meteor 2.1.1 an updated dependencies](https://github.com/wekan/wekan/commit/bb8c4325c60582cdcda5d406071586f18681e737).
|
||||
Thanks to developers of dependencies.
|
||||
- [Updated to Node.js v12.22.1](https://github.com/wekan/wekan/commit/2201372744639ade3ba74b6ff9115988f011b9ac).
|
||||
Thanks to Node.js developers.
|
||||
- [Updated release scripts](https://github.com/wekan/wekan/commit/9871bf196352edcb5475e1b0ee4983e8f312e449).
|
||||
Thanks to xet7.
|
||||
- [Updated caniuse-lite etc dependencies](https://github.com/wekan/wekan/commit/0857a2ea91f672201ba96f2ba635165784b30fd8).
|
||||
Thanks to developers of dependencies.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Revert stable tag, because it did break Wekan version numbers](https://github.com/wekan/wekan/commit/5ca90f4d2245910580cb0af885fac17dcec44ef0).
|
||||
Thanks to xet7.
|
||||
- [Updating ARM Dockerfile](https://github.com/wekan/wekan/pull/3692).
|
||||
Thanks to loganballard.
|
||||
- [Added latest arm64 bundle symlink](https://github.com/wekan/wekan/commit/6fe3edebb18414ebe7e69b2de3269438662b6163).
|
||||
Thanks to xet7.
|
||||
- [Bug fix: Rules for moving from list/swimlane](https://github.com/wekan/wekan/pull/3706).
|
||||
Thanks to jrsupplee.
|
||||
- [Fixed Elements are duplicated on the view "My cards".
|
||||
Rewrite routine for building the My Cards hierarchical list.
|
||||
Use a separate publication for retrieving My Cards.
|
||||
Fixed bug with limit and skip projection](https://github.com/wekan/wekan/pull/3708).
|
||||
Thanks to jrsupplee.
|
||||
- [Popover needs to be destroyed anytime the details panel is closed](https://github.com/wekan/wekan/pull/3712).
|
||||
Thanks to ryanMushy.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.18 2021-04-14 Wekan release
|
||||
|
||||
Not released version, because some version numbers not changed in all release files.
|
||||
|
||||
# v5.17 2021-04-01 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fix Link dialog closes card when clicking in dialog](https://github.com/wekan/wekan/commit/454d3b5bbeed6cef8ecface7e6094cabfcc4847c).
|
||||
Thanks to ryanMushy.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Added stable tag release script](https://github.com/wekan/wekan/commit/8dfb6916c5180f5d074748840d51dd04f9adb2bb).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.16 2021-04-01 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Added stable tag](https://github.com/wekan/wekan/commit/c40668be3fb9c35d44698f49ab06fec5bcabbe1b).
|
||||
Thanks to rynr and xet7.
|
||||
- [Added back Summernote editor. Removed emoji picker](https://github.com/wekan/wekan/commit/47ecc654b825a875074dfd4826c36e2c5c55f599).
|
||||
Thanks to ryanMushy and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.15 2021-03-31 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fixed card sort reset](https://github.com/wekan/wekan/pull/3686).
|
||||
Thanks to ednamaeG.
|
||||
- [Fix bug in My Cards and Global Search](https://github.com/wekan/wekan/pull/3687).
|
||||
Thanks to jrsupplee.
|
||||
- [Fix bug in Due Cards introduced by last bug fix](https://github.com/wekan/wekan/pull/3688).
|
||||
Thanks to jrsupplee.
|
||||
- [Fixed Bug: Move Swimlane to Archive does not work anymore. Fixed lint in
|
||||
router.js](https://github.com/wekan/wekan/commit/0b263cf582a9649ef72efbd289927105a27583af).
|
||||
Thanks to marcungeschikts and xet7.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- Updated sandstorm release script and added new node update script
|
||||
[Part 1](https://github.com/wekan/wekan/commit/34b6aa0858678da937eacf9a87878bbcb476fd4b),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/01de3f187c90af3ac94215ba7e8c7e780c98768d),
|
||||
[Part 3](https://github.com/wekan/wekan/commit/1d3673e9d320926127b46383321023f149287d6d),
|
||||
[Part 4](https://github.com/wekan/wekan/commit/09b9f690b162ae2797d1996e82c96ed8b8c74221),
|
||||
[Part 5](https://github.com/wekan/wekan/commit/6819303047eab17b03a0c28108fd9a2cfde23d20).
|
||||
Thanks to xet7.
|
||||
- Updated to Node.js v12.22.0
|
||||
[Part 1](https://github.com/wekan/wekan/commit/7a9c3972642601e9d89d5e7a3816643f91448c63),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/c2b7525864048694c39f9cbe8f8c4cd96e36f7aa).
|
||||
Thanks to Node.js developers.
|
||||
- Fixed release website script
|
||||
[Part 1](https://github.com/wekan/wekan/commit/b6f60c08e55c2620fe6724ae2b5da0d9dfc9ec31),
|
||||
[Part 2](https://github.com/wekan/wekan/commit/91dae9795ebd1a98b8ed12c68b78ad90a6983402).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.14 2021-03-29 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Clean-up Global Search, Due Cards, and My Cards. New environment variable `RESULTS_PER_PAGE` for search
|
||||
results](https://github.com/wekan/wekan/pull/3676).
|
||||
Thanks to jrsupplee.
|
||||
- [Added environment variable `RESULTS_PER_PAGE` to all Wekan platforms](https://github.com/wekan/wekan/commit/ba05f383ca29211c5474e06c5ba6673e712afe7a).
|
||||
Thanks to xet7.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Updated release scripts](https://github.com/wekan/wekan/commit/59580e4b0f711ca55e8cb0d73803a4ff8b56352d).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Require signed-in user for My Cards, Due Cards, and global search](https://github.com/wekan/wekan/pull/3677).
|
||||
Thanks to jrsupplee.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.13 2021-03-28 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fixed Sandstorm Wekan attachments upload](https://github.com/wekan/wekan/commit/d4a1611b86521cd5913277cfa2c86c43958eec7b).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.12 2021-03-28 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fix HTTP not defined](https://github.com/wekan/wekan/commit/4c609161915cc46ebfccad3d9e7ffdecdef1f85c).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.11 2021-03-28 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Added emoji picker to card description edit and card comment edit.
|
||||
Removed and disabled Summernote wysiwyg editor, package-lock.json
|
||||
etc](https://github.com/wekan/wekan/commit/84fde1ecfc81e89ed1895cab3bcb328e4f166a87).
|
||||
Thanks to xet7.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Updated dependencies. Fixed lint](https://github.com/wekan/wekan/commit/4e1c0fdce82e3b4add8c4ffd1832752181573e88).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.10 2021-03-28 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Move swimlane from one board to another](https://github.com/wekan/wekan/pull/3674).
|
||||
Thanks to jrsupplee.
|
||||
- [Added translatable Move Swimlane popup title](https://github.com/wekan/wekan/commit/16665bccf912c5e907739c35f7ef5a376c81740e).
|
||||
Thanks to xet7.
|
||||
- [REST API: Export one attachment](https://github.com/wekan/wekan/pull/3673).
|
||||
Thanks to vagnernascimento.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Updated package-lock.json](https://github.com/wekan/wekan/commit/3145ec65a3defb8ac8d97aed7e43595f661f7100).
|
||||
Thanks to developers of dependencies.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.09 2021-03-26 Wekan release
|
||||
|
||||
This release adds the following improvements:
|
||||
|
||||
- [Replace edit icon by plus-square on new links](https://github.com/wekan/wekan/pull/3671).
|
||||
Thanks to sim51.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix openapi docs generation](https://github.com/wekan/wekan/pull/3672).
|
||||
Thanks to bentiss.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.08 2021-03-26 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Admin Panel/Settings/Accounts: Hide system messages of all users](https://github.com/wekan/wekan/commit/a249ffc8054189d8e3db9b4c8f082cc7ce7dcb52).
|
||||
Thanks to bbyszio, r4nc0r and xet7.
|
||||
|
||||
and adds the following improvements:
|
||||
|
||||
- [Add Trello attached links to the card description](https://github.com/wekan/wekan/pull/3669).
|
||||
Thanks to jrsupplee.
|
||||
- [Added package-lock.json and updated .gitignore](https://github.com/wekan/wekan/commit/d532a3591f338cec9a3839d43d9a1e9d69f59dc2).
|
||||
Thanks to xet7.
|
||||
|
||||
and adds the following new translations:
|
||||
|
||||
- [Added translation: español de América Latina](https://github.com/wekan/wekan/commit/ccc9efb2703efda4e199a861920b9ec88e634b59).
|
||||
Thanks to translators.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix typos in translations](https://github.com/wekan/wekan/commit/a0e1b6f918dbb252a13db05d6b9e1f832c28654f).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.07 2021-03-19 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fixed sort cards feature](https://github.com/wekan/wekan/pull/3662).
|
||||
Thanks to ednamaeG.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v5.06 2021-03-18 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
+2
-1
@@ -12,7 +12,7 @@ ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates python3" \
|
||||
DEBUG=false \
|
||||
NODE_VERSION=v12.21.0 \
|
||||
NODE_VERSION=v12.22.1 \
|
||||
METEOR_RELEASE=1.10.2 \
|
||||
USE_EDGE=false \
|
||||
METEOR_EDGE=1.5-beta.17 \
|
||||
@@ -21,6 +21,7 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-
|
||||
ARCHITECTURE=linux-x64 \
|
||||
SRC_PATH=./ \
|
||||
WITH_API=true \
|
||||
RESULTS_PER_PAGE="" \
|
||||
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
|
||||
ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \
|
||||
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \
|
||||
|
||||
+9
-9
@@ -4,16 +4,16 @@ FROM amd64/alpine:3.7 AS builder
|
||||
ENV QEMU_VERSION=v4.2.0-6 \
|
||||
QEMU_ARCHITECTURE=aarch64 \
|
||||
NODE_ARCHITECTURE=linux-arm64 \
|
||||
NODE_VERSION=v12.21.0 \
|
||||
WEKAN_VERSION=3.96 \
|
||||
NODE_VERSION=v12.22.1 \
|
||||
WEKAN_VERSION=latest \
|
||||
WEKAN_ARCHITECTURE=arm64
|
||||
|
||||
# Install dependencies
|
||||
RUN apk update && apk add ca-certificates outils-sha1 && \
|
||||
\
|
||||
# Download qemu static for our architecture
|
||||
wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \
|
||||
\
|
||||
# Install dependencies
|
||||
RUN apk update && apk add ca-certificates outils-sha1 && \
|
||||
\
|
||||
# Download qemu static for our architecture
|
||||
wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \
|
||||
\
|
||||
# Download wekan and shasum
|
||||
wget https://releases.wekan.team/raspi3/wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
|
||||
wget https://releases.wekan.team/raspi3/SHA256SUMS.txt && \
|
||||
@@ -40,7 +40,7 @@ LABEL maintainer="wekan"
|
||||
# Set the environment variables (defaults where required)
|
||||
ENV QEMU_ARCHITECTURE=aarch64 \
|
||||
NODE_ARCHITECTURE=linux-arm64 \
|
||||
NODE_VERSION=v12.21.0 \
|
||||
NODE_VERSION=v12.22.1 \
|
||||
NODE_ENV=production \
|
||||
NPM_VERSION=latest \
|
||||
WITH_API=true \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[](https://gitpod.io/#https://github.com/wekan/wekan)
|
||||
[](https://gitpod.io/#https://github.com/wekan/wekan)
|
||||
|
||||
# Wekan - Open Source kanban
|
||||
|
||||
@@ -32,7 +32,7 @@ It's better than at chat where details get lost when chat scrolls up.
|
||||
|
||||
## FAQ
|
||||
|
||||
**NOTE**:
|
||||
**NOTE**:
|
||||
- Please read the [FAQ](https://github.com/wekan/wekan/wiki/FAQ) first
|
||||
- Please don't feed the [trolls](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-troll) and [spammers](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-spammer) that are mentioned in the FAQ :)
|
||||
|
||||
@@ -52,7 +52,7 @@ that by providing one-click installation on various platforms.
|
||||
|
||||
- Wekan is used in [most countries of the world](https://snapcraft.io/wekan).
|
||||
- Wekan largest user has 13k users using Wekan in their company.
|
||||
- Wekan has been [translated](https://transifex.com/wekan/wekan) to about 61 languages.
|
||||
- Wekan has been [translated](https://transifex.com/wekan/wekan) to about 63 languages.
|
||||
- [Features][features]: Wekan has real-time user interface.
|
||||
- [Platforms][platforms]: Wekan supports many platforms.
|
||||
Wekan is critical part of new platforms Wekan is currently being integrated to.
|
||||
@@ -64,7 +64,7 @@ that by providing one-click installation on various platforms.
|
||||
[More Platforms](https://github.com/wekan/wekan/wiki/Platforms), bundle for RasPi3 ARM and other CPUs where Node.js and MongoDB exists.
|
||||
- 1 GB RAM minimum free for Wekan. Production server should have minimum total 4 GB RAM.
|
||||
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/master/docker-compose.yml): 3 frontend servers,
|
||||
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
|
||||
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
|
||||
- Enough disk space and alerts about low disk space. If you run out disk space, MongoDB database gets corrupted.
|
||||
- SECURITY: Updating to newest Wekan version very often. Please check you do not have automatic updates of Sandstorm or Snap turned off.
|
||||
Old versions have security issues because of old versions Node.js etc. Only newest Wekan is supported.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v5.06.0"
|
||||
appVersion: "v5.26.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
template(name="commentForm")
|
||||
.new-comment.js-new-comment(
|
||||
class="{{#if commentFormIsOpen}}is-open{{/if}}")
|
||||
+userAvatar(userId=currentUser._id)
|
||||
+userAvatar(userId=currentUser._id noRemove=true)
|
||||
form.js-new-comment-form
|
||||
+editor(class="js-new-comment-input")
|
||||
| {{getUnsavedValue 'cardComment' currentCard._id}}
|
||||
|
||||
@@ -3,6 +3,7 @@ const commentFormIsOpen = new ReactiveVar(false);
|
||||
BlazeComponent.extendComponent({
|
||||
onDestroyed() {
|
||||
commentFormIsOpen.set(false);
|
||||
$('.note-popover').hide();
|
||||
},
|
||||
|
||||
commentFormIsOpen() {
|
||||
|
||||
@@ -34,9 +34,12 @@ template(name="boardHeaderBar")
|
||||
if $eq watchLevel "muted"
|
||||
i.fa.fa-bell-slash
|
||||
span {{_ watchLevel}}
|
||||
a.board-header-btn.js-sort-cards(title="{{_ 'sort-cards'}}")
|
||||
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
|
||||
i.fa.fa-sort
|
||||
| {{_ 'sort-cards'}} {{sortCardsBy.get}}
|
||||
span {{#if isSortActive }}{{_ 'Sort is on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
|
||||
if isSortActive
|
||||
a.board-header-btn-close.js-sort-reset(title="Remove Sort")
|
||||
i.fa.fa-times-thin
|
||||
|
||||
else
|
||||
a.board-header-btn.js-log-in(
|
||||
|
||||
@@ -129,6 +129,9 @@ BlazeComponent.extendComponent({
|
||||
Sidebar.setView();
|
||||
Filter.reset();
|
||||
},
|
||||
'click .js-sort-reset'() {
|
||||
Session.set('sortBy', '');
|
||||
},
|
||||
'click .js-open-search-view'() {
|
||||
Sidebar.setView('search');
|
||||
},
|
||||
@@ -162,6 +165,9 @@ Template.boardHeaderBar.helpers({
|
||||
boardView() {
|
||||
return Utils.boardView();
|
||||
},
|
||||
isSortActive() {
|
||||
return Session.get('sortBy') ? true : false;
|
||||
},
|
||||
});
|
||||
|
||||
Template.boardChangeViewPopup.events({
|
||||
@@ -407,14 +413,6 @@ BlazeComponent.extendComponent({
|
||||
sortCardsBy.set(TAPi18n.__('date-created-oldest-first'));
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-sort-default'() {
|
||||
const sortBy = {
|
||||
sort: 1,
|
||||
};
|
||||
Session.set('sortBy', sortBy);
|
||||
sortCardsBy.set(TAPi18n.__('default'));
|
||||
Popup.close();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
@@ -119,3 +119,24 @@ template(name="cardCustomField-dropdown")
|
||||
if value
|
||||
+viewer
|
||||
= selectedItem
|
||||
|
||||
template(name="cardCustomField-stringtemplate")
|
||||
if canModifyCard
|
||||
+inlinedForm(classNames="js-card-customfield-stringtemplate")
|
||||
each item in stringtemplateItems.get
|
||||
input.js-card-customfield-stringtemplate-item(type="text" value=item placeholder="")
|
||||
input.js-card-customfield-stringtemplate-item.last(type="text" value="" placeholder="{{_ 'custom-field-stringtemplate-item-placeholder'}}")
|
||||
.edit-controls.clearfix
|
||||
button.primary(type="submit") {{_ 'save'}}
|
||||
a.fa.fa-times-thin.js-close-inlined-form
|
||||
else
|
||||
a.js-open-inlined-form
|
||||
if value
|
||||
+viewer
|
||||
= formattedValue
|
||||
else
|
||||
| {{_ 'edit'}}
|
||||
else
|
||||
if value
|
||||
+viewer
|
||||
= formattedValue
|
||||
@@ -1,3 +1,6 @@
|
||||
import { DatePicker } from '/client/lib/datepicker';
|
||||
import Cards from '/models/cards';
|
||||
|
||||
Template.cardCustomFieldsPopup.helpers({
|
||||
hasCustomField() {
|
||||
const card = Cards.findOne(Session.get('currentCard'));
|
||||
@@ -234,3 +237,90 @@ CardCustomField.register('cardCustomField');
|
||||
];
|
||||
}
|
||||
}.register('cardCustomField-dropdown'));
|
||||
|
||||
// cardCustomField-stringtemplate
|
||||
(class extends CardCustomField {
|
||||
onCreated() {
|
||||
super.onCreated();
|
||||
|
||||
this.stringtemplateFormat = this.data().definition.settings.stringtemplateFormat;
|
||||
this.stringtemplateSeparator = this.data().definition.settings.stringtemplateSeparator;
|
||||
|
||||
this.stringtemplateItems = new ReactiveVar(this.data().value ?? []);
|
||||
}
|
||||
|
||||
formattedValue() {
|
||||
return (this.data().value ?? [])
|
||||
.filter(value => !!value.trim())
|
||||
.map(value => this.stringtemplateFormat.replace(/%\{value\}/gi, value))
|
||||
.join(this.stringtemplateSeparator ?? '');
|
||||
}
|
||||
|
||||
getItems() {
|
||||
return Array.from(this.findAll('input'))
|
||||
.map(input => input.value)
|
||||
.filter(value => !!value.trim());
|
||||
}
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'submit .js-card-customfield-stringtemplate'(event) {
|
||||
event.preventDefault();
|
||||
const items = this.getItems();
|
||||
this.card.setCustomField(this.customFieldId, items);
|
||||
},
|
||||
|
||||
'keydown .js-card-customfield-stringtemplate-item'(event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
this.find('button[type=submit]').click();
|
||||
} else if (event.target.value.trim()) {
|
||||
const inputLast = this.find('input.last');
|
||||
|
||||
let items = this.getItems();
|
||||
|
||||
if (event.target === inputLast) {
|
||||
inputLast.value = '';
|
||||
} else if (event.target.nextSibling === inputLast) {
|
||||
inputLast.focus();
|
||||
} else {
|
||||
event.target.blur();
|
||||
|
||||
const idx = Array.from(this.findAll('input')).indexOf(
|
||||
event.target,
|
||||
);
|
||||
items.splice(idx + 1, 0, '');
|
||||
|
||||
Tracker.afterFlush(() => {
|
||||
const element = this.findAll('input')[idx + 1];
|
||||
element.focus();
|
||||
element.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
this.stringtemplateItems.set(items);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'blur .js-card-customfield-stringtemplate-item'(event) {
|
||||
if (
|
||||
!event.target.value.trim() ||
|
||||
event.target === this.find('input.last')
|
||||
) {
|
||||
const items = this.getItems();
|
||||
this.stringtemplateItems.set(items);
|
||||
this.find('input.last').value = '';
|
||||
}
|
||||
},
|
||||
|
||||
'click .js-close-inlined-form'(event) {
|
||||
this.stringtemplateItems.set(this.data().value ?? []);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
}.register('cardCustomField-stringtemplate'));
|
||||
@@ -1,122 +1,4 @@
|
||||
// Helper function to replace HH with H for 24 hours format, because H allows also single-digit hours
|
||||
function adjustedTimeFormat() {
|
||||
return moment
|
||||
.localeData()
|
||||
.longDateFormat('LT')
|
||||
.replace(/HH/i, 'H');
|
||||
}
|
||||
|
||||
// Edit received, start, due & end dates
|
||||
BlazeComponent.extendComponent({
|
||||
template() {
|
||||
return 'editCardDate';
|
||||
},
|
||||
|
||||
onCreated() {
|
||||
this.error = new ReactiveVar('');
|
||||
this.card = this.data();
|
||||
this.date = new ReactiveVar(moment.invalid());
|
||||
},
|
||||
|
||||
onRendered() {
|
||||
const $picker = this.$('.js-datepicker')
|
||||
.datepicker({
|
||||
todayHighlight: true,
|
||||
todayBtn: 'linked',
|
||||
language: TAPi18n.getLanguage(),
|
||||
})
|
||||
.on(
|
||||
'changeDate',
|
||||
function(evt) {
|
||||
this.find('#date').value = moment(evt.date).format('L');
|
||||
this.error.set('');
|
||||
this.find('#time').focus();
|
||||
}.bind(this),
|
||||
);
|
||||
|
||||
if (this.date.get().isValid()) {
|
||||
$picker.datepicker('update', this.date.get().toDate());
|
||||
}
|
||||
},
|
||||
|
||||
showDate() {
|
||||
if (this.date.get().isValid()) return this.date.get().format('L');
|
||||
return '';
|
||||
},
|
||||
showTime() {
|
||||
if (this.date.get().isValid()) return this.date.get().format('LT');
|
||||
return '';
|
||||
},
|
||||
dateFormat() {
|
||||
return moment.localeData().longDateFormat('L');
|
||||
},
|
||||
timeFormat() {
|
||||
return moment.localeData().longDateFormat('LT');
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'keyup .js-date-field'() {
|
||||
// parse for localized date format in strict mode
|
||||
const dateMoment = moment(this.find('#date').value, 'L', true);
|
||||
if (dateMoment.isValid()) {
|
||||
this.error.set('');
|
||||
this.$('.js-datepicker').datepicker('update', dateMoment.toDate());
|
||||
}
|
||||
},
|
||||
'keyup .js-time-field'() {
|
||||
// parse for localized time format in strict mode
|
||||
const dateMoment = moment(
|
||||
this.find('#time').value,
|
||||
adjustedTimeFormat(),
|
||||
true,
|
||||
);
|
||||
if (dateMoment.isValid()) {
|
||||
this.error.set('');
|
||||
}
|
||||
},
|
||||
'submit .edit-date'(evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
// if no time was given, init with 12:00
|
||||
const time =
|
||||
evt.target.time.value ||
|
||||
moment(new Date().setHours(12, 0, 0)).format('LT');
|
||||
const newTime = moment(time, adjustedTimeFormat(), true);
|
||||
const newDate = moment(evt.target.date.value, 'L', true);
|
||||
const dateString = `${evt.target.date.value} ${time}`;
|
||||
const newCompleteDate = moment(
|
||||
dateString,
|
||||
'L ' + adjustedTimeFormat(),
|
||||
true,
|
||||
);
|
||||
if (!newTime.isValid()) {
|
||||
this.error.set('invalid-time');
|
||||
evt.target.time.focus();
|
||||
}
|
||||
if (!newDate.isValid()) {
|
||||
this.error.set('invalid-date');
|
||||
evt.target.date.focus();
|
||||
}
|
||||
if (newCompleteDate.isValid()) {
|
||||
this._storeDate(newCompleteDate.toDate());
|
||||
Popup.close();
|
||||
} else {
|
||||
if (!this.error) {
|
||||
this.error.set('invalid');
|
||||
}
|
||||
}
|
||||
},
|
||||
'click .js-delete-date'(evt) {
|
||||
evt.preventDefault();
|
||||
this._deleteDate();
|
||||
Popup.close();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
import { DatePicker } from '/client/lib/datepicker';
|
||||
|
||||
Template.dateBadge.helpers({
|
||||
canModifyCard() {
|
||||
|
||||
@@ -3,5 +3,5 @@ template(name="descriptionForm")
|
||||
.new-description.js-new-description(
|
||||
class="{{#if descriptionFormIsOpen}}is-open{{/if}}")
|
||||
form.js-new-description-form
|
||||
+editor(class="js-new-description-input" data-meteor-emoji-large="true" autofocus="autofocus")
|
||||
+editor(class="js-new-description-input" autofocus="autofocus")
|
||||
| {{getUnsavedValue 'cardDescription' _id getDescription}}
|
||||
@@ -3,6 +3,7 @@ const descriptionFormIsOpen = new ReactiveVar(false);
|
||||
BlazeComponent.extendComponent({
|
||||
onDestroyed() {
|
||||
descriptionFormIsOpen.set(false);
|
||||
$('.note-popover').hide();
|
||||
},
|
||||
|
||||
descriptionFormIsOpen() {
|
||||
|
||||
@@ -110,15 +110,24 @@ template(name="cardDetails")
|
||||
a.card-label.add-label.js-end-date
|
||||
i.fa.fa-plus
|
||||
|
||||
hr
|
||||
if currentBoard.allowsCreator
|
||||
.card-details-item.card-details-item-creator
|
||||
h3.card-details-item-title
|
||||
i.fa.fa-user
|
||||
| {{_ 'creator'}}
|
||||
|
||||
+userAvatar(userId=userId noRemove=true)
|
||||
| {{! XXX Hack to hide syntaxic coloration /// }}
|
||||
|
||||
//.card-details-items
|
||||
if currentBoard.allowsMembers
|
||||
hr
|
||||
.card-details-item.card-details-item-members
|
||||
h3.card-details-item-title
|
||||
i.fa.fa-users
|
||||
| {{_ 'members'}}
|
||||
each getMembers
|
||||
+userAvatar(userId=this cardId=../_id)
|
||||
each userId in getMembers
|
||||
+userAvatar(userId=userId cardId=_id)
|
||||
| {{! XXX Hack to hide syntaxic coloration /// }}
|
||||
if canModifyCard
|
||||
unless currentUser.isWorker
|
||||
@@ -131,8 +140,8 @@ template(name="cardDetails")
|
||||
h3.card-details-item-title
|
||||
i.fa.fa-user
|
||||
| {{_ 'assignee'}}
|
||||
each getAssignees
|
||||
+userAvatarAssignee(userId=this cardId=../_id)
|
||||
each userId in getAssignees
|
||||
+userAvatar(userId=userId cardId=_id assignee=true)
|
||||
| {{! XXX Hack to hide syntaxic coloration /// }}
|
||||
if canModifyCard
|
||||
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
|
||||
@@ -488,23 +497,6 @@ template(name="cardAssigneesPopup")
|
||||
if currentUser.isCardAssignee
|
||||
i.fa.fa-check
|
||||
|
||||
template(name="userAvatarAssignee")
|
||||
a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
|
||||
if userData.profile.avatarUrl
|
||||
img.avatar.avatar-image(src="{{userData.profile.avatarUrl}}")
|
||||
else
|
||||
+userAvatarAssigneeInitials(userId=userData._id)
|
||||
|
||||
if showStatus
|
||||
span.assignee-presence-status(class=presenceStatusClassName)
|
||||
span.member-type(class=memberType)
|
||||
|
||||
unless isSandstorm
|
||||
if showEdit
|
||||
if $eq currentUser._id userData._id
|
||||
a.edit-avatar.js-change-avatar
|
||||
i.fa.fa-pencil
|
||||
|
||||
template(name="cardAssigneePopup")
|
||||
.board-assignee-menu
|
||||
.mini-profile-info
|
||||
@@ -522,10 +514,6 @@ template(name="cardAssigneePopup")
|
||||
with currentUser
|
||||
li: a.js-edit-profile {{_ 'edit-profile'}}
|
||||
|
||||
template(name="userAvatarAssigneeInitials")
|
||||
svg.avatar.avatar-assignee-initials(viewBox="0 0 {{viewPortWidth}} 15")
|
||||
text(x="50%" y="13" text-anchor="middle")= initials
|
||||
|
||||
template(name="cardMorePopup")
|
||||
p.quiet
|
||||
span.clearfix
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import { DatePicker } from '/client/lib/datepicker';
|
||||
import Cards from '/models/cards';
|
||||
import Boards from '/models/boards';
|
||||
import Checklists from '/models/checklists';
|
||||
import Integrations from '/models/integrations';
|
||||
import Users from '/models/users';
|
||||
import Lists from '/models/lists';
|
||||
import CardComments from '/models/cardComments';
|
||||
import { ALLOWED_COLORS } from '/config/const';
|
||||
import moment from 'moment';
|
||||
import { UserAvatar } from '../users/userAvatar';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
const { calculateIndexData } = Utils;
|
||||
|
||||
let cardColors;
|
||||
Meteor.startup(() => {
|
||||
cardColors = Cards.simpleSchema()._schema.color.allowedValues;
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
mixins() {
|
||||
return [Mixins.InfiniteScrolling];
|
||||
@@ -160,9 +167,7 @@ BlazeComponent.extendComponent({
|
||||
integration,
|
||||
'CardSelected',
|
||||
params,
|
||||
() => {
|
||||
return;
|
||||
},
|
||||
() => {},
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -290,7 +295,7 @@ BlazeComponent.extendComponent({
|
||||
Utils.goBoardId(this.data().boardId);
|
||||
},
|
||||
'click .js-copy-link'() {
|
||||
StringToCopyElement = document.getElementById('cardURL_copy');
|
||||
const StringToCopyElement = document.getElementById('cardURL_copy');
|
||||
StringToCopyElement.value =
|
||||
window.location.origin + window.location.pathname;
|
||||
StringToCopyElement.select();
|
||||
@@ -407,122 +412,6 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
}).register('cardDetails');
|
||||
|
||||
Template.cardDetails.helpers({
|
||||
userData() {
|
||||
// We need to handle a special case for the search results provided by the
|
||||
// `matteodem:easy-search` package. Since these results gets published in a
|
||||
// separate collection, and not in the standard Meteor.Users collection as
|
||||
// expected, we use a component parameter ("property") to distinguish the
|
||||
// two cases.
|
||||
const userCollection = this.esSearch ? ESSearchResults : Users;
|
||||
return userCollection.findOne(this.userId, {
|
||||
fields: {
|
||||
profile: 1,
|
||||
username: 1,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
receivedSelected() {
|
||||
if (this.getReceived().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
startSelected() {
|
||||
if (this.getStart().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
endSelected() {
|
||||
if (this.getEnd().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
dueSelected() {
|
||||
if (this.getDue().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
memberSelected() {
|
||||
if (this.getMembers().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
labelSelected() {
|
||||
if (this.getLabels().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
assigneeSelected() {
|
||||
if (this.getAssignees().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
requestBySelected() {
|
||||
if (this.getRequestBy().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
assigneeBySelected() {
|
||||
if (this.getAssigneeBy().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
memberType() {
|
||||
const user = Users.findOne(this.userId);
|
||||
return user && user.isBoardAdmin() ? 'admin' : 'normal';
|
||||
},
|
||||
|
||||
presenceStatusClassName() {
|
||||
const user = Users.findOne(this.userId);
|
||||
const userPresence = presences.findOne({ userId: this.userId });
|
||||
if (user && user.isInvitedTo(Session.get('currentBoard'))) return 'pending';
|
||||
else if (!userPresence) return 'disconnected';
|
||||
else if (Session.equals('currentBoard', userPresence.state.currentBoardId))
|
||||
return 'active';
|
||||
else return 'idle';
|
||||
},
|
||||
});
|
||||
|
||||
Template.userAvatarAssigneeInitials.helpers({
|
||||
initials() {
|
||||
const user = Users.findOne(this.userId);
|
||||
return user && user.getInitials();
|
||||
},
|
||||
|
||||
viewPortWidth() {
|
||||
const user = Users.findOne(this.userId);
|
||||
return ((user && user.getInitials().length) || 1) * 12;
|
||||
},
|
||||
});
|
||||
|
||||
// We extends the normal InlinedForm component to support UnsavedEdits draft
|
||||
// feature.
|
||||
(class extends InlinedForm {
|
||||
@@ -697,7 +586,7 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
boards() {
|
||||
const boards = Boards.find(
|
||||
return Boards.find(
|
||||
{
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
@@ -707,7 +596,6 @@ BlazeComponent.extendComponent({
|
||||
sort: { sort: 1 /* boards default sorting */ },
|
||||
},
|
||||
);
|
||||
return boards;
|
||||
},
|
||||
|
||||
swimlanes() {
|
||||
@@ -736,7 +624,7 @@ Template.copyCardPopup.events({
|
||||
'click .js-done'() {
|
||||
const card = Cards.findOne(Session.get('currentCard'));
|
||||
const lSelect = $('.js-select-lists')[0];
|
||||
listId = lSelect.options[lSelect.selectedIndex].value;
|
||||
const listId = lSelect.options[lSelect.selectedIndex].value;
|
||||
const slSelect = $('.js-select-swimlanes')[0];
|
||||
const swimlaneId = slSelect.options[slSelect.selectedIndex].value;
|
||||
const bSelect = $('.js-select-boards')[0];
|
||||
@@ -801,7 +689,7 @@ Template.copyChecklistToManyCardsPopup.events({
|
||||
});
|
||||
|
||||
// copy subtasks
|
||||
cursor = Cards.find({ parentId: oldId });
|
||||
const cursor = Cards.find({ parentId: oldId });
|
||||
cursor.forEach(function() {
|
||||
'use strict';
|
||||
const subtask = arguments[0];
|
||||
@@ -827,7 +715,7 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
colors() {
|
||||
return cardColors.map(color => ({ color, name: '' }));
|
||||
return ALLOWED_COLORS.map(color => ({ color, name: '' }));
|
||||
},
|
||||
|
||||
isSelected(color) {
|
||||
@@ -871,7 +759,7 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
boards() {
|
||||
const boards = Boards.find(
|
||||
return Boards.find(
|
||||
{
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
@@ -883,7 +771,6 @@ BlazeComponent.extendComponent({
|
||||
sort: { sort: 1 /* boards default sorting */ },
|
||||
},
|
||||
);
|
||||
return boards;
|
||||
},
|
||||
|
||||
cards() {
|
||||
|
||||
@@ -211,6 +211,7 @@ avatar-radius = 50%
|
||||
word-wrap: break-word
|
||||
max-width: 36%
|
||||
flex-grow: 1
|
||||
&.card-details-item-creator,
|
||||
&.card-details-item-received,
|
||||
&.card-details-item-start,
|
||||
&.card-details-item-due,
|
||||
|
||||
@@ -82,6 +82,9 @@ template(name="minicard")
|
||||
+minicardCustomFieldDate
|
||||
else if $eq definition.type "checkbox"
|
||||
.materialCheckBox(class="{{#if value }}is-checked{{/if}}")
|
||||
else if $eq definition.type "stringtemplate"
|
||||
+viewer
|
||||
= formattedStringtemplateCustomFieldValue(definition)
|
||||
else
|
||||
+viewer
|
||||
= trueValue
|
||||
@@ -96,6 +99,10 @@ template(name="minicard")
|
||||
each getMembers
|
||||
+userAvatar(userId=this)
|
||||
|
||||
if showCreator
|
||||
.minicard-creator
|
||||
+userAvatar(userId=this.userId noRemove=true)
|
||||
|
||||
.badges
|
||||
unless currentUser.isNoComments
|
||||
if comments.count
|
||||
@@ -122,3 +129,8 @@ template(name="minicard")
|
||||
.badge(class="{{#if checklistFinished}}is-finished{{/if}}")
|
||||
span.badge-icon.fa.fa-check-square-o
|
||||
span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}}
|
||||
if allSubtasks.count
|
||||
.badge
|
||||
span.badge-icon.fa.fa-sitemap
|
||||
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
|
||||
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
|
||||
@@ -21,6 +21,34 @@ BlazeComponent.extendComponent({
|
||||
}).format(customFieldTrueValue);
|
||||
},
|
||||
|
||||
formattedStringtemplateCustomFieldValue(definition) {
|
||||
const customField = this.data()
|
||||
.customFieldsWD()
|
||||
.find(f => f._id === definition._id);
|
||||
|
||||
const customFieldTrueValue =
|
||||
customField && customField.trueValue ? customField.trueValue : [];
|
||||
|
||||
return customFieldTrueValue
|
||||
.filter(value => !!value.trim())
|
||||
.map(value =>
|
||||
definition.settings.stringtemplateFormat.replace(/%\{value\}/gi, value),
|
||||
)
|
||||
.join(definition.settings.stringtemplateSeparator ?? '');
|
||||
},
|
||||
|
||||
showCreator() {
|
||||
if (this.data().board()) {
|
||||
return (
|
||||
this.data().board.allowsCreator === null ||
|
||||
this.data().board().allowsCreator === undefined ||
|
||||
this.data().board().allowsCreator
|
||||
);
|
||||
// return this.data().board().allowsCreator;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
border-radius: 2px
|
||||
margin-right: 3px
|
||||
margin-bottom: 3px
|
||||
|
||||
|
||||
.minicard-custom-fields
|
||||
display:block;
|
||||
.minicard-custom-field
|
||||
@@ -163,7 +163,8 @@
|
||||
line-height: 12px
|
||||
|
||||
.minicard-members,
|
||||
.minicard-assignees
|
||||
.minicard-assignees,
|
||||
.minicard-creator
|
||||
float: right
|
||||
margin-left: 5px
|
||||
margin-bottom: 4px
|
||||
@@ -187,6 +188,9 @@
|
||||
.minicard-assignees
|
||||
border-bottom: 1px solid red
|
||||
|
||||
.minicard-creator
|
||||
border-bottom: 1px solid green
|
||||
|
||||
.minicard-members:empty,
|
||||
.minicard-assignees:empty
|
||||
display: none
|
||||
|
||||
@@ -6,8 +6,12 @@ template(name="resultCard")
|
||||
ul.result-card-context-list
|
||||
li.result-card-context(title="{{_ 'board'}}")
|
||||
.result-card-block-wrapper
|
||||
+viewer
|
||||
= getBoard.title
|
||||
if boardId
|
||||
+viewer
|
||||
= getBoard.title
|
||||
else
|
||||
.broken-cards-null
|
||||
| NULL
|
||||
if getBoard.archived
|
||||
i.fa.fa-archive
|
||||
li.result-card-context.result-card-context-separator
|
||||
@@ -16,8 +20,12 @@ template(name="resultCard")
|
||||
= ' '
|
||||
li.result-card-context(title="{{_ 'swimlane'}}")
|
||||
.result-card-block-wrapper
|
||||
+viewer
|
||||
= getSwimlane.title
|
||||
if swimlaneId
|
||||
+viewer
|
||||
= getSwimlane.title
|
||||
else
|
||||
.broken-cards-null
|
||||
| NULL
|
||||
if getSwimlane.archived
|
||||
i.fa.fa-archive
|
||||
li.result-card-context.result-card-context-separator
|
||||
@@ -26,7 +34,11 @@ template(name="resultCard")
|
||||
= ' '
|
||||
li.result-card-context(title="{{_ 'list'}}")
|
||||
.result-card-block-wrapper
|
||||
+viewer
|
||||
= getList.title
|
||||
if listId
|
||||
+viewer
|
||||
= getList.title
|
||||
else
|
||||
.broken-cards-null
|
||||
| NULL
|
||||
if getList.archived
|
||||
i.fa.fa-archive
|
||||
@@ -3,39 +3,15 @@ template(name="brokenCardsHeaderBar")
|
||||
| {{_ 'broken-cards'}}
|
||||
|
||||
template(name="brokenCards")
|
||||
.wrapper
|
||||
.broken-cards-wrapper
|
||||
each card in brokenCardsList
|
||||
.broken-cards-card-wrapper
|
||||
.broken-cards-card-title
|
||||
= card.title
|
||||
ul.broken-cards-context-list
|
||||
li.broken-cards-context(title="{{_ 'board'}}")
|
||||
if card.boardId
|
||||
+viewer
|
||||
= card.getBoard.title
|
||||
else
|
||||
.broken-cards-null
|
||||
| NULL
|
||||
li.broken-cards-context.broken-cards-context-separator
|
||||
= ' '
|
||||
| {{_ 'context-separator'}}
|
||||
= ' '
|
||||
li.broken-cards-context(title="{{_ 'swimlane'}}")
|
||||
if card.swimlaneId
|
||||
+viewer
|
||||
= card.getSwimlane.title
|
||||
else
|
||||
.broken-cards-null
|
||||
| NULL
|
||||
li.broken-cards-context
|
||||
= ' '
|
||||
| {{_ 'context-separator'}}
|
||||
= ' '
|
||||
li.broken-cards-context(title="{{_ 'list'}}")
|
||||
if card.listId
|
||||
+viewer
|
||||
= card.getList.title
|
||||
else
|
||||
.broken-cards-null
|
||||
| NULL
|
||||
if currentUser
|
||||
if searching.get
|
||||
+spinner
|
||||
else if hasResults.get
|
||||
.global-search-results-list-wrapper
|
||||
if hasQueryErrors.get
|
||||
div
|
||||
each msg in errorMessages
|
||||
span.global-search-error-messages
|
||||
= msg
|
||||
else
|
||||
+resultsPaged(this)
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CardSearchPagedComponent } from '../../lib/cardSearch';
|
||||
|
||||
BlazeComponent.extendComponent({}).register('brokenCardsHeaderBar');
|
||||
|
||||
Template.brokenCards.helpers({
|
||||
@@ -6,23 +8,11 @@ Template.brokenCards.helpers({
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
class BrokenCardsComponent extends CardSearchPagedComponent {
|
||||
onCreated() {
|
||||
Meteor.subscribe('setting');
|
||||
Meteor.subscribe('brokenCards');
|
||||
},
|
||||
super.onCreated();
|
||||
|
||||
brokenCardsList() {
|
||||
const selector = {
|
||||
$or: [
|
||||
{ boardId: { $in: [null, ''] } },
|
||||
{ swimlaneId: { $in: [null, ''] } },
|
||||
{ listId: { $in: [null, ''] } },
|
||||
{ permission: 'public' },
|
||||
{ members: { $elemMatch: { userId: user._id, isActive: true } } },
|
||||
],
|
||||
};
|
||||
|
||||
return Cards.find(selector);
|
||||
},
|
||||
}).register('brokenCards');
|
||||
Meteor.subscribe('brokenCards', this.sessionId);
|
||||
}
|
||||
}
|
||||
BrokenCardsComponent.register('brokenCards');
|
||||
@@ -22,13 +22,17 @@ template(name="dueCardsModalTitle")
|
||||
|
||||
template(name="dueCards")
|
||||
if currentUser
|
||||
if isPageReady.get
|
||||
.wrapper
|
||||
.due-cards-dueat-list-wrapper
|
||||
each card in dueCardsList
|
||||
+resultCard(card)
|
||||
else
|
||||
if searching.get
|
||||
+spinner
|
||||
else if hasResults.get
|
||||
.global-search-results-list-wrapper
|
||||
if hasQueryErrors.get
|
||||
div
|
||||
each msg in errorMessages
|
||||
span.global-search-error-messages
|
||||
= msg
|
||||
else
|
||||
+resultsPaged(this)
|
||||
|
||||
template(name="dueCardsViewChangePopup")
|
||||
if currentUser
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
const subManager = new SubsManager();
|
||||
import { CardSearchPagedComponent } from '../../lib/cardSearch';
|
||||
import {
|
||||
OPERATOR_HAS,
|
||||
OPERATOR_SORT,
|
||||
OPERATOR_USER,
|
||||
ORDER_ASCENDING,
|
||||
PREDICATE_DUE_AT,
|
||||
} from '../../../config/search-const';
|
||||
import { QueryParams } from '../../../config/query-classes';
|
||||
|
||||
// const subManager = new SubsManager();
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
dueCardsView() {
|
||||
@@ -40,106 +50,51 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
}).register('dueCardsViewChangePopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
class DueCardsComponent extends CardSearchPagedComponent {
|
||||
onCreated() {
|
||||
this.isPageReady = new ReactiveVar(false);
|
||||
super.onCreated();
|
||||
|
||||
this.autorun(() => {
|
||||
const handle = subManager.subscribe(
|
||||
'dueCards',
|
||||
Utils.dueCardsView() === 'all',
|
||||
);
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
this.isPageReady.set(handle.ready());
|
||||
});
|
||||
});
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.addPredicate(OPERATOR_HAS, {
|
||||
field: PREDICATE_DUE_AT,
|
||||
exists: true,
|
||||
});
|
||||
Meteor.subscribe('setting');
|
||||
},
|
||||
// queryParams[OPERATOR_LIMIT] = 5;
|
||||
queryParams.addPredicate(OPERATOR_SORT, {
|
||||
name: PREDICATE_DUE_AT,
|
||||
order: ORDER_ASCENDING,
|
||||
});
|
||||
|
||||
if (Utils.dueCardsView() !== 'all') {
|
||||
queryParams.addPredicate(OPERATOR_USER, Meteor.user().username);
|
||||
}
|
||||
|
||||
this.runGlobalSearch(queryParams);
|
||||
}
|
||||
|
||||
dueCardsView() {
|
||||
// eslint-disable-next-line no-console
|
||||
//console.log('sort:', Utils.dueCardsView());
|
||||
return Utils.dueCardsView();
|
||||
},
|
||||
}
|
||||
|
||||
sortByBoard() {
|
||||
return this.dueCardsView() === 'board';
|
||||
},
|
||||
}
|
||||
|
||||
dueCardsList() {
|
||||
const allUsers = Utils.dueCardsView() === 'all';
|
||||
|
||||
const user = Meteor.user();
|
||||
|
||||
const archivedBoards = [];
|
||||
Boards.find({ archived: true }).forEach(board => {
|
||||
archivedBoards.push(board._id);
|
||||
});
|
||||
|
||||
const permiitedBoards = [];
|
||||
let selector = {
|
||||
archived: false,
|
||||
};
|
||||
// for every user including admin allow her to see cards only from public boards
|
||||
// or those where she is a member
|
||||
//if (!user.isAdmin) {
|
||||
selector.$or = [
|
||||
{ permission: 'public' },
|
||||
{ members: { $elemMatch: { userId: user._id, isActive: true } } },
|
||||
];
|
||||
//}
|
||||
Boards.find(selector).forEach(board => {
|
||||
permiitedBoards.push(board._id);
|
||||
});
|
||||
|
||||
const archivedSwimlanes = [];
|
||||
Swimlanes.find({ archived: true }).forEach(swimlane => {
|
||||
archivedSwimlanes.push(swimlane._id);
|
||||
});
|
||||
|
||||
const archivedLists = [];
|
||||
Lists.find({ archived: true }).forEach(list => {
|
||||
archivedLists.push(list._id);
|
||||
});
|
||||
|
||||
selector = {
|
||||
archived: false,
|
||||
boardId: {
|
||||
$nin: archivedBoards,
|
||||
$in: permiitedBoards,
|
||||
},
|
||||
swimlaneId: { $nin: archivedSwimlanes },
|
||||
listId: { $nin: archivedLists },
|
||||
dueAt: { $ne: null },
|
||||
endAt: null,
|
||||
};
|
||||
|
||||
if (!allUsers) {
|
||||
selector.$or = [{ members: user._id }, { assignees: user._id }];
|
||||
const results = this.getResults();
|
||||
console.log('results:', results);
|
||||
const cards = [];
|
||||
if (results) {
|
||||
results.forEach(card => {
|
||||
cards.push(card);
|
||||
});
|
||||
}
|
||||
|
||||
const cards = [];
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('cards selector:', selector);
|
||||
Cards.find(selector).forEach(card => {
|
||||
cards.push(card);
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log(
|
||||
// 'board:',
|
||||
// card.board(),
|
||||
// 'swimlane:',
|
||||
// card.swimlane(),
|
||||
// 'list:',
|
||||
// card.list(),
|
||||
// );
|
||||
});
|
||||
|
||||
cards.sort((a, b) => {
|
||||
const x = a.dueAt === null ? Date('2100-12-31') : a.dueAt;
|
||||
const y = b.dueAt === null ? Date('2100-12-31') : b.dueAt;
|
||||
const x = a.dueAt === null ? new Date('2100-12-31') : a.dueAt;
|
||||
const y = b.dueAt === null ? new Date('2100-12-31') : b.dueAt;
|
||||
|
||||
if (x > y) return 1;
|
||||
else if (x < y) return -1;
|
||||
@@ -148,7 +103,9 @@ BlazeComponent.extendComponent({
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('cards:', cards);
|
||||
console.log('cards:', cards);
|
||||
return cards;
|
||||
},
|
||||
}).register('dueCards');
|
||||
}
|
||||
}
|
||||
|
||||
DueCardsComponent.register('dueCards');
|
||||
@@ -49,7 +49,7 @@ Template.editor.onRendered(() => {
|
||||
['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']], // modal popup has issue somehow :(
|
||||
['insert', ['link']], //, 'picture']], // modal popup has issue somehow :(
|
||||
['view', ['fullscreen', 'help']],
|
||||
];
|
||||
const cleanPastedHTML = function(input) {
|
||||
@@ -145,6 +145,7 @@ Template.editor.onRendered(() => {
|
||||
const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
|
||||
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
|
||||
const insertImage = src => {
|
||||
// process all image upload types to the description/comment window
|
||||
const img = document.createElement('img');
|
||||
img.src = src;
|
||||
img.setAttribute('width', '100%');
|
||||
@@ -210,7 +211,16 @@ Template.editor.onRendered(() => {
|
||||
}
|
||||
}
|
||||
},
|
||||
onPaste() {
|
||||
onPaste(e) {
|
||||
var clipboardData = e.clipboardData;
|
||||
var pastedData = clipboardData.getData('Text');
|
||||
|
||||
//if pasted data is an image, exit
|
||||
if( !pastedData.length ){
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// clear up unwanted tag info when user pasted in text
|
||||
const thisNote = this;
|
||||
const updatePastedText = function(object) {
|
||||
@@ -219,7 +229,7 @@ Template.editor.onRendered(() => {
|
||||
// (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');
|
||||
//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.
|
||||
@@ -231,20 +241,20 @@ Template.editor.onRendered(() => {
|
||||
//the function is called before the text is really pasted.
|
||||
updatePastedText(thisNote);
|
||||
}, 10);
|
||||
},
|
||||
}
|
||||
},
|
||||
dialogsInBody: true,
|
||||
disableDragAndDrop: true,
|
||||
spellCheck: true,
|
||||
disableGrammar: false,
|
||||
disableDragAndDrop: false,
|
||||
toolbar,
|
||||
popover: {
|
||||
image: [
|
||||
[
|
||||
'image',
|
||||
['resizeFull', 'resizeHalf', 'resizeQuarter', 'resizeNone'],
|
||||
],
|
||||
['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']],
|
||||
['float', ['floatLeft', 'floatRight', 'floatNone']],
|
||||
['remove', ['removeMedia']],
|
||||
],
|
||||
link: [['link', ['linkDialogShow', 'unlink']]],
|
||||
table: [
|
||||
['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']],
|
||||
['delete', ['deleteRow', 'deleteCol', 'deleteTable']],
|
||||
|
||||
@@ -10,11 +10,30 @@ template(name="globalSearchModalTitle")
|
||||
i.fa.fa-keyboard-o
|
||||
| {{_ 'globalSearch-title'}}
|
||||
|
||||
template(name="resultsPaged")
|
||||
if resultsHeading.get
|
||||
h1
|
||||
= resultsHeading.get
|
||||
a.fa.fa-link(title="{{_ 'link-to-search' }}" href="{{ getSearchHref }}")
|
||||
each card in results.get
|
||||
+resultCard(card)
|
||||
table.global-search-footer
|
||||
tr
|
||||
td.global-search-previous-page
|
||||
if hasPreviousPage.get
|
||||
button.js-previous-page
|
||||
| {{_ 'previous-page' }}
|
||||
td.global-search-next-page(align="right")
|
||||
if hasNextPage.get
|
||||
button.js-next-page
|
||||
| {{_ 'next-page' }}
|
||||
|
||||
template(name="globalSearch")
|
||||
if currentUser
|
||||
.wrapper
|
||||
form.global-search-page.js-search-query-form
|
||||
input.global-search-query-input(
|
||||
style="{# if hasResults.get #}display: inline-block;{#/if#}"
|
||||
id="global-search-input"
|
||||
type="text"
|
||||
name="searchQuery"
|
||||
@@ -22,31 +41,23 @@ template(name="globalSearch")
|
||||
value="{{ query.get }}"
|
||||
autofocus dir="auto"
|
||||
)
|
||||
a.js-new-search.fa.fa-eraser
|
||||
if searching.get
|
||||
+spinner
|
||||
else if hasResults.get
|
||||
.global-search-results-list-wrapper
|
||||
if hasQueryErrors.get
|
||||
div
|
||||
ul
|
||||
each msg in errorMessages
|
||||
span.global-search-error-messages
|
||||
li.global-search-error-messages
|
||||
= msg
|
||||
else
|
||||
h1
|
||||
= resultsHeading.get
|
||||
a.fa.fa-link(title="{{_ 'link-to-search' }}" href="{{ getSearchHref }}")
|
||||
each card in results.get
|
||||
+resultCard(card)
|
||||
table.global-search-footer
|
||||
tr
|
||||
td.global-search-previous-page
|
||||
if hasPreviousPage.get
|
||||
button.js-previous-page
|
||||
| {{_ 'previous-page' }}
|
||||
td.global-search-next-page(align="right")
|
||||
if hasNextPage.get
|
||||
button.js-next-page
|
||||
| {{_ 'next-page' }}
|
||||
+resultsPaged(this)
|
||||
else if serverError.get
|
||||
.global-search-page
|
||||
.global-search-help
|
||||
h1 {{_ 'server-error' }}
|
||||
+viewer
|
||||
| {{_ 'server-error-troubleshooting' }}
|
||||
else
|
||||
.global-search-page
|
||||
.global-search-help
|
||||
@@ -73,24 +84,3 @@ template(name="globalSearch")
|
||||
.global-search-instructions
|
||||
+viewer
|
||||
= searchInstructions
|
||||
|
||||
template(name="globalSearchViewChangePopup")
|
||||
if currentUser
|
||||
ul.pop-over-list
|
||||
li
|
||||
with "globalSearchViewChange-choice-me"
|
||||
a.js-global-search-view-me
|
||||
i.fa.fa-user.colorful
|
||||
| {{_ 'globalSearchViewChange-choice-me'}}
|
||||
if $eq Utils.globalSearchView "me"
|
||||
i.fa.fa-check
|
||||
li
|
||||
with "globalSearchViewChange-choice-all"
|
||||
a.js-global-search-view-all
|
||||
i.fa.fa-users.colorful
|
||||
| {{_ 'globalSearchViewChange-choice-all'}}
|
||||
span.sub-name
|
||||
+viewer
|
||||
| {{_ 'globalSearchViewChange-choice-all-description' }}
|
||||
if $eq Utils.globalSearchView "all"
|
||||
i.fa.fa-check
|
||||
@@ -1,4 +1,8 @@
|
||||
const subManager = new SubsManager();
|
||||
import { CardSearchPagedComponent } from '../../lib/cardSearch';
|
||||
import Boards from '../../../models/boards';
|
||||
import { Query, QueryErrors } from '../../../config/query-classes';
|
||||
|
||||
// const subManager = new SubsManager();
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
events() {
|
||||
@@ -16,45 +20,14 @@ Template.globalSearch.helpers({
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-due-cards-view-me'() {
|
||||
Utils.setDueCardsView('me');
|
||||
Popup.close();
|
||||
},
|
||||
|
||||
'click .js-due-cards-view-all'() {
|
||||
Utils.setDueCardsView('all');
|
||||
Popup.close();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('globalSearchViewChangePopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
class GlobalSearchComponent extends CardSearchPagedComponent {
|
||||
onCreated() {
|
||||
this.searching = new ReactiveVar(false);
|
||||
this.hasResults = new ReactiveVar(false);
|
||||
this.hasQueryErrors = new ReactiveVar(false);
|
||||
this.query = new ReactiveVar('');
|
||||
this.resultsHeading = new ReactiveVar('');
|
||||
this.searchLink = new ReactiveVar(null);
|
||||
super.onCreated();
|
||||
this.myLists = new ReactiveVar([]);
|
||||
this.myLabelNames = new ReactiveVar([]);
|
||||
this.myBoardNames = new ReactiveVar([]);
|
||||
this.results = new ReactiveVar([]);
|
||||
this.hasNextPage = new ReactiveVar(false);
|
||||
this.hasPreviousPage = new ReactiveVar(false);
|
||||
this.parsingErrors = new QueryErrors();
|
||||
this.queryParams = null;
|
||||
this.parsingErrors = [];
|
||||
this.resultsCount = 0;
|
||||
this.totalHits = 0;
|
||||
this.queryErrors = null;
|
||||
this.colorMap = null;
|
||||
this.resultsPerPage = 25;
|
||||
|
||||
Meteor.call('myLists', (err, data) => {
|
||||
if (!err) {
|
||||
@@ -73,510 +46,71 @@ BlazeComponent.extendComponent({
|
||||
this.myBoardNames.set(data);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
onRendered() {
|
||||
Meteor.subscribe('setting');
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
//console.log('lang:', TAPi18n.getLanguage());
|
||||
this.colorMap = Boards.colorMap();
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('colorMap:', this.colorMap);
|
||||
|
||||
if (Session.get('globalQuery')) {
|
||||
this.searchAllBoards(Session.get('globalQuery'));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
resetSearch() {
|
||||
this.searching.set(false);
|
||||
this.results.set([]);
|
||||
this.hasResults.set(false);
|
||||
this.hasQueryErrors.set(false);
|
||||
this.resultsHeading.set('');
|
||||
this.parsingErrors = [];
|
||||
this.resultsCount = 0;
|
||||
this.totalHits = 0;
|
||||
this.queryErrors = null;
|
||||
},
|
||||
|
||||
getSessionData() {
|
||||
return SessionData.findOne({
|
||||
userId: Meteor.userId(),
|
||||
sessionId: SessionData.getSessionId(),
|
||||
});
|
||||
},
|
||||
|
||||
getResults() {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('getting results');
|
||||
if (this.queryParams) {
|
||||
const sessionData = this.getSessionData();
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('selector:', sessionData.getSelector());
|
||||
// console.log('session data:', sessionData);
|
||||
const projection = sessionData.getProjection();
|
||||
projection.skip = 0;
|
||||
const cards = Cards.find({ _id: { $in: sessionData.cards } }, projection);
|
||||
this.queryErrors = sessionData.errors;
|
||||
if (this.queryErrors.length) {
|
||||
this.hasQueryErrors.set(true);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cards) {
|
||||
this.totalHits = sessionData.totalHits;
|
||||
this.resultsCount = cards.count();
|
||||
this.resultsStart = sessionData.lastHit - this.resultsCount + 1;
|
||||
this.resultsEnd = sessionData.lastHit;
|
||||
this.resultsHeading.set(this.getResultsHeading());
|
||||
this.results.set(cards);
|
||||
this.hasNextPage.set(sessionData.lastHit < sessionData.totalHits);
|
||||
this.hasPreviousPage.set(
|
||||
sessionData.lastHit - sessionData.resultsCount > 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
this.resultsCount = 0;
|
||||
return null;
|
||||
},
|
||||
super.resetSearch();
|
||||
this.parsingErrors = new QueryErrors();
|
||||
}
|
||||
|
||||
errorMessages() {
|
||||
if (this.parsingErrors.length) {
|
||||
return this.parsingErrorMessages();
|
||||
if (this.parsingErrors.hasErrors()) {
|
||||
return this.parsingErrors.errorMessages();
|
||||
}
|
||||
return this.queryErrorMessages();
|
||||
},
|
||||
}
|
||||
|
||||
parsingErrorMessages() {
|
||||
const messages = [];
|
||||
this.parsingErrors.errorMessages();
|
||||
}
|
||||
|
||||
if (this.parsingErrors.length) {
|
||||
this.parsingErrors.forEach(err => {
|
||||
messages.push(TAPi18n.__(err.tag, err.value));
|
||||
});
|
||||
}
|
||||
|
||||
return messages;
|
||||
},
|
||||
|
||||
queryErrorMessages() {
|
||||
messages = [];
|
||||
|
||||
this.queryErrors.forEach(err => {
|
||||
let value = err.color ? TAPi18n.__(`color-${err.value}`) : err.value;
|
||||
if (!value) {
|
||||
value = err.value;
|
||||
}
|
||||
messages.push(TAPi18n.__(err.tag, value));
|
||||
});
|
||||
|
||||
return messages;
|
||||
},
|
||||
|
||||
searchAllBoards(query) {
|
||||
query = query.trim();
|
||||
searchAllBoards(queryText) {
|
||||
queryText = queryText.trim();
|
||||
// eslint-disable-next-line no-console
|
||||
//console.log('query:', query);
|
||||
//console.log('queryText:', queryText);
|
||||
|
||||
this.query.set(query);
|
||||
this.query.set(queryText);
|
||||
|
||||
this.resetSearch();
|
||||
|
||||
if (!query) {
|
||||
if (!queryText) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.searching.set(true);
|
||||
|
||||
const reOperator1 = new RegExp(
|
||||
'^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<value>[\\p{Letter}\\p{Mark}]+)(\\s+|$)',
|
||||
'iu',
|
||||
);
|
||||
const reOperator2 = new RegExp(
|
||||
'^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<quote>["\']*)(?<value>.*?)\\k<quote>(\\s+|$)',
|
||||
'iu',
|
||||
);
|
||||
const reText = new RegExp('^(?<text>\\S+)(\\s+|$)', 'u');
|
||||
const reQuotedText = new RegExp(
|
||||
'^(?<quote>["\'])(?<text>.*?)\\k<quote>(\\s+|$)',
|
||||
'u',
|
||||
);
|
||||
const reNegatedOperator = new RegExp('^-(?<operator>.*)$');
|
||||
|
||||
const operators = {
|
||||
'operator-board': 'boards',
|
||||
'operator-board-abbrev': 'boards',
|
||||
'operator-swimlane': 'swimlanes',
|
||||
'operator-swimlane-abbrev': 'swimlanes',
|
||||
'operator-list': 'lists',
|
||||
'operator-list-abbrev': 'lists',
|
||||
'operator-label': 'labels',
|
||||
'operator-label-abbrev': 'labels',
|
||||
'operator-user': 'users',
|
||||
'operator-user-abbrev': 'users',
|
||||
'operator-member': 'members',
|
||||
'operator-member-abbrev': 'members',
|
||||
'operator-assignee': 'assignees',
|
||||
'operator-assignee-abbrev': 'assignees',
|
||||
'operator-status': 'status',
|
||||
'operator-due': 'dueAt',
|
||||
'operator-created': 'createdAt',
|
||||
'operator-modified': 'modifiedAt',
|
||||
'operator-comment': 'comments',
|
||||
'operator-has': 'has',
|
||||
'operator-sort': 'sort',
|
||||
'operator-limit': 'limit',
|
||||
};
|
||||
|
||||
const predicates = {
|
||||
due: {
|
||||
'predicate-overdue': 'overdue',
|
||||
},
|
||||
durations: {
|
||||
'predicate-week': 'week',
|
||||
'predicate-month': 'month',
|
||||
'predicate-quarter': 'quarter',
|
||||
'predicate-year': 'year',
|
||||
},
|
||||
status: {
|
||||
'predicate-archived': 'archived',
|
||||
'predicate-all': 'all',
|
||||
'predicate-open': 'open',
|
||||
'predicate-ended': 'ended',
|
||||
'predicate-public': 'public',
|
||||
'predicate-private': 'private',
|
||||
},
|
||||
sorts: {
|
||||
'predicate-due': 'dueAt',
|
||||
'predicate-created': 'createdAt',
|
||||
'predicate-modified': 'modifiedAt',
|
||||
},
|
||||
has: {
|
||||
'predicate-description': 'description',
|
||||
'predicate-checklist': 'checklist',
|
||||
'predicate-attachment': 'attachment',
|
||||
'predicate-start': 'startAt',
|
||||
'predicate-end': 'endAt',
|
||||
'predicate-due': 'dueAt',
|
||||
'predicate-assignee': 'assignees',
|
||||
'predicate-member': 'members',
|
||||
},
|
||||
};
|
||||
const predicateTranslations = {};
|
||||
Object.entries(predicates).forEach(([category, catPreds]) => {
|
||||
predicateTranslations[category] = {};
|
||||
Object.entries(catPreds).forEach(([tag, value]) => {
|
||||
predicateTranslations[category][TAPi18n.__(tag)] = value;
|
||||
});
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('predicateTranslations:', predicateTranslations);
|
||||
|
||||
const operatorMap = {};
|
||||
Object.entries(operators).forEach(([key, value]) => {
|
||||
operatorMap[TAPi18n.__(key).toLowerCase()] = value;
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('operatorMap:', operatorMap);
|
||||
|
||||
const params = {
|
||||
limit: this.resultsPerPage,
|
||||
boards: [],
|
||||
swimlanes: [],
|
||||
lists: [],
|
||||
users: [],
|
||||
members: [],
|
||||
assignees: [],
|
||||
labels: [],
|
||||
status: [],
|
||||
dueAt: null,
|
||||
createdAt: null,
|
||||
modifiedAt: null,
|
||||
comments: [],
|
||||
has: [],
|
||||
};
|
||||
|
||||
let text = '';
|
||||
while (query) {
|
||||
m = query.match(reOperator1);
|
||||
if (!m) {
|
||||
m = query.match(reOperator2);
|
||||
if (m) {
|
||||
query = query.replace(reOperator2, '');
|
||||
}
|
||||
} else {
|
||||
query = query.replace(reOperator1, '');
|
||||
}
|
||||
if (m) {
|
||||
let op;
|
||||
if (m.groups.operator) {
|
||||
op = m.groups.operator.toLowerCase();
|
||||
} else {
|
||||
op = m.groups.abbrev.toLowerCase();
|
||||
}
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (operatorMap.hasOwnProperty(op)) {
|
||||
const operator = operatorMap[op];
|
||||
let value = m.groups.value;
|
||||
if (operator === 'labels') {
|
||||
if (value in this.colorMap) {
|
||||
value = this.colorMap[value];
|
||||
// console.log('found color:', value);
|
||||
}
|
||||
} else if (['dueAt', 'createdAt', 'modifiedAt'].includes(operator)) {
|
||||
let days = parseInt(value, 10);
|
||||
let duration = null;
|
||||
if (isNaN(days)) {
|
||||
// duration was specified as text
|
||||
if (predicateTranslations.durations[value]) {
|
||||
duration = predicateTranslations.durations[value];
|
||||
let date = null;
|
||||
switch (duration) {
|
||||
case 'week':
|
||||
let week = moment().week();
|
||||
if (week === 52) {
|
||||
date = moment(1, 'W');
|
||||
date.set('year', date.year() + 1);
|
||||
} else {
|
||||
date = moment(week + 1, 'W');
|
||||
}
|
||||
break;
|
||||
case 'month':
|
||||
let month = moment().month();
|
||||
// .month() is zero indexed
|
||||
if (month === 11) {
|
||||
date = moment(1, 'M');
|
||||
date.set('year', date.year() + 1);
|
||||
} else {
|
||||
date = moment(month + 2, 'M');
|
||||
}
|
||||
break;
|
||||
case 'quarter':
|
||||
let quarter = moment().quarter();
|
||||
if (quarter === 4) {
|
||||
date = moment(1, 'Q');
|
||||
date.set('year', date.year() + 1);
|
||||
} else {
|
||||
date = moment(quarter + 1, 'Q');
|
||||
}
|
||||
break;
|
||||
case 'year':
|
||||
date = moment(moment().year() + 1, 'YYYY');
|
||||
break;
|
||||
}
|
||||
if (date) {
|
||||
value = {
|
||||
operator: '$lt',
|
||||
value: date.format('YYYY-MM-DD'),
|
||||
};
|
||||
}
|
||||
} else if (operator === 'dueAt' && value === 'overdue') {
|
||||
value = {
|
||||
operator: '$lt',
|
||||
value: moment().format('YYYY-MM-DD'),
|
||||
};
|
||||
} else {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-number-expected',
|
||||
value: { operator: op, value },
|
||||
});
|
||||
value = null;
|
||||
}
|
||||
} else {
|
||||
if (operator === 'dueAt') {
|
||||
value = {
|
||||
operator: '$lt',
|
||||
value: moment(moment().format('YYYY-MM-DD'))
|
||||
.add(days + 1, duration ? duration : 'days')
|
||||
.format(),
|
||||
};
|
||||
} else {
|
||||
value = {
|
||||
operator: '$gte',
|
||||
value: moment(moment().format('YYYY-MM-DD'))
|
||||
.subtract(days, duration ? duration : 'days')
|
||||
.format(),
|
||||
};
|
||||
}
|
||||
}
|
||||
} else if (operator === 'sort') {
|
||||
let negated = false;
|
||||
const m = value.match(reNegatedOperator);
|
||||
if (m) {
|
||||
value = m.groups.operator;
|
||||
negated = true;
|
||||
}
|
||||
if (!predicateTranslations.sorts[value]) {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-sort-invalid',
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
value = {
|
||||
name: predicateTranslations.sorts[value],
|
||||
order: negated ? 'des' : 'asc',
|
||||
};
|
||||
}
|
||||
} else if (operator === 'status') {
|
||||
if (!predicateTranslations.status[value]) {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-status-invalid',
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
value = predicateTranslations.status[value];
|
||||
}
|
||||
} else if (operator === 'has') {
|
||||
let negated = false;
|
||||
const m = value.match(reNegatedOperator);
|
||||
if (m) {
|
||||
value = m.groups.operator;
|
||||
negated = true;
|
||||
}
|
||||
if (!predicateTranslations.has[value]) {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-has-invalid',
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
value = {
|
||||
field: predicateTranslations.has[value],
|
||||
exists: !negated,
|
||||
};
|
||||
}
|
||||
} else if (operator === 'limit') {
|
||||
const limit = parseInt(value, 10);
|
||||
if (isNaN(limit) || limit < 1) {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-limit-invalid',
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
value = limit;
|
||||
}
|
||||
}
|
||||
if (Array.isArray(params[operator])) {
|
||||
params[operator].push(value);
|
||||
} else {
|
||||
params[operator] = value;
|
||||
}
|
||||
} else {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-unknown-error',
|
||||
value: op,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
m = query.match(reQuotedText);
|
||||
if (!m) {
|
||||
m = query.match(reText);
|
||||
if (m) {
|
||||
query = query.replace(reText, '');
|
||||
}
|
||||
} else {
|
||||
query = query.replace(reQuotedText, '');
|
||||
}
|
||||
if (m) {
|
||||
text += (text ? ' ' : '') + m.groups.text;
|
||||
}
|
||||
}
|
||||
const query = new Query();
|
||||
query.buildParams(queryText);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('text:', text);
|
||||
params.text = text;
|
||||
// console.log('params:', query.getParams());
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('params:', params);
|
||||
this.queryParams = query.getQueryParams().getParams();
|
||||
|
||||
this.queryParams = params;
|
||||
|
||||
if (this.parsingErrors.length) {
|
||||
if (query.hasErrors()) {
|
||||
this.searching.set(false);
|
||||
this.queryErrors = this.parsingErrorMessages();
|
||||
this.queryErrors = query.errors();
|
||||
this.hasResults.set(true);
|
||||
this.hasQueryErrors.set(true);
|
||||
return;
|
||||
}
|
||||
|
||||
this.autorun(() => {
|
||||
const handle = Meteor.subscribe(
|
||||
'globalSearch',
|
||||
SessionData.getSessionId(),
|
||||
params,
|
||||
);
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
if (handle.ready()) {
|
||||
this.getResults();
|
||||
this.searching.set(false);
|
||||
this.hasResults.set(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
nextPage() {
|
||||
const sessionData = this.getSessionData();
|
||||
|
||||
this.autorun(() => {
|
||||
const handle = Meteor.subscribe('nextPage', sessionData.sessionId);
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
if (handle.ready()) {
|
||||
this.getResults();
|
||||
this.searching.set(false);
|
||||
this.hasResults.set(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
previousPage() {
|
||||
const sessionData = this.getSessionData();
|
||||
|
||||
this.autorun(() => {
|
||||
const handle = Meteor.subscribe('previousPage', sessionData.sessionId);
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
if (handle.ready()) {
|
||||
this.getResults();
|
||||
this.searching.set(false);
|
||||
this.hasResults.set(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getResultsHeading() {
|
||||
if (this.resultsCount === 0) {
|
||||
return TAPi18n.__('no-cards-found');
|
||||
} else if (this.resultsCount === 1) {
|
||||
return TAPi18n.__('one-card-found');
|
||||
} else if (this.resultsCount === this.totalHits) {
|
||||
return TAPi18n.__('n-cards-found', this.resultsCount);
|
||||
}
|
||||
|
||||
return TAPi18n.__('n-n-of-n-cards-found', {
|
||||
start: this.resultsStart,
|
||||
end: this.resultsEnd,
|
||||
total: this.totalHits,
|
||||
});
|
||||
},
|
||||
|
||||
getSearchHref() {
|
||||
const baseUrl = window.location.href.replace(/([?#].*$|\s*$)/, '');
|
||||
return `${baseUrl}?q=${encodeURIComponent(this.query.get())}`;
|
||||
},
|
||||
this.runGlobalSearch(query.getQueryParams());
|
||||
}
|
||||
|
||||
searchInstructions() {
|
||||
tags = {
|
||||
const tags = {
|
||||
operator_board: TAPi18n.__('operator-board'),
|
||||
operator_list: TAPi18n.__('operator-list'),
|
||||
operator_swimlane: TAPi18n.__('operator-swimlane'),
|
||||
@@ -589,6 +123,7 @@ BlazeComponent.extendComponent({
|
||||
operator_member_abbrev: TAPi18n.__('operator-member-abbrev'),
|
||||
operator_assignee: TAPi18n.__('operator-assignee'),
|
||||
operator_assignee_abbrev: TAPi18n.__('operator-assignee-abbrev'),
|
||||
operator_creator: TAPi18n.__('operator-creator'),
|
||||
operator_due: TAPi18n.__('operator-due'),
|
||||
operator_created: TAPi18n.__('operator-created'),
|
||||
operator_modified: TAPi18n.__('operator-modified'),
|
||||
@@ -618,61 +153,47 @@ BlazeComponent.extendComponent({
|
||||
predicate_member: TAPi18n.__('predicate-member'),
|
||||
};
|
||||
|
||||
let text = `# ${TAPi18n.__('globalSearch-instructions-heading')}`;
|
||||
text += `\n${TAPi18n.__('globalSearch-instructions-description', tags)}`;
|
||||
text += `\n\n${TAPi18n.__('globalSearch-instructions-operators', tags)}`;
|
||||
|
||||
let text = '';
|
||||
[
|
||||
'globalSearch-instructions-operator-board',
|
||||
'globalSearch-instructions-operator-list',
|
||||
'globalSearch-instructions-operator-swimlane',
|
||||
'globalSearch-instructions-operator-comment',
|
||||
'globalSearch-instructions-operator-label',
|
||||
'globalSearch-instructions-operator-hash',
|
||||
'globalSearch-instructions-operator-user',
|
||||
'globalSearch-instructions-operator-at',
|
||||
'globalSearch-instructions-operator-member',
|
||||
'globalSearch-instructions-operator-assignee',
|
||||
'globalSearch-instructions-operator-due',
|
||||
'globalSearch-instructions-operator-created',
|
||||
'globalSearch-instructions-operator-modified',
|
||||
'globalSearch-instructions-operator-status',
|
||||
].forEach(instruction => {
|
||||
text += `\n* ${TAPi18n.__(instruction, tags)}`;
|
||||
});
|
||||
|
||||
[
|
||||
'globalSearch-instructions-status-archived',
|
||||
'globalSearch-instructions-status-public',
|
||||
'globalSearch-instructions-status-private',
|
||||
'globalSearch-instructions-status-all',
|
||||
'globalSearch-instructions-status-ended',
|
||||
].forEach(instruction => {
|
||||
text += `\n * ${TAPi18n.__(instruction, tags)}`;
|
||||
});
|
||||
|
||||
[
|
||||
'globalSearch-instructions-operator-has',
|
||||
'globalSearch-instructions-operator-sort',
|
||||
'globalSearch-instructions-operator-limit',
|
||||
].forEach(instruction => {
|
||||
text += `\n* ${TAPi18n.__(instruction, tags)}`;
|
||||
});
|
||||
|
||||
text += `\n## ${TAPi18n.__('heading-notes')}`;
|
||||
[
|
||||
'globalSearch-instructions-notes-1',
|
||||
'globalSearch-instructions-notes-2',
|
||||
'globalSearch-instructions-notes-3',
|
||||
'globalSearch-instructions-notes-3-2',
|
||||
'globalSearch-instructions-notes-4',
|
||||
'globalSearch-instructions-notes-5',
|
||||
].forEach(instruction => {
|
||||
text += `\n* ${TAPi18n.__(instruction, tags)}`;
|
||||
['# ', 'globalSearch-instructions-heading'],
|
||||
['\n', 'globalSearch-instructions-description'],
|
||||
['\n\n', 'globalSearch-instructions-operators'],
|
||||
['\n* ', 'globalSearch-instructions-operator-board'],
|
||||
['\n* ', 'globalSearch-instructions-operator-list'],
|
||||
['\n* ', 'globalSearch-instructions-operator-swimlane'],
|
||||
['\n* ', 'globalSearch-instructions-operator-comment'],
|
||||
['\n* ', 'globalSearch-instructions-operator-label'],
|
||||
['\n* ', 'globalSearch-instructions-operator-hash'],
|
||||
['\n* ', 'globalSearch-instructions-operator-user'],
|
||||
['\n* ', 'globalSearch-instructions-operator-at'],
|
||||
['\n* ', 'globalSearch-instructions-operator-member'],
|
||||
['\n* ', 'globalSearch-instructions-operator-assignee'],
|
||||
['\n* ', 'globalSearch-instructions-operator-creator'],
|
||||
['\n* ', 'globalSearch-instructions-operator-due'],
|
||||
['\n* ', 'globalSearch-instructions-operator-created'],
|
||||
['\n* ', 'globalSearch-instructions-operator-modified'],
|
||||
['\n* ', 'globalSearch-instructions-operator-status'],
|
||||
['\n * ', 'globalSearch-instructions-status-archived'],
|
||||
['\n * ', 'globalSearch-instructions-status-public'],
|
||||
['\n * ', 'globalSearch-instructions-status-private'],
|
||||
['\n * ', 'globalSearch-instructions-status-all'],
|
||||
['\n * ', 'globalSearch-instructions-status-ended'],
|
||||
['\n* ', 'globalSearch-instructions-operator-has'],
|
||||
['\n* ', 'globalSearch-instructions-operator-sort'],
|
||||
['\n* ', 'globalSearch-instructions-operator-limit'],
|
||||
['\n## ', 'heading-notes'],
|
||||
['\n* ', 'globalSearch-instructions-notes-1'],
|
||||
['\n* ', 'globalSearch-instructions-notes-2'],
|
||||
['\n* ', 'globalSearch-instructions-notes-3'],
|
||||
['\n* ', 'globalSearch-instructions-notes-3-2'],
|
||||
['\n* ', 'globalSearch-instructions-notes-4'],
|
||||
['\n* ', 'globalSearch-instructions-notes-5'],
|
||||
].forEach(([prefix, instruction]) => {
|
||||
text += `${prefix}${TAPi18n.__(instruction, tags)}`;
|
||||
});
|
||||
|
||||
return text;
|
||||
},
|
||||
}
|
||||
|
||||
labelColors() {
|
||||
return Boards.simpleSchema()._schema['labels.$.color'].allowedValues.map(
|
||||
@@ -680,23 +201,15 @@ BlazeComponent.extendComponent({
|
||||
return { color, name: TAPi18n.__(`color-${color}`) };
|
||||
},
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
events() {
|
||||
return [
|
||||
return super.events().concat([
|
||||
{
|
||||
'submit .js-search-query-form'(evt) {
|
||||
evt.preventDefault();
|
||||
this.searchAllBoards(evt.target.searchQuery.value);
|
||||
},
|
||||
'click .js-next-page'(evt) {
|
||||
evt.preventDefault();
|
||||
this.nextPage();
|
||||
},
|
||||
'click .js-previous-page'(evt) {
|
||||
evt.preventDefault();
|
||||
this.previousPage();
|
||||
},
|
||||
'click .js-label-color'(evt) {
|
||||
evt.preventDefault();
|
||||
const input = document.getElementById('global-search-input');
|
||||
@@ -737,7 +250,16 @@ BlazeComponent.extendComponent({
|
||||
);
|
||||
document.getElementById('global-search-input').focus();
|
||||
},
|
||||
'click .js-new-search'(evt) {
|
||||
evt.preventDefault();
|
||||
const input = document.getElementById('global-search-input');
|
||||
input.value = '';
|
||||
this.query.set('');
|
||||
this.hasResults.set(false);
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('globalSearch');
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
GlobalSearchComponent.register('globalSearch');
|
||||
@@ -77,8 +77,8 @@ Template.userFormsLayout.helpers({
|
||||
} else if (lang.name === 'ar-EG') {
|
||||
// ar-EG = Arabic (Egypt), simply Masri (مَصرى, [ˈmɑsˤɾi], Egyptian, Masr refers to Cairo)
|
||||
name = 'مَصرى';
|
||||
} else if (lang.name === 'es-PY') {
|
||||
name = 'Español de Paraguayo';
|
||||
} else if (lang.name === 'de-CH') {
|
||||
name = 'Deutsch (Schweiz)';
|
||||
} else if (lang.name === 'fa-IR') {
|
||||
// fa-IR = Persian (Iran)
|
||||
name = 'فارسی/پارسی (ایران)';
|
||||
@@ -94,6 +94,14 @@ Template.userFormsLayout.helpers({
|
||||
name = 'Latviešu';
|
||||
} else if (lang.name === 'Español') {
|
||||
name = 'español';
|
||||
} else if (lang.name === 'es_419') {
|
||||
name = 'español de América Latina';
|
||||
} else if (lang.name === 'es-419') {
|
||||
name = 'español de América Latina';
|
||||
} else if (lang.name === 'Español de América Latina') {
|
||||
name = 'español de América Latina';
|
||||
} else if (lang.name === 'es-LA') {
|
||||
name = 'español de América Latina';
|
||||
} else if (lang.name === 'Español de Argentina') {
|
||||
name = 'español de Argentina';
|
||||
} else if (lang.name === 'Español de Chile') {
|
||||
@@ -104,6 +112,8 @@ Template.userFormsLayout.helpers({
|
||||
name = 'español de México';
|
||||
} else if (lang.name === 'es-PY') {
|
||||
name = 'español de Paraguayo';
|
||||
} else if (lang.name === 'Español de Paraguayo') {
|
||||
name = 'español de Paraguayo';
|
||||
} else if (lang.name === 'Español de Perú') {
|
||||
name = 'español de Perú';
|
||||
} else if (lang.name === 'Español de Puerto Rico') {
|
||||
|
||||
@@ -6,16 +6,6 @@ template(name="myCardsHeaderBar")
|
||||
i.fa.fa-list
|
||||
| {{_ 'my-cards'}}
|
||||
|
||||
.board-header-btns.left
|
||||
a.board-header-btn.js-toggle-my-cards-choose-sort(title="{{_ 'myCardsSortChange-title'}}")
|
||||
i.fa.fa-caret-down
|
||||
if $eq myCardsSort 'board'
|
||||
i.fa.fa-th-large
|
||||
| {{_ 'myCardsSortChange-choice-board'}}
|
||||
if $eq myCardsSort 'dueAt'
|
||||
i.fa.fa-calendar
|
||||
| {{_ 'myCardsSortChange-choice-dueat'}}
|
||||
|
||||
template(name="myCardsModalTitle")
|
||||
if currentUser
|
||||
h2
|
||||
@@ -24,50 +14,26 @@ template(name="myCardsModalTitle")
|
||||
|
||||
template(name="myCards")
|
||||
if currentUser
|
||||
if isPageReady.get
|
||||
.wrapper
|
||||
if $eq myCardsSort 'board'
|
||||
each board in myCardsList
|
||||
.my-cards-board-wrapper
|
||||
.my-cards-board-title(class=board.colorClass, id="header")
|
||||
a(href=board.originRelativeUrl)
|
||||
+viewer
|
||||
= board.title
|
||||
each swimlane in board.mySwimlanes
|
||||
.my-cards-swimlane-title(class="{{#if swimlane.colorClass}}{{ swimlane.colorClass }}{{else}}swimlane-default-color{{/if}}")
|
||||
+viewer
|
||||
= swimlane.title
|
||||
each list in swimlane.myLists
|
||||
.my-cards-list-wrapper
|
||||
.my-cards-list-title(class=list.colorClass)
|
||||
+viewer
|
||||
= list.title
|
||||
each card in list.myCards
|
||||
.my-cards-card-wrapper
|
||||
a.minicard-wrapper(href=card.originRelativeUrl)
|
||||
+minicard(card)
|
||||
else
|
||||
.my-cards-dueat-list-wrapper
|
||||
each card in myDueCardsList
|
||||
+resultCard(card)
|
||||
else
|
||||
if searching.get
|
||||
+spinner
|
||||
|
||||
template(name="myCardsSortChangePopup")
|
||||
if currentUser
|
||||
ul.pop-over-list
|
||||
li
|
||||
with "my-cards-sort-board"
|
||||
a.js-my-cards-sort-board
|
||||
i.fa.fa-th-large.colorful
|
||||
| {{_ 'myCardsSortChange-choice-board'}}
|
||||
if $eq Utils.myCardsSort "board"
|
||||
i.fa.fa-check
|
||||
hr
|
||||
li
|
||||
with "my-cards-sort-dueat"
|
||||
a.js-my-cards-sort-dueat
|
||||
i.fa.fa-calendar.colorful
|
||||
| {{_ 'myCardsSortChange-choice-dueat'}}
|
||||
if $eq Utils.myCardsSort "dueAt"
|
||||
i.fa.fa-check
|
||||
else
|
||||
.wrapper
|
||||
each board in myCardsList
|
||||
.my-cards-board-wrapper
|
||||
.my-cards-board-title(class=board.colorClass, id="header")
|
||||
a(href=board.originRelativeUrl)
|
||||
+viewer
|
||||
= board.title
|
||||
each swimlane in board.mySwimlanes
|
||||
.my-cards-swimlane-title(class="{{#if swimlane.colorClass}}{{ swimlane.colorClass }}{{else}}swimlane-default-color{{/if}}")
|
||||
+viewer
|
||||
= swimlane.title
|
||||
each list in swimlane.myLists
|
||||
.my-cards-list-wrapper
|
||||
.my-cards-list-title(class=list.colorClass)
|
||||
+viewer
|
||||
= list.title
|
||||
each card in list.myCards
|
||||
.my-cards-card-wrapper
|
||||
a.minicard-wrapper(href=card.originRelativeUrl)
|
||||
+minicard(card)
|
||||
@@ -1,4 +1,4 @@
|
||||
const subManager = new SubsManager();
|
||||
import { CardSearchPagedComponent } from '../../lib/cardSearch';
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
myCardsSort() {
|
||||
@@ -24,227 +24,115 @@ Template.myCards.helpers({
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-my-cards-sort-board'() {
|
||||
Utils.setMyCardsSort('board');
|
||||
Popup.close();
|
||||
},
|
||||
|
||||
'click .js-my-cards-sort-dueat'() {
|
||||
Utils.setMyCardsSort('dueAt');
|
||||
Popup.close();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('myCardsSortChangePopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
class MyCardsComponent extends CardSearchPagedComponent {
|
||||
onCreated() {
|
||||
this.isPageReady = new ReactiveVar(false);
|
||||
super.onCreated();
|
||||
|
||||
this.autorun(() => {
|
||||
const handle = subManager.subscribe('myCards');
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
this.isPageReady.set(handle.ready());
|
||||
});
|
||||
});
|
||||
});
|
||||
this.runGlobalSearch(null);
|
||||
Meteor.subscribe('setting');
|
||||
},
|
||||
}
|
||||
|
||||
myCardsSort() {
|
||||
// eslint-disable-next-line no-console
|
||||
//console.log('sort:', Utils.myCardsSort());
|
||||
return Utils.myCardsSort();
|
||||
},
|
||||
|
||||
sortByBoard() {
|
||||
return this.myCardsSort() === 'board';
|
||||
},
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getSubscription(queryParams) {
|
||||
return Meteor.subscribe(
|
||||
'myCards',
|
||||
this.sessionId,
|
||||
this.subscriptionCallbacks,
|
||||
);
|
||||
}
|
||||
|
||||
myCardsList() {
|
||||
const userId = Meteor.userId();
|
||||
const boards = [];
|
||||
let board = null;
|
||||
let swimlane = null;
|
||||
let list = null;
|
||||
|
||||
const cursor = Cards.find(
|
||||
{
|
||||
$or: [{ members: userId }, { assignees: userId }],
|
||||
archived: false,
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
},
|
||||
},
|
||||
);
|
||||
const cursor = this.getResults();
|
||||
|
||||
let newBoard = false;
|
||||
let newSwimlane = false;
|
||||
let newList = false;
|
||||
|
||||
cursor.forEach(card => {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('card:', card.title);
|
||||
if (list === null || card.listId !== list._id) {
|
||||
if (cursor) {
|
||||
cursor.forEach(card => {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('new list');
|
||||
list = card.getList();
|
||||
if (list.archived) {
|
||||
list = null;
|
||||
return;
|
||||
}
|
||||
list.myCards = [card];
|
||||
newList = true;
|
||||
}
|
||||
if (swimlane === null || card.swimlaneId !== swimlane._id) {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('new swimlane');
|
||||
swimlane = card.getSwimlane();
|
||||
if (swimlane.archived) {
|
||||
// console.log('card:', card.title);
|
||||
if (board === null || card.boardId !== board._id) {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('new board');
|
||||
board = card.getBoard();
|
||||
if (board.archived) {
|
||||
board = null;
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('board:', b, b._id, b.title);
|
||||
boards.push(board);
|
||||
board.mySwimlanes = [];
|
||||
swimlane = null;
|
||||
return;
|
||||
list = null;
|
||||
}
|
||||
|
||||
if (swimlane === null || card.swimlaneId !== swimlane._id) {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('new swimlane');
|
||||
swimlane = card.getSwimlane();
|
||||
if (swimlane.archived) {
|
||||
swimlane = null;
|
||||
return;
|
||||
}
|
||||
board.mySwimlanes.push(swimlane);
|
||||
swimlane.myLists = [];
|
||||
list = null;
|
||||
}
|
||||
|
||||
if (list === null || card.listId !== list._id) {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('new list');
|
||||
list = card.getList();
|
||||
if (list.archived) {
|
||||
list = null;
|
||||
return;
|
||||
}
|
||||
swimlane.myLists.push(list);
|
||||
list.myCards = [];
|
||||
}
|
||||
swimlane.myLists = [list];
|
||||
newSwimlane = true;
|
||||
}
|
||||
if (board === null || card.boardId !== board._id) {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('new board');
|
||||
board = card.getBoard();
|
||||
if (board.archived) {
|
||||
board = null;
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('board:', b, b._id, b.title);
|
||||
board.mySwimlanes = [swimlane];
|
||||
newBoard = true;
|
||||
}
|
||||
|
||||
if (newBoard) {
|
||||
boards.push(board);
|
||||
} else if (newSwimlane) {
|
||||
board.mySwimlanes.push(swimlane);
|
||||
} else if (newList) {
|
||||
swimlane.myLists.push(list);
|
||||
} else {
|
||||
list.myCards.push(card);
|
||||
}
|
||||
});
|
||||
|
||||
newBoard = false;
|
||||
newSwimlane = false;
|
||||
newList = false;
|
||||
});
|
||||
|
||||
// sort the data structure
|
||||
boards.forEach(board => {
|
||||
board.mySwimlanes.forEach(swimlane => {
|
||||
swimlane.myLists.forEach(list => {
|
||||
list.myCards.sort((a, b) => {
|
||||
// sort the data structure
|
||||
boards.forEach(board => {
|
||||
board.mySwimlanes.forEach(swimlane => {
|
||||
swimlane.myLists.forEach(list => {
|
||||
list.myCards.sort((a, b) => {
|
||||
return a.sort - b.sort;
|
||||
});
|
||||
});
|
||||
swimlane.myLists.sort((a, b) => {
|
||||
return a.sort - b.sort;
|
||||
});
|
||||
});
|
||||
swimlane.myLists.sort((a, b) => {
|
||||
board.mySwimlanes.sort((a, b) => {
|
||||
return a.sort - b.sort;
|
||||
});
|
||||
});
|
||||
board.mySwimlanes.sort((a, b) => {
|
||||
return a.sort - b.sort;
|
||||
|
||||
boards.sort((a, b) => {
|
||||
let x = a.sort;
|
||||
let y = b.sort;
|
||||
|
||||
// show the template board last
|
||||
if (a.type === 'template-container') {
|
||||
x = 99999999;
|
||||
} else if (b.type === 'template-container') {
|
||||
y = 99999999;
|
||||
}
|
||||
return x - y;
|
||||
});
|
||||
});
|
||||
|
||||
boards.sort((a, b) => {
|
||||
let x = a.sort;
|
||||
let y = b.sort;
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('boards:', boards);
|
||||
return boards;
|
||||
}
|
||||
|
||||
// show the template board last
|
||||
if (a.type === 'template-container') {
|
||||
x = 99999999;
|
||||
} else if (b.type === 'template-container') {
|
||||
y = 99999999;
|
||||
}
|
||||
return x - y;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('boards:', boards);
|
||||
return boards;
|
||||
},
|
||||
|
||||
myDueCardsList() {
|
||||
const userId = Meteor.userId();
|
||||
|
||||
const cursor = Cards.find(
|
||||
{
|
||||
$or: [{ members: userId }, { assignees: userId }],
|
||||
archived: false,
|
||||
},
|
||||
{
|
||||
sort: {
|
||||
dueAt: -1,
|
||||
boardId: 1,
|
||||
swimlaneId: 1,
|
||||
listId: 1,
|
||||
sort: 1,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('cursor:', cursor);
|
||||
|
||||
const cards = [];
|
||||
cursor.forEach(card => {
|
||||
if (
|
||||
!card.getBoard().archived &&
|
||||
!card.getSwimlane().archived &&
|
||||
!card.getList().archived
|
||||
) {
|
||||
cards.push(card);
|
||||
}
|
||||
});
|
||||
|
||||
cards.sort((a, b) => {
|
||||
const x = a.dueAt === null ? Date('2100-12-31') : a.dueAt;
|
||||
const y = b.dueAt === null ? Date('2100-12-31') : b.dueAt;
|
||||
|
||||
if (x > y) return 1;
|
||||
else if (x < y) return -1;
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('cursor:', cards);
|
||||
return cards;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
// 'click .js-my-card'(evt) {
|
||||
// const card = this.currentData().card;
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log('currentData():', this.currentData());
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log('card:', card);
|
||||
// if (card) {
|
||||
// Utils.goCardId(card._id);
|
||||
// }
|
||||
// evt.preventDefault();
|
||||
// },
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('myCards');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
MyCardsComponent.register('myCards');
|
||||
@@ -0,0 +1,111 @@
|
||||
template(name="adminReports")
|
||||
.setting-content
|
||||
unless currentUser.isAdmin
|
||||
| {{_ 'error-notAuthorized'}}
|
||||
else
|
||||
.content-body
|
||||
.side-menu
|
||||
ul
|
||||
li
|
||||
a.js-report-broken(data-id="report-broken")
|
||||
i.fa.fa-chain-broken
|
||||
| {{_ 'broken-cards'}}
|
||||
|
||||
li
|
||||
a.js-report-files(data-id="report-orphaned-files")
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'orphanedFilesReportTitle'}}
|
||||
|
||||
li
|
||||
a.js-report-files(data-id="report-files")
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'filesReportTitle'}}
|
||||
|
||||
li
|
||||
a.js-report-rules(data-id="report-rules")
|
||||
i.fa.fa-magic
|
||||
| {{_ 'rulesReportTitle'}}
|
||||
|
||||
.main-body
|
||||
if loading.get
|
||||
+spinner
|
||||
else if showBrokenCardsReport.get
|
||||
+brokenCardsReport
|
||||
else if showFilesReport.get
|
||||
+filesReport
|
||||
else if showOrphanedFilesReport.get
|
||||
+orphanedFilesReport
|
||||
else if showRulesReport.get
|
||||
+rulesReport
|
||||
|
||||
|
||||
template(name="brokenCardsReport")
|
||||
.global-search-results-list-wrapper
|
||||
h1 {{_ 'broken-cards'}}
|
||||
if resultsCount
|
||||
+resultsPaged(this)
|
||||
else
|
||||
div {{_ 'no-results' }}
|
||||
|
||||
template(name="rulesReport")
|
||||
h1 {{_ 'rulesReportTitle'}}
|
||||
if resultsCount
|
||||
table.table
|
||||
tr
|
||||
th Rule Title
|
||||
th Board Title
|
||||
th actionType
|
||||
th activityType
|
||||
|
||||
each rule in rows
|
||||
tr
|
||||
td {{ rule.title }}
|
||||
td {{ rule.boardTitle }}
|
||||
td {{ rule.action.actionType }}
|
||||
td {{ rule.trigger.activityType }}
|
||||
else
|
||||
div {{_ 'no-results' }}
|
||||
|
||||
template(name="filesReport")
|
||||
h1 {{_ 'filesReportTitle'}}
|
||||
if resultsCount
|
||||
table.table
|
||||
tr
|
||||
th Filename
|
||||
th.right Size (kB)
|
||||
th MIME Type
|
||||
th.center Usage
|
||||
th MD5 Sum
|
||||
th ID
|
||||
|
||||
each att in attachmentFiles
|
||||
tr
|
||||
td {{ att.filename }}
|
||||
td.right {{fileSize att.length }}
|
||||
td {{ att.contentType }}
|
||||
td.center {{usageCount att._id.toHexString }}
|
||||
td {{ att.md5 }}
|
||||
td {{ att._id.toHexString }}
|
||||
else
|
||||
div {{_ 'no-results' }}
|
||||
|
||||
template(name="orphanedFilesReport")
|
||||
h1 {{_ 'orphanedFilesReportTitle'}}
|
||||
if resultsCount
|
||||
table.table
|
||||
tr
|
||||
th Filename
|
||||
th.right Size (kB)
|
||||
th MIME Type
|
||||
th MD5 Sum
|
||||
th ID
|
||||
|
||||
each att in attachmentFiles
|
||||
tr
|
||||
td {{ att.filename }}
|
||||
td.right {{fileSize att.length }}
|
||||
td {{ att.contentType }}
|
||||
td {{ att.md5 }}
|
||||
td {{ att._id.toHexString }}
|
||||
else
|
||||
div {{_ 'no-results' }}
|
||||
@@ -0,0 +1,156 @@
|
||||
import { AttachmentStorage } from '/models/attachments';
|
||||
import { CardSearchPagedComponent } from '/client/lib/cardSearch';
|
||||
import SessionData from '/models/usersessiondata';
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
subscription: null,
|
||||
showFilesReport: new ReactiveVar(false),
|
||||
showBrokenCardsReport: new ReactiveVar(false),
|
||||
showOrphanedFilesReport: new ReactiveVar(false),
|
||||
showRulesReport: new ReactiveVar(false),
|
||||
|
||||
onCreated() {
|
||||
this.error = new ReactiveVar('');
|
||||
this.loading = new ReactiveVar(false);
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click a.js-report-broken': this.switchMenu,
|
||||
'click a.js-report-files': this.switchMenu,
|
||||
'click a.js-report-orphaned-files': this.switchMenu,
|
||||
'click a.js-report-rules': this.switchMenu,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
switchMenu(event) {
|
||||
const target = $(event.target);
|
||||
if (!target.hasClass('active')) {
|
||||
this.loading.set(true);
|
||||
this.showFilesReport.set(false);
|
||||
this.showBrokenCardsReport.set(false);
|
||||
this.showOrphanedFilesReport.set(false);
|
||||
if (this.subscription) {
|
||||
this.subscription.stop();
|
||||
}
|
||||
|
||||
$('.side-menu li.active').removeClass('active');
|
||||
target.parent().addClass('active');
|
||||
const targetID = target.data('id');
|
||||
|
||||
if ('report-broken' === targetID) {
|
||||
this.showBrokenCardsReport.set(true);
|
||||
this.subscription = Meteor.subscribe(
|
||||
'brokenCards',
|
||||
SessionData.getSessionId(),
|
||||
() => {
|
||||
this.loading.set(false);
|
||||
},
|
||||
);
|
||||
} else if ('report-files' === targetID) {
|
||||
this.showFilesReport.set(true);
|
||||
this.subscription = Meteor.subscribe('attachmentsList', () => {
|
||||
this.loading.set(false);
|
||||
});
|
||||
} else if ('report-orphaned-files' === targetID) {
|
||||
this.showOrphanedFilesReport.set(true);
|
||||
this.subscription = Meteor.subscribe('orphanedAttachments', () => {
|
||||
this.loading.set(false);
|
||||
});
|
||||
} else if ('report-rules' === targetID) {
|
||||
this.subscription = Meteor.subscribe('rulesReport', () => {
|
||||
this.showRulesReport.set(true);
|
||||
this.loading.set(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}).register('adminReports');
|
||||
|
||||
Template.filesReport.helpers({
|
||||
attachmentFiles() {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('attachments:', AttachmentStorage.find());
|
||||
// console.log('attachments.count:', AttachmentStorage.find().count());
|
||||
return AttachmentStorage.find();
|
||||
},
|
||||
|
||||
rulesReport() {
|
||||
const rules = [];
|
||||
|
||||
Rules.find().forEach(rule => {
|
||||
rules.push({
|
||||
_id: rule._id,
|
||||
title: rule.title,
|
||||
boardId: rule.boardId,
|
||||
boardTitle: rule.board().title,
|
||||
action: rule.action().fetch(),
|
||||
trigger: rule.trigger().fetch(),
|
||||
});
|
||||
});
|
||||
|
||||
return rules;
|
||||
},
|
||||
|
||||
resultsCount() {
|
||||
return AttachmentStorage.find().count();
|
||||
},
|
||||
|
||||
fileSize(size) {
|
||||
return Math.round(size / 1024);
|
||||
},
|
||||
|
||||
usageCount(key) {
|
||||
return Attachments.find({ 'copies.attachments.key': key }).count();
|
||||
},
|
||||
});
|
||||
|
||||
Template.orphanedFilesReport.helpers({
|
||||
attachmentFiles() {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('attachments:', AttachmentStorage.find());
|
||||
// console.log('attachments.count:', AttachmentStorage.find().count());
|
||||
return AttachmentStorage.find();
|
||||
},
|
||||
|
||||
resultsCount() {
|
||||
return AttachmentStorage.find().count();
|
||||
},
|
||||
|
||||
fileSize(size) {
|
||||
return Math.round(size / 1024);
|
||||
},
|
||||
});
|
||||
|
||||
Template.rulesReport.helpers({
|
||||
rows() {
|
||||
const rules = [];
|
||||
|
||||
Rules.find().forEach(rule => {
|
||||
rules.push({
|
||||
_id: rule._id,
|
||||
title: rule.title,
|
||||
boardId: rule.boardId,
|
||||
boardTitle: rule.board().title,
|
||||
action: rule.action(),
|
||||
trigger: rule.trigger(),
|
||||
});
|
||||
});
|
||||
|
||||
console.log('rows:', rules);
|
||||
return rules;
|
||||
},
|
||||
|
||||
resultsCount() {
|
||||
return Rules.find().count();
|
||||
},
|
||||
});
|
||||
|
||||
class BrokenCardsComponent extends CardSearchPagedComponent {
|
||||
onCreated() {
|
||||
super.onCreated();
|
||||
}
|
||||
}
|
||||
BrokenCardsComponent.register('brokenCardsReport');
|
||||
@@ -117,17 +117,17 @@ template(name="peopleGeneral")
|
||||
|
||||
template(name="newOrgRow")
|
||||
a.new-org
|
||||
i.fa.fa-edit
|
||||
i.fa.fa-plus-square
|
||||
| {{_ 'new'}}
|
||||
|
||||
template(name="newTeamRow")
|
||||
a.new-team
|
||||
i.fa.fa-edit
|
||||
i.fa.fa-plus-square
|
||||
| {{_ 'new'}}
|
||||
|
||||
template(name="newUserRow")
|
||||
a.new-user
|
||||
i.fa.fa-edit
|
||||
i.fa.fa-plus-square
|
||||
| {{_ 'new'}}
|
||||
|
||||
template(name="orgRow")
|
||||
|
||||
@@ -122,6 +122,8 @@ template(name='email')
|
||||
|
||||
template(name='accountSettings')
|
||||
ul#account-setting.setting-detail
|
||||
li
|
||||
button.js-all-hide-system-messages.primary {{_ 'hide-system-messages-of-all-users'}}
|
||||
li.accounts-form
|
||||
.title {{_ 'accounts-allowEmailChange'}}
|
||||
.form-group.flex
|
||||
@@ -129,23 +131,18 @@ template(name='accountSettings')
|
||||
span {{_ 'yes'}}
|
||||
input.wekan-form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="false" checked="{{#unless allowEmailChange}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
li
|
||||
li.accounts-form
|
||||
.title {{_ 'accounts-allowUserNameChange'}}
|
||||
.form-group.flex
|
||||
input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="true" checked="{{#if allowUserNameChange}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="false" checked="{{#unless allowUserNameChange}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
li
|
||||
li.accounts-form
|
||||
.title {{_ 'accounts-allowUserDelete'}}
|
||||
.form-group.flex
|
||||
input.wekan-form-control#accounts-allowUserDelete(type="radio" name="allowUserDelete" value="true" checked="{{#if allowUserDelete}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
input.wekan-form-control#accounts-allowUserDelete(type="radio" name="allowUserDelete" value="false" checked="{{#unless allowUserDelete}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
li
|
||||
button.js-accounts-save.primary {{_ 'save'}}
|
||||
|
||||
template(name='announcementSettings')
|
||||
|
||||
@@ -274,7 +274,6 @@ BlazeComponent.extendComponent({
|
||||
$set: { booleanValue: allowUserDelete },
|
||||
});
|
||||
},
|
||||
|
||||
allowEmailChange() {
|
||||
return AccountSettings.findOne('accounts-allowEmailChange').booleanValue;
|
||||
},
|
||||
@@ -284,12 +283,31 @@ BlazeComponent.extendComponent({
|
||||
allowUserDelete() {
|
||||
return AccountSettings.findOne('accounts-allowUserDelete').booleanValue;
|
||||
},
|
||||
allHideSystemMessages() {
|
||||
Meteor.call('setAllUsersHideSystemMessages', (err, ret) => {
|
||||
if (!err && ret) {
|
||||
if (ret === true) {
|
||||
const message = `${TAPi18n.__(
|
||||
'now-system-messages-of-all-users-are-hidden',
|
||||
)}`;
|
||||
alert(message);
|
||||
}
|
||||
} else {
|
||||
const reason = err.reason || '';
|
||||
const message = `${TAPi18n.__(err.error)}\n${reason}`;
|
||||
alert(message);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click button.js-accounts-save': this.saveAccountsChange,
|
||||
},
|
||||
{
|
||||
'click button.js-all-hide-system-messages': this.allHideSystemMessages,
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('accountSettings');
|
||||
|
||||
@@ -12,6 +12,10 @@ template(name="settingHeaderBar")
|
||||
i.fa(class="fa-users")
|
||||
span {{_ 'people'}}
|
||||
|
||||
a.setting-header-btn.informations(href="{{pathFor 'admin-reports'}}")
|
||||
i.fa(class="fa-list")
|
||||
span {{_ 'reports'}}
|
||||
|
||||
a.setting-header-btn.informations(href="{{pathFor 'information'}}")
|
||||
i.fa(class="fa-info-circle")
|
||||
span {{_ 'info'}}
|
||||
|
||||
@@ -105,6 +105,14 @@ template(name="boardCardSettingsPopup")
|
||||
span
|
||||
i.fa.fa-users
|
||||
| {{_ 'members'}}
|
||||
|
||||
div.check-div
|
||||
a.flex.js-field-has-creator(class="{{#if allowsCreator}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsCreator}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-user
|
||||
| {{_ 'creator'}}
|
||||
|
||||
div.check-div
|
||||
a.flex.js-field-has-assignee(class="{{#if allowsAssignee}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsAssignee}}is-checked{{/if}}")
|
||||
|
||||
@@ -730,6 +730,14 @@ BlazeComponent.extendComponent({
|
||||
return this.currentBoard.allowsSubtasks;
|
||||
},
|
||||
|
||||
allowsCreator() {
|
||||
return (
|
||||
this.currentBoard.allowsCreator === null ||
|
||||
this.currentBoard.allowsCreator === undefined ||
|
||||
this.currentBoard.allowsCreator
|
||||
);
|
||||
},
|
||||
|
||||
allowsMembers() {
|
||||
return this.currentBoard.allowsMembers;
|
||||
},
|
||||
@@ -889,6 +897,19 @@ BlazeComponent.extendComponent({
|
||||
this.currentBoard.allowsSubtasks,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-creator'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsCreator = !this.currentBoard.allowsCreator;
|
||||
this.currentBoard.setAllowsCreator(this.currentBoard.allowsCreator);
|
||||
$(`.js-field-has-creator ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsCreator,
|
||||
);
|
||||
$('.js-field-has-creator').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsCreator,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-members'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsMembers = !this.currentBoard.allowsMembers;
|
||||
|
||||
@@ -50,6 +50,15 @@ template(name="createCustomFieldPopup")
|
||||
each dropdownItems.get
|
||||
input.js-dropdown-item(type="text" value=name placeholder="")
|
||||
input.js-dropdown-item.last(type="text" value="" placeholder="{{_ 'custom-field-dropdown-options-placeholder'}}")
|
||||
|
||||
div.js-field-settings.js-field-settings-stringtemplate(class="{{#if isTypeNotSelected 'stringtemplate'}}hide{{/if}}")
|
||||
label
|
||||
| {{_ 'custom-field-stringtemplate-format'}}
|
||||
input.js-field-stringtemplate-format(type="text" value=getStringtemplateFormat)
|
||||
label
|
||||
| {{_ 'custom-field-stringtemplate-separator'}}
|
||||
input.js-field-stringtemplate-separator(type="text" value=getStringtemplateSeparator)
|
||||
|
||||
a.flex.js-field-show-on-card(class="{{#if showOnCard}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if showOnCard}}is-checked{{/if}}")
|
||||
|
||||
|
||||
@@ -16,7 +16,15 @@ BlazeComponent.extendComponent({
|
||||
}).register('customFieldsSidebar');
|
||||
|
||||
const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
_types: ['text', 'number', 'date', 'dropdown', 'currency', 'checkbox'],
|
||||
_types: [
|
||||
'text',
|
||||
'number',
|
||||
'date',
|
||||
'dropdown',
|
||||
'currency',
|
||||
'checkbox',
|
||||
'stringtemplate',
|
||||
],
|
||||
|
||||
_currencyList: [
|
||||
{
|
||||
@@ -77,6 +85,18 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
? this.data().settings.dropdownItems
|
||||
: [],
|
||||
);
|
||||
|
||||
this.stringtemplateFormat = new ReactiveVar(
|
||||
this.data().settings && this.data().settings.stringtemplateFormat
|
||||
? this.data().settings.stringtemplateFormat
|
||||
: '',
|
||||
);
|
||||
|
||||
this.stringtemplateSeparator = new ReactiveVar(
|
||||
this.data().settings && this.data().settings.stringtemplateSeparator
|
||||
? this.data().settings.stringtemplateSeparator
|
||||
: '',
|
||||
);
|
||||
},
|
||||
|
||||
types() {
|
||||
@@ -121,6 +141,14 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
return items;
|
||||
},
|
||||
|
||||
getStringtemplateFormat() {
|
||||
return this.stringtemplateFormat.get();
|
||||
},
|
||||
|
||||
getStringtemplateSeparator() {
|
||||
return this.stringtemplateSeparator.get();
|
||||
},
|
||||
|
||||
getSettings() {
|
||||
const settings = {};
|
||||
switch (this.type.get()) {
|
||||
@@ -136,6 +164,14 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
settings.dropdownItems = dropdownItems;
|
||||
break;
|
||||
}
|
||||
case 'stringtemplate': {
|
||||
const stringtemplateFormat = this.stringtemplateFormat.get();
|
||||
settings.stringtemplateFormat = stringtemplateFormat;
|
||||
|
||||
const stringtemplateSeparator = this.stringtemplateSeparator.get();
|
||||
settings.stringtemplateSeparator = stringtemplateSeparator;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
},
|
||||
@@ -158,6 +194,14 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
|
||||
evt.target.value = '';
|
||||
}
|
||||
},
|
||||
'input .js-field-stringtemplate-format'(evt) {
|
||||
const value = evt.target.value;
|
||||
this.stringtemplateFormat.set(value);
|
||||
},
|
||||
'input .js-field-stringtemplate-separator'(evt) {
|
||||
const value = evt.target.value;
|
||||
this.stringtemplateSeparator.set(value);
|
||||
},
|
||||
'click .js-field-show-on-card'(evt) {
|
||||
let $target = $(evt.target);
|
||||
if (!$target.hasClass('js-field-show-on-card')) {
|
||||
|
||||
@@ -72,6 +72,43 @@ template(name="filterSidebar")
|
||||
| (<span class="username">{{ username }}</span>)
|
||||
if Filter.assignees.isSelected _id
|
||||
i.fa.fa-check
|
||||
|
||||
hr
|
||||
h3
|
||||
i.fa.fa-list-alt
|
||||
| {{_ 'filter-dates-label' }}
|
||||
ul.sidebar-list
|
||||
li(class="{{#if Filter.dueAt.isSelected 'noDate'}}active{{/if}}")
|
||||
a.name.js-toggle-no-due-date-filter
|
||||
span.sidebar-list-item-description
|
||||
| {{_ 'filter-no-due-date' }}
|
||||
if Filter.dueAt.isSelected 'noDate'
|
||||
i.fa.fa-check
|
||||
li(class="{{#if Filter.dueAt.isSelected 'past'}}active{{/if}}")
|
||||
a.name.js-toggle-overdue-filter
|
||||
span.sidebar-list-item-description
|
||||
| {{_ 'filter-overdue' }}
|
||||
if Filter.dueAt.isSelected 'past'
|
||||
i.fa.fa-check
|
||||
li(class="{{#if Filter.dueAt.isSelected 'today'}}active{{/if}}")
|
||||
a.name.js-toggle-due-today-filter
|
||||
span.sidebar-list-item-description
|
||||
| {{_ 'filter-due-today' }}
|
||||
if Filter.dueAt.isSelected 'today'
|
||||
i.fa.fa-check
|
||||
li(class="{{#if Filter.dueAt.isSelected 'tomorrow'}}active{{/if}}")
|
||||
a.name.js-toggle-due-tomorrow-filter
|
||||
span.sidebar-list-item-description
|
||||
| {{_ 'filter-due-tomorrow' }}
|
||||
if Filter.dueAt.isSelected 'tomorrow'
|
||||
i.fa.fa-check
|
||||
li(class="{{#if Filter.dueAt.isSelected 'week'}}active{{/if}}")
|
||||
a.name.js-toggle-due-this-week-filter
|
||||
span.sidebar-list-item-description
|
||||
| {{_ 'filter-due-this-week' }}
|
||||
if Filter.dueAt.isSelected 'week'
|
||||
i.fa.fa-check
|
||||
|
||||
hr
|
||||
h3
|
||||
i.fa.fa-list-alt
|
||||
|
||||
@@ -23,6 +23,31 @@ BlazeComponent.extendComponent({
|
||||
Filter.assignees.toggle(this.currentData()._id);
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-no-due-date-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.noDate();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-overdue-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.past();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-due-today-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.today();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-due-tomorrow-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.tomorrow();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-due-this-week-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.dueAt.thisWeek();
|
||||
Filter.resetExceptions();
|
||||
},
|
||||
'click .js-toggle-archive-filter'(evt) {
|
||||
evt.preventDefault();
|
||||
Filter.archive.toggle(this.currentData()._id);
|
||||
|
||||
@@ -39,6 +39,10 @@ template(name="swimlaneActionPopup")
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-close-swimlane {{_ 'archive-swimlane'}}
|
||||
ul.pop-over-list
|
||||
li: a.js-copy-swimlane {{_ 'copy-swimlane'}}
|
||||
ul.pop-over-list
|
||||
li: a.js-move-swimlane {{_ 'move-swimlane'}}
|
||||
|
||||
template(name="swimlaneAddPopup")
|
||||
unless currentUser.isCommentOnly
|
||||
|
||||
@@ -54,9 +54,11 @@ Template.swimlaneActionPopup.events({
|
||||
this.archive();
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-move-swimlane': Popup.open('moveSwimlane'),
|
||||
'click .js-copy-swimlane': Popup.open('copySwimlane'),
|
||||
});
|
||||
|
||||
Template.swimlaneActionPopup.helpers({
|
||||
Template.swimlaneActionPopup.events({
|
||||
isCommentOnly() {
|
||||
return Meteor.user().isCommentOnly();
|
||||
},
|
||||
|
||||
@@ -61,3 +61,23 @@ template(name="addListForm")
|
||||
a.open-list-composer.js-open-inlined-form
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-list'}}
|
||||
|
||||
template(name="moveSwimlanePopup")
|
||||
unless currentUser.isWorker
|
||||
label {{_ 'boards'}}:
|
||||
select.js-select-boards(autofocus)
|
||||
each toBoard in toBoards
|
||||
option(value="{{toBoard._id}}") {{toBoard.title}}
|
||||
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm.js-done {{_ 'done'}}
|
||||
|
||||
template(name="copySwimlanePopup")
|
||||
unless currentUser.isWorker
|
||||
label {{_ 'boards'}}:
|
||||
select.js-select-boards(autofocus)
|
||||
each toBoard in toBoards
|
||||
option(value="{{toBoard._id}}" selected="{{#if $eq toBoard.title board.title}}1{{/if}}") {{toBoard.title}}
|
||||
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm.js-done {{_ 'done'}}
|
||||
@@ -323,3 +323,55 @@ BlazeComponent.extendComponent({
|
||||
initSortable(boardComponent, $listsDom);
|
||||
},
|
||||
}).register('listsGroup');
|
||||
|
||||
class MoveSwimlaneComponent extends BlazeComponent {
|
||||
serverMethod = 'moveSwimlane';
|
||||
|
||||
onCreated() {
|
||||
this.currentSwimlane = this.currentData();
|
||||
}
|
||||
|
||||
board() {
|
||||
return Boards.findOne(Session.get('currentBoard'));
|
||||
}
|
||||
|
||||
toBoardsSelector() {
|
||||
return {
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
type: 'board',
|
||||
_id: { $ne: this.board()._id },
|
||||
};
|
||||
}
|
||||
|
||||
toBoards() {
|
||||
return Boards.find(this.toBoardsSelector(), { sort: { title: 1 } });
|
||||
}
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-done'() {
|
||||
// const swimlane = Swimlanes.findOne(this.currentSwimlane._id);
|
||||
const bSelect = $('.js-select-boards')[0];
|
||||
let boardId;
|
||||
if (bSelect) {
|
||||
boardId = bSelect.options[bSelect.selectedIndex].value;
|
||||
Meteor.call(this.serverMethod, this.currentSwimlane._id, boardId);
|
||||
}
|
||||
Popup.close();
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
MoveSwimlaneComponent.register('moveSwimlanePopup');
|
||||
|
||||
(class extends MoveSwimlaneComponent {
|
||||
serverMethod = 'copySwimlane';
|
||||
toBoardsSelector() {
|
||||
const selector = super.toBoardsSelector();
|
||||
delete selector._id;
|
||||
return selector;
|
||||
}
|
||||
}.register('copySwimlanePopup'));
|
||||
@@ -1,5 +1,5 @@
|
||||
template(name="userAvatar")
|
||||
a.member.js-member(title="{{userData.profile.fullname}} ({{userData.username}}) {{_ memberType}}")
|
||||
a.member(class="js-{{#if assignee}}assignee{{else}}member{{/if}}" title="{{userData.profile.fullname}} ({{userData.username}}) {{_ memberType}}")
|
||||
if userData.profile.avatarUrl
|
||||
img.avatar.avatar-image(src="{{userData.profile.avatarUrl}}")
|
||||
else
|
||||
@@ -72,9 +72,10 @@ template(name="cardMemberPopup")
|
||||
h3= user.profile.fullname
|
||||
p.quiet @{{ user.username }}
|
||||
ul.pop-over-list
|
||||
if currentUser.isNotCommentOnly
|
||||
if currentUser.isNotWorker
|
||||
li: a.js-remove-member {{_ 'remove-member-from-card'}}
|
||||
unless noRemove
|
||||
if currentUser.isNotCommentOnly
|
||||
if currentUser.isNotWorker
|
||||
li: a.js-remove-member {{_ 'remove-member-from-card'}}
|
||||
|
||||
if $eq currentUser._id user._id
|
||||
with currentUser
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import Cards from '/models/cards';
|
||||
import Avatars from '/models/avatars';
|
||||
import Users from '/models/users';
|
||||
|
||||
Template.userAvatar.helpers({
|
||||
userData() {
|
||||
// We need to handle a special case for the search results provided by the
|
||||
@@ -30,10 +34,6 @@ Template.userAvatar.helpers({
|
||||
},
|
||||
});
|
||||
|
||||
Template.userAvatar.events({
|
||||
'click .js-change-avatar': Popup.open('changeAvatar'),
|
||||
});
|
||||
|
||||
Template.userAvatarInitials.helpers({
|
||||
initials() {
|
||||
const user = Users.findOne(this.userId);
|
||||
|
||||
@@ -25,10 +25,6 @@ template(name="memberMenuPopup")
|
||||
a.js-global-search(href="{{pathFor 'global-search'}}")
|
||||
i.fa.fa-search
|
||||
| {{_ 'globalSearch-title'}}
|
||||
li
|
||||
a.js-broken-cards(href="{{pathFor 'broken-cards'}}")
|
||||
i.fa.fa-chain-broken
|
||||
| {{_ 'broken-cards'}}
|
||||
li
|
||||
a(href="{{pathFor 'home'}}")
|
||||
span.fa.fa-home
|
||||
|
||||
@@ -171,11 +171,11 @@ Template.changeLanguagePopup.helpers({
|
||||
} else if (lang.name === 'ar-EG') {
|
||||
// ar-EG = Arabic (Egypt), simply Masri (مَصرى, [ˈmɑsˤɾi], Egyptian, Masr refers to Cairo)
|
||||
name = 'مَصرى';
|
||||
} else if (lang.name === 'es-PY') {
|
||||
name = 'Español de Paraguayo';
|
||||
} else if (lang.name === 'fa-IR') {
|
||||
// fa-IR = Persian (Iran)
|
||||
name = 'فارسی/پارسی (ایران)';
|
||||
} else if (lang.name === 'de-CH') {
|
||||
name = 'Deutsch (Schweiz)';
|
||||
} else if (lang.name === 'fr-BE') {
|
||||
name = 'Français (Belgique)';
|
||||
} else if (lang.name === 'fr-CA') {
|
||||
@@ -188,6 +188,14 @@ Template.changeLanguagePopup.helpers({
|
||||
name = 'Latviešu';
|
||||
} else if (lang.name === 'Español') {
|
||||
name = 'español';
|
||||
} else if (lang.name === 'es_419') {
|
||||
name = 'español de América Latina';
|
||||
} else if (lang.name === 'es-419') {
|
||||
name = 'español de América Latina';
|
||||
} else if (lang.name === 'Español de América Latina') {
|
||||
name = 'español de América Latina';
|
||||
} else if (lang.name === 'es-LA') {
|
||||
name = 'español de América Latina';
|
||||
} else if (lang.name === 'Español de Argentina') {
|
||||
name = 'español de Argentina';
|
||||
} else if (lang.name === 'Español de Chile') {
|
||||
@@ -198,6 +206,8 @@ Template.changeLanguagePopup.helpers({
|
||||
name = 'español de México';
|
||||
} else if (lang.name === 'es-PY') {
|
||||
name = 'español de Paraguayo';
|
||||
} else if (lang.name === 'Español de Paraguayo') {
|
||||
name = 'español de Paraguayo';
|
||||
} else if (lang.name === 'Español de Perú') {
|
||||
name = 'español de Perú';
|
||||
} else if (lang.name === 'Español de Puerto Rico') {
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import Cards from '../../models/cards';
|
||||
import SessionData from '../../models/usersessiondata';
|
||||
|
||||
export class CardSearchPagedComponent extends BlazeComponent {
|
||||
onCreated() {
|
||||
this.searching = new ReactiveVar(false);
|
||||
this.hasResults = new ReactiveVar(false);
|
||||
this.hasQueryErrors = new ReactiveVar(false);
|
||||
this.query = new ReactiveVar('');
|
||||
this.resultsHeading = new ReactiveVar('');
|
||||
this.searchLink = new ReactiveVar(null);
|
||||
this.results = new ReactiveVar([]);
|
||||
this.hasNextPage = new ReactiveVar(false);
|
||||
this.hasPreviousPage = new ReactiveVar(false);
|
||||
this.resultsCount = 0;
|
||||
this.totalHits = 0;
|
||||
this.queryErrors = null;
|
||||
this.resultsPerPage = 25;
|
||||
this.sessionId = SessionData.getSessionId();
|
||||
this.subscriptionHandle = null;
|
||||
this.serverError = new ReactiveVar(false);
|
||||
|
||||
const that = this;
|
||||
this.subscriptionCallbacks = {
|
||||
onReady() {
|
||||
that.getResults();
|
||||
that.searching.set(false);
|
||||
that.hasResults.set(true);
|
||||
that.serverError.set(false);
|
||||
},
|
||||
onError(error) {
|
||||
that.searching.set(false);
|
||||
that.hasResults.set(false);
|
||||
that.serverError.set(true);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Error.reason:', error.reason);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Error.message:', error.message);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Error.stack:', error.stack);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
resetSearch() {
|
||||
this.searching.set(false);
|
||||
this.results.set([]);
|
||||
this.hasResults.set(false);
|
||||
this.hasQueryErrors.set(false);
|
||||
this.resultsHeading.set('');
|
||||
this.serverError.set(false);
|
||||
this.resultsCount = 0;
|
||||
this.totalHits = 0;
|
||||
this.queryErrors = null;
|
||||
}
|
||||
|
||||
getSessionData(sessionId) {
|
||||
return SessionData.findOne({
|
||||
sessionId: sessionId ? sessionId : SessionData.getSessionId(),
|
||||
});
|
||||
}
|
||||
|
||||
getResults() {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('getting results');
|
||||
const sessionData = this.getSessionData();
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('session data:', sessionData);
|
||||
const cards = [];
|
||||
sessionData.cards.forEach(cardId => {
|
||||
cards.push(Cards.findOne({ _id: cardId }));
|
||||
});
|
||||
this.queryErrors = sessionData.errors;
|
||||
if (this.queryErrors.length) {
|
||||
// console.log('queryErrors:', this.queryErrorMessages());
|
||||
this.hasQueryErrors.set(true);
|
||||
// return null;
|
||||
}
|
||||
|
||||
if (cards) {
|
||||
this.totalHits = sessionData.totalHits;
|
||||
this.resultsCount = cards.length;
|
||||
this.resultsStart = sessionData.lastHit - this.resultsCount + 1;
|
||||
this.resultsEnd = sessionData.lastHit;
|
||||
this.resultsHeading.set(this.getResultsHeading());
|
||||
this.results.set(cards);
|
||||
this.hasNextPage.set(sessionData.lastHit < sessionData.totalHits);
|
||||
this.hasPreviousPage.set(
|
||||
sessionData.lastHit - sessionData.resultsCount > 0,
|
||||
);
|
||||
return cards;
|
||||
}
|
||||
|
||||
this.resultsCount = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
stopSubscription() {
|
||||
if (this.subscriptionHandle) {
|
||||
this.subscriptionHandle.stop();
|
||||
}
|
||||
}
|
||||
|
||||
getSubscription(queryParams) {
|
||||
return Meteor.subscribe(
|
||||
'globalSearch',
|
||||
this.sessionId,
|
||||
queryParams.params,
|
||||
queryParams.text,
|
||||
this.subscriptionCallbacks,
|
||||
);
|
||||
}
|
||||
|
||||
runGlobalSearch(queryParams) {
|
||||
this.searching.set(true);
|
||||
this.stopSubscription();
|
||||
this.subscriptionHandle = this.getSubscription(queryParams);
|
||||
}
|
||||
|
||||
queryErrorMessages() {
|
||||
const messages = [];
|
||||
|
||||
this.queryErrors.forEach(err => {
|
||||
let value = err.color ? TAPi18n.__(`color-${err.value}`) : err.value;
|
||||
if (!value) {
|
||||
value = err.value;
|
||||
}
|
||||
messages.push(TAPi18n.__(err.tag, value));
|
||||
});
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
nextPage() {
|
||||
this.searching.set(true);
|
||||
this.stopSubscription();
|
||||
this.subscriptionHandle = Meteor.subscribe(
|
||||
'nextPage',
|
||||
this.sessionId,
|
||||
this.subscriptionCallbacks,
|
||||
);
|
||||
}
|
||||
|
||||
previousPage() {
|
||||
this.searching.set(true);
|
||||
this.stopSubscription();
|
||||
this.subscriptionHandle = Meteor.subscribe(
|
||||
'previousPage',
|
||||
this.sessionId,
|
||||
this.subscriptionCallbacks,
|
||||
);
|
||||
}
|
||||
|
||||
getResultsHeading() {
|
||||
if (this.resultsCount === 0) {
|
||||
return TAPi18n.__('no-cards-found');
|
||||
} else if (this.resultsCount === 1) {
|
||||
return TAPi18n.__('one-card-found');
|
||||
} else if (this.resultsCount === this.totalHits) {
|
||||
return TAPi18n.__('n-cards-found', this.resultsCount);
|
||||
}
|
||||
|
||||
return TAPi18n.__('n-n-of-n-cards-found', {
|
||||
start: this.resultsStart,
|
||||
end: this.resultsEnd,
|
||||
total: this.totalHits,
|
||||
});
|
||||
}
|
||||
|
||||
getSearchHref() {
|
||||
const baseUrl = window.location.href.replace(/([?#].*$|\s*$)/, '');
|
||||
return `${baseUrl}?q=${encodeURIComponent(this.query.get())}`;
|
||||
}
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-next-page'(evt) {
|
||||
evt.preventDefault();
|
||||
this.nextPage();
|
||||
},
|
||||
'click .js-previous-page'(evt) {
|
||||
evt.preventDefault();
|
||||
this.previousPage();
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
+15
-17
@@ -6,17 +6,17 @@ function adjustedTimeFormat() {
|
||||
.replace(/HH/i, 'H');
|
||||
}
|
||||
|
||||
DatePicker = BlazeComponent.extendComponent({
|
||||
export class DatePicker extends BlazeComponent {
|
||||
template() {
|
||||
return 'datepicker';
|
||||
},
|
||||
}
|
||||
|
||||
onCreated(defaultTime = '1970-01-01 08:00:00') {
|
||||
this.error = new ReactiveVar('');
|
||||
this.card = this.data();
|
||||
this.date = new ReactiveVar(moment.invalid());
|
||||
this.defaultTime = defaultTime;
|
||||
},
|
||||
}
|
||||
|
||||
startDayOfWeek() {
|
||||
const currentUser = Meteor.user();
|
||||
@@ -25,7 +25,7 @@ DatePicker = BlazeComponent.extendComponent({
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
onRendered() {
|
||||
const $picker = this.$('.js-datepicker')
|
||||
@@ -42,7 +42,7 @@ DatePicker = BlazeComponent.extendComponent({
|
||||
this.error.set('');
|
||||
const timeInput = this.find('#time');
|
||||
timeInput.focus();
|
||||
if (!timeInput.value) {
|
||||
if (!timeInput.value && this.defaultTime) {
|
||||
const currentHour = evt.date.getHours();
|
||||
const defaultMoment = moment(
|
||||
currentHour > 0 ? evt.date : this.defaultTime,
|
||||
@@ -55,22 +55,22 @@ DatePicker = BlazeComponent.extendComponent({
|
||||
if (this.date.get().isValid()) {
|
||||
$picker.datepicker('update', this.date.get().toDate());
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
showDate() {
|
||||
if (this.date.get().isValid()) return this.date.get().format('L');
|
||||
return '';
|
||||
},
|
||||
}
|
||||
showTime() {
|
||||
if (this.date.get().isValid()) return this.date.get().format('LT');
|
||||
return '';
|
||||
},
|
||||
}
|
||||
dateFormat() {
|
||||
return moment.localeData().longDateFormat('L');
|
||||
},
|
||||
}
|
||||
timeFormat() {
|
||||
return moment.localeData().longDateFormat('LT');
|
||||
},
|
||||
}
|
||||
|
||||
events() {
|
||||
return [
|
||||
@@ -106,7 +106,7 @@ DatePicker = BlazeComponent.extendComponent({
|
||||
const dateString = `${evt.target.date.value} ${time}`;
|
||||
const newCompleteDate = moment(
|
||||
dateString,
|
||||
'L ' + adjustedTimeFormat(),
|
||||
`L ${adjustedTimeFormat()}`,
|
||||
true,
|
||||
);
|
||||
if (!newTime.isValid()) {
|
||||
@@ -120,10 +120,8 @@ DatePicker = BlazeComponent.extendComponent({
|
||||
if (newCompleteDate.isValid()) {
|
||||
this._storeDate(newCompleteDate.toDate());
|
||||
Popup.close();
|
||||
} else {
|
||||
if (!this.error) {
|
||||
this.error.set('invalid');
|
||||
}
|
||||
} else if (!this.error) {
|
||||
this.error.set('invalid');
|
||||
}
|
||||
},
|
||||
'click .js-delete-date'(evt) {
|
||||
@@ -133,5 +131,5 @@ DatePicker = BlazeComponent.extendComponent({
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,125 @@ function showFilterSidebar() {
|
||||
Sidebar.setView('filter');
|
||||
}
|
||||
|
||||
class DateFilter {
|
||||
constructor() {
|
||||
this._dep = new Tracker.Dependency();
|
||||
this.subField = ''; // Prevent name mangling in Filter
|
||||
this._filter = null;
|
||||
this._filterState = null;
|
||||
}
|
||||
|
||||
_updateState(state) {
|
||||
this._filterState = state;
|
||||
showFilterSidebar();
|
||||
this._dep.changed();
|
||||
}
|
||||
|
||||
// past builds a filter for all dates before now
|
||||
past() {
|
||||
if (this._filterState == 'past') { this.reset(); return; }
|
||||
this._filter = { $lte: moment().toDate() };
|
||||
this._updateState('past');
|
||||
}
|
||||
|
||||
// today is a convenience method for calling relativeDay with 0
|
||||
today() {
|
||||
if (this._filterState == 'today') { this.reset(); return; }
|
||||
this.relativeDay(0);
|
||||
this._updateState('today');
|
||||
}
|
||||
|
||||
// tomorrow is a convenience method for calling relativeDay with 1
|
||||
tomorrow() {
|
||||
if (this._filterState == 'tomorrow') { this.reset(); return; }
|
||||
this.relativeDay(1);
|
||||
this._updateState('tomorrow');
|
||||
}
|
||||
|
||||
// thisWeek is a convenience method for calling relativeWeek with 1
|
||||
thisWeek() {
|
||||
this.relativeWeek(1);
|
||||
}
|
||||
|
||||
// relativeDay builds a filter starting from now and including all
|
||||
// days up to today +/- offset.
|
||||
relativeDay(offset) {
|
||||
if (this._filterState == 'day') { this.reset(); return; }
|
||||
|
||||
var startDay = moment().startOf('day').toDate(),
|
||||
endDay = moment().endOf('day').add(offset, 'day').toDate();
|
||||
|
||||
if (offset >= 0) {
|
||||
this._filter = { $gte: startDay, $lte: endDay };
|
||||
} else {
|
||||
this._filter = { $lte: startDay, $gte: endDay };
|
||||
}
|
||||
|
||||
this._updateState('day');
|
||||
}
|
||||
|
||||
// relativeWeek builds a filter starting from today and including all
|
||||
// weeks up to today +/- offset. This considers the user's preferred
|
||||
// start of week day (as defined by Meteor).
|
||||
relativeWeek(offset) {
|
||||
if (this._filterState == 'week') { this.reset(); return; }
|
||||
|
||||
// getStartDayOfWeek returns the offset from Sunday of the user's
|
||||
// preferred starting day of the week. This date should be added
|
||||
// to the moment start of week to get the real start of week date.
|
||||
// The default is 1, meaning Monday.
|
||||
const currentUser = Meteor.user();
|
||||
const weekStartDay = currentUser ? currentUser.getStartDayOfWeek() : 1;
|
||||
|
||||
// Moments are mutable so they must be cloned before modification
|
||||
var thisWeekStart = moment().startOf('day').startOf('week').add(weekStartDay, 'days');
|
||||
var thisWeekEnd = thisWeekStart.clone().add(offset, 'week').endOf('day');
|
||||
var startDate = thisWeekStart.toDate();
|
||||
var endDate = thisWeekEnd.toDate();
|
||||
|
||||
if (offset >= 0) {
|
||||
this._filter = { $gte: startDate, $lte: endDate };
|
||||
} else {
|
||||
this._filter = { $lte: startDate, $gte: endDate };
|
||||
}
|
||||
|
||||
this._updateState('week');
|
||||
}
|
||||
|
||||
// noDate builds a filter for items where date is not set
|
||||
noDate() {
|
||||
if (this._filterState == 'noDate') { this.reset(); return; }
|
||||
this._filter = null;
|
||||
this._updateState('noDate');
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._filter = null;
|
||||
this._filterState = null;
|
||||
this._dep.changed();
|
||||
}
|
||||
|
||||
isSelected(val) {
|
||||
this._dep.depend();
|
||||
return this._filterState == val;
|
||||
}
|
||||
|
||||
_isActive() {
|
||||
this._dep.depend();
|
||||
return this._filterState !== null;
|
||||
}
|
||||
|
||||
_getMongoSelector() {
|
||||
this._dep.depend();
|
||||
return this._filter;
|
||||
}
|
||||
|
||||
_getEmptySelector() {
|
||||
this._dep.depend();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Use a "set" filter for a field that is a set of documents uniquely
|
||||
// identified. For instance `{ labels: ['labelA', 'labelC', 'labelD'] }`.
|
||||
// use "subField" for searching inside object Fields.
|
||||
@@ -462,6 +581,7 @@ Filter = {
|
||||
assignees: new SetFilter(),
|
||||
archive: new SetFilter(),
|
||||
hideEmpty: new SetFilter(),
|
||||
dueAt: new DateFilter(),
|
||||
customFields: new SetFilter('_id'),
|
||||
advanced: new AdvancedFilter(),
|
||||
lists: new AdvancedFilter(), // we need the ability to filter list by name as well
|
||||
@@ -472,6 +592,7 @@ Filter = {
|
||||
'assignees',
|
||||
'archive',
|
||||
'hideEmpty',
|
||||
'dueAt',
|
||||
'customFields',
|
||||
],
|
||||
|
||||
|
||||
+1
-1
@@ -50,5 +50,5 @@ EscapeActions.register(
|
||||
'modalWindow',
|
||||
() => Modal.close(),
|
||||
() => Modal.isOpen(),
|
||||
{ noClickEscapeOn: '.modal-container' },
|
||||
{ noClickEscapeOn: '.modal-container,.model-content' },
|
||||
);
|
||||
@@ -0,0 +1,51 @@
|
||||
export const ALLOWED_BOARD_COLORS = [
|
||||
'belize',
|
||||
'nephritis',
|
||||
'pomegranate',
|
||||
'pumpkin',
|
||||
'wisteria',
|
||||
'moderatepink',
|
||||
'strongcyan',
|
||||
'limegreen',
|
||||
'midnight',
|
||||
'dark',
|
||||
'relax',
|
||||
'corteza',
|
||||
'clearblue',
|
||||
'natural',
|
||||
'modern',
|
||||
'moderndark',
|
||||
];
|
||||
export const ALLOWED_COLORS = [
|
||||
'white',
|
||||
'green',
|
||||
'yellow',
|
||||
'orange',
|
||||
'red',
|
||||
'purple',
|
||||
'blue',
|
||||
'sky',
|
||||
'lime',
|
||||
'pink',
|
||||
'black',
|
||||
'silver',
|
||||
'peachpuff',
|
||||
'crimson',
|
||||
'plum',
|
||||
'darkgreen',
|
||||
'slateblue',
|
||||
'magenta',
|
||||
'gold',
|
||||
'navy',
|
||||
'gray',
|
||||
'saddlebrown',
|
||||
'paleturquoise',
|
||||
'mistyrose',
|
||||
'indigo',
|
||||
];
|
||||
export const TYPE_BOARD = 'board';
|
||||
export const TYPE_CARD = 'cardType-card';
|
||||
export const TYPE_LINKED_BOARD = 'cardType-linkedBoard';
|
||||
export const TYPE_LINKED_CARD = 'cardType-linkedCard';
|
||||
export const TYPE_TEMPLATE_BOARD = 'template-board';
|
||||
export const TYPE_TEMPLATE_CONTAINER = 'template-container';
|
||||
@@ -0,0 +1,519 @@
|
||||
import {
|
||||
OPERATOR_ASSIGNEE,
|
||||
OPERATOR_BOARD,
|
||||
OPERATOR_COMMENT,
|
||||
OPERATOR_CREATED_AT,
|
||||
OPERATOR_CREATOR,
|
||||
OPERATOR_DUE,
|
||||
OPERATOR_HAS,
|
||||
OPERATOR_LABEL,
|
||||
OPERATOR_LIMIT,
|
||||
OPERATOR_LIST,
|
||||
OPERATOR_MEMBER,
|
||||
OPERATOR_MODIFIED_AT,
|
||||
OPERATOR_SORT,
|
||||
OPERATOR_STATUS,
|
||||
OPERATOR_SWIMLANE,
|
||||
OPERATOR_UNKNOWN,
|
||||
OPERATOR_USER,
|
||||
ORDER_ASCENDING,
|
||||
ORDER_DESCENDING,
|
||||
PREDICATE_ALL,
|
||||
PREDICATE_ARCHIVED,
|
||||
PREDICATE_ASSIGNEES,
|
||||
PREDICATE_ATTACHMENT,
|
||||
PREDICATE_CHECKLIST,
|
||||
PREDICATE_CREATED_AT,
|
||||
PREDICATE_DESCRIPTION,
|
||||
PREDICATE_DUE_AT,
|
||||
PREDICATE_END_AT,
|
||||
PREDICATE_ENDED,
|
||||
PREDICATE_MEMBERS,
|
||||
PREDICATE_MODIFIED_AT,
|
||||
PREDICATE_MONTH,
|
||||
PREDICATE_OPEN,
|
||||
PREDICATE_OVERDUE,
|
||||
PREDICATE_PRIVATE,
|
||||
PREDICATE_PUBLIC,
|
||||
PREDICATE_QUARTER,
|
||||
PREDICATE_START_AT,
|
||||
PREDICATE_WEEK,
|
||||
PREDICATE_YEAR,
|
||||
} from './search-const';
|
||||
import Boards from '../models/boards';
|
||||
import moment from 'moment';
|
||||
|
||||
export class QueryParams {
|
||||
text = '';
|
||||
|
||||
constructor(params = {}, text = '') {
|
||||
this.params = params;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
hasOperator(operator) {
|
||||
return (
|
||||
this.params[operator] !== undefined &&
|
||||
(this.params[operator].length === undefined ||
|
||||
this.params[operator].length > 0)
|
||||
);
|
||||
}
|
||||
|
||||
addPredicate(operator, predicate) {
|
||||
if (!this.hasOperator(operator)) {
|
||||
this.params[operator] = [];
|
||||
}
|
||||
this.params[operator].push(predicate);
|
||||
}
|
||||
|
||||
setPredicate(operator, predicate) {
|
||||
this.params[operator] = predicate;
|
||||
}
|
||||
|
||||
getPredicate(operator) {
|
||||
if (typeof this.params[operator] === 'object') {
|
||||
return this.params[operator][0];
|
||||
} else {
|
||||
return this.params[operator];
|
||||
}
|
||||
}
|
||||
|
||||
getPredicates(operator) {
|
||||
return this.params[operator];
|
||||
}
|
||||
|
||||
getParams() {
|
||||
return this.params;
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryErrors {
|
||||
operatorTagMap = [
|
||||
[OPERATOR_BOARD, 'board-title-not-found'],
|
||||
[OPERATOR_SWIMLANE, 'swimlane-title-not-found'],
|
||||
[
|
||||
OPERATOR_LABEL,
|
||||
label => {
|
||||
if (Boards.labelColors().includes(label)) {
|
||||
return {
|
||||
tag: 'label-color-not-found',
|
||||
value: label,
|
||||
color: true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
tag: 'label-not-found',
|
||||
value: label,
|
||||
color: false,
|
||||
};
|
||||
}
|
||||
},
|
||||
],
|
||||
[OPERATOR_LIST, 'list-title-not-found'],
|
||||
[OPERATOR_COMMENT, 'comment-not-found'],
|
||||
[OPERATOR_USER, 'user-username-not-found'],
|
||||
[OPERATOR_ASSIGNEE, 'user-username-not-found'],
|
||||
[OPERATOR_MEMBER, 'user-username-not-found'],
|
||||
[OPERATOR_CREATOR, 'user-username-not-found'],
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this._errors = {};
|
||||
|
||||
this.operatorTags = {};
|
||||
this.operatorTagMap.forEach(([operator, tag]) => {
|
||||
this.operatorTags[operator] = tag;
|
||||
});
|
||||
|
||||
this.colorMap = Boards.colorMap();
|
||||
}
|
||||
|
||||
addError(operator, error) {
|
||||
if (!this._errors[operator]) {
|
||||
this._errors[operator] = [];
|
||||
}
|
||||
this._errors[operator].push(error);
|
||||
}
|
||||
|
||||
addNotFound(operator, value) {
|
||||
if (typeof this.operatorTags[operator] === 'function') {
|
||||
this.addError(operator, this.operatorTags[operator](value));
|
||||
} else {
|
||||
this.addError(operator, { tag: this.operatorTags[operator], value });
|
||||
}
|
||||
}
|
||||
|
||||
hasErrors() {
|
||||
return Object.entries(this._errors).length > 0;
|
||||
}
|
||||
|
||||
errors() {
|
||||
const errs = [];
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Object.entries(this._errors).forEach(([, errors]) => {
|
||||
errors.forEach(err => {
|
||||
errs.push(err);
|
||||
});
|
||||
});
|
||||
return errs;
|
||||
}
|
||||
|
||||
errorMessages() {
|
||||
const messages = [];
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Object.entries(this._errors).forEach(([, errors]) => {
|
||||
errors.forEach(err => {
|
||||
messages.push(TAPi18n.__(err.tag, err.value));
|
||||
});
|
||||
});
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
export class Query {
|
||||
selector = {};
|
||||
projection = {};
|
||||
|
||||
constructor(selector, projection) {
|
||||
this._errors = new QueryErrors();
|
||||
this.queryParams = new QueryParams();
|
||||
this.colorMap = Boards.colorMap();
|
||||
|
||||
if (selector) {
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
if (projection) {
|
||||
this.projection = projection;
|
||||
}
|
||||
}
|
||||
|
||||
hasErrors() {
|
||||
return this._errors.hasErrors();
|
||||
}
|
||||
|
||||
errors() {
|
||||
return this._errors.errors();
|
||||
}
|
||||
|
||||
errorMessages() {
|
||||
return this._errors.errorMessages();
|
||||
}
|
||||
|
||||
getQueryParams() {
|
||||
return this.queryParams;
|
||||
}
|
||||
|
||||
setQueryParams(queryParams) {
|
||||
this.queryParams = queryParams;
|
||||
}
|
||||
|
||||
addPredicate(operator, predicate) {
|
||||
this.queryParams.addPredicate(operator, predicate);
|
||||
}
|
||||
|
||||
buildParams(queryText) {
|
||||
queryText = queryText.trim();
|
||||
// eslint-disable-next-line no-console
|
||||
//console.log('query:', query);
|
||||
|
||||
if (!queryText) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reOperator1 = new RegExp(
|
||||
'^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<value>[\\p{Letter}\\p{Mark}]+)(\\s+|$)',
|
||||
'iu',
|
||||
);
|
||||
const reOperator2 = new RegExp(
|
||||
'^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<quote>["\']*)(?<value>.*?)\\k<quote>(\\s+|$)',
|
||||
'iu',
|
||||
);
|
||||
const reText = new RegExp('^(?<text>\\S+)(\\s+|$)', 'u');
|
||||
const reQuotedText = new RegExp(
|
||||
'^(?<quote>["\'])(?<text>.*?)\\k<quote>(\\s+|$)',
|
||||
'u',
|
||||
);
|
||||
const reNegatedOperator = new RegExp('^-(?<operator>.*)$');
|
||||
|
||||
const operators = {
|
||||
'operator-board': OPERATOR_BOARD,
|
||||
'operator-board-abbrev': OPERATOR_BOARD,
|
||||
'operator-swimlane': OPERATOR_SWIMLANE,
|
||||
'operator-swimlane-abbrev': OPERATOR_SWIMLANE,
|
||||
'operator-list': OPERATOR_LIST,
|
||||
'operator-list-abbrev': OPERATOR_LIST,
|
||||
'operator-label': OPERATOR_LABEL,
|
||||
'operator-label-abbrev': OPERATOR_LABEL,
|
||||
'operator-user': OPERATOR_USER,
|
||||
'operator-user-abbrev': OPERATOR_USER,
|
||||
'operator-member': OPERATOR_MEMBER,
|
||||
'operator-member-abbrev': OPERATOR_MEMBER,
|
||||
'operator-assignee': OPERATOR_ASSIGNEE,
|
||||
'operator-creator': OPERATOR_CREATOR,
|
||||
'operator-assignee-abbrev': OPERATOR_ASSIGNEE,
|
||||
'operator-status': OPERATOR_STATUS,
|
||||
'operator-due': OPERATOR_DUE,
|
||||
'operator-created': OPERATOR_CREATED_AT,
|
||||
'operator-modified': OPERATOR_MODIFIED_AT,
|
||||
'operator-comment': OPERATOR_COMMENT,
|
||||
'operator-has': OPERATOR_HAS,
|
||||
'operator-sort': OPERATOR_SORT,
|
||||
'operator-limit': OPERATOR_LIMIT,
|
||||
};
|
||||
|
||||
const predicates = {
|
||||
due: {
|
||||
'predicate-overdue': PREDICATE_OVERDUE,
|
||||
},
|
||||
durations: {
|
||||
'predicate-week': PREDICATE_WEEK,
|
||||
'predicate-month': PREDICATE_MONTH,
|
||||
'predicate-quarter': PREDICATE_QUARTER,
|
||||
'predicate-year': PREDICATE_YEAR,
|
||||
},
|
||||
status: {
|
||||
'predicate-archived': PREDICATE_ARCHIVED,
|
||||
'predicate-all': PREDICATE_ALL,
|
||||
'predicate-open': PREDICATE_OPEN,
|
||||
'predicate-ended': PREDICATE_ENDED,
|
||||
'predicate-public': PREDICATE_PUBLIC,
|
||||
'predicate-private': PREDICATE_PRIVATE,
|
||||
},
|
||||
sorts: {
|
||||
'predicate-due': PREDICATE_DUE_AT,
|
||||
'predicate-created': PREDICATE_CREATED_AT,
|
||||
'predicate-modified': PREDICATE_MODIFIED_AT,
|
||||
},
|
||||
has: {
|
||||
'predicate-description': PREDICATE_DESCRIPTION,
|
||||
'predicate-checklist': PREDICATE_CHECKLIST,
|
||||
'predicate-attachment': PREDICATE_ATTACHMENT,
|
||||
'predicate-start': PREDICATE_START_AT,
|
||||
'predicate-end': PREDICATE_END_AT,
|
||||
'predicate-due': PREDICATE_DUE_AT,
|
||||
'predicate-assignee': PREDICATE_ASSIGNEES,
|
||||
'predicate-member': PREDICATE_MEMBERS,
|
||||
},
|
||||
};
|
||||
const predicateTranslations = {};
|
||||
Object.entries(predicates).forEach(([category, catPreds]) => {
|
||||
predicateTranslations[category] = {};
|
||||
Object.entries(catPreds).forEach(([tag, value]) => {
|
||||
predicateTranslations[category][TAPi18n.__(tag)] = value;
|
||||
});
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('predicateTranslations:', predicateTranslations);
|
||||
|
||||
const operatorMap = {};
|
||||
Object.entries(operators).forEach(([key, value]) => {
|
||||
operatorMap[TAPi18n.__(key).toLowerCase()] = value;
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('operatorMap:', operatorMap);
|
||||
|
||||
let text = '';
|
||||
while (queryText) {
|
||||
let m = queryText.match(reOperator1);
|
||||
if (!m) {
|
||||
m = queryText.match(reOperator2);
|
||||
if (m) {
|
||||
queryText = queryText.replace(reOperator2, '');
|
||||
}
|
||||
} else {
|
||||
queryText = queryText.replace(reOperator1, '');
|
||||
}
|
||||
if (m) {
|
||||
let op;
|
||||
if (m.groups.operator) {
|
||||
op = m.groups.operator.toLowerCase();
|
||||
} else {
|
||||
op = m.groups.abbrev.toLowerCase();
|
||||
}
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (operatorMap.hasOwnProperty(op)) {
|
||||
const operator = operatorMap[op];
|
||||
let value = m.groups.value;
|
||||
if (operator === OPERATOR_LABEL) {
|
||||
if (value in this.colorMap) {
|
||||
value = this.colorMap[value];
|
||||
// console.log('found color:', value);
|
||||
}
|
||||
} else if (
|
||||
[OPERATOR_DUE, OPERATOR_CREATED_AT, OPERATOR_MODIFIED_AT].includes(
|
||||
operator,
|
||||
)
|
||||
) {
|
||||
const days = parseInt(value, 10);
|
||||
let duration = null;
|
||||
if (isNaN(days)) {
|
||||
// duration was specified as text
|
||||
if (predicateTranslations.durations[value]) {
|
||||
duration = predicateTranslations.durations[value];
|
||||
let date = null;
|
||||
switch (duration) {
|
||||
case PREDICATE_WEEK:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const week = moment().week();
|
||||
if (week === 52) {
|
||||
date = moment(1, 'W');
|
||||
date.set('year', date.year() + 1);
|
||||
} else {
|
||||
date = moment(week + 1, 'W');
|
||||
}
|
||||
break;
|
||||
case PREDICATE_MONTH:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const month = moment().month();
|
||||
// .month() is zero indexed
|
||||
if (month === 11) {
|
||||
date = moment(1, 'M');
|
||||
date.set('year', date.year() + 1);
|
||||
} else {
|
||||
date = moment(month + 2, 'M');
|
||||
}
|
||||
break;
|
||||
case PREDICATE_QUARTER:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const quarter = moment().quarter();
|
||||
if (quarter === 4) {
|
||||
date = moment(1, 'Q');
|
||||
date.set('year', date.year() + 1);
|
||||
} else {
|
||||
date = moment(quarter + 1, 'Q');
|
||||
}
|
||||
break;
|
||||
case PREDICATE_YEAR:
|
||||
date = moment(moment().year() + 1, 'YYYY');
|
||||
break;
|
||||
}
|
||||
if (date) {
|
||||
value = {
|
||||
operator: '$lt',
|
||||
value: date.format('YYYY-MM-DD'),
|
||||
};
|
||||
}
|
||||
} else if (
|
||||
operator === OPERATOR_DUE &&
|
||||
value === PREDICATE_OVERDUE
|
||||
) {
|
||||
value = {
|
||||
operator: '$lt',
|
||||
value: moment().format('YYYY-MM-DD'),
|
||||
};
|
||||
} else {
|
||||
this.errors.addError(OPERATOR_DUE, {
|
||||
tag: 'operator-number-expected',
|
||||
value: { operator: op, value },
|
||||
});
|
||||
continue;
|
||||
}
|
||||
} else if (operator === OPERATOR_DUE) {
|
||||
value = {
|
||||
operator: '$lt',
|
||||
value: moment(moment().format('YYYY-MM-DD'))
|
||||
.add(days + 1, duration ? duration : 'days')
|
||||
.format(),
|
||||
};
|
||||
} else {
|
||||
value = {
|
||||
operator: '$gte',
|
||||
value: moment(moment().format('YYYY-MM-DD'))
|
||||
.subtract(days, duration ? duration : 'days')
|
||||
.format(),
|
||||
};
|
||||
}
|
||||
} else if (operator === OPERATOR_SORT) {
|
||||
let negated = false;
|
||||
const m = value.match(reNegatedOperator);
|
||||
if (m) {
|
||||
value = m.groups.operator;
|
||||
negated = true;
|
||||
}
|
||||
if (!predicateTranslations.sorts[value]) {
|
||||
this.errors.addError(OPERATOR_SORT, {
|
||||
tag: 'operator-sort-invalid',
|
||||
value,
|
||||
});
|
||||
continue;
|
||||
} else {
|
||||
value = {
|
||||
name: predicateTranslations.sorts[value],
|
||||
order: negated ? ORDER_DESCENDING : ORDER_ASCENDING,
|
||||
};
|
||||
}
|
||||
} else if (operator === OPERATOR_STATUS) {
|
||||
if (!predicateTranslations.status[value]) {
|
||||
this.errors.addError(OPERATOR_STATUS, {
|
||||
tag: 'operator-status-invalid',
|
||||
value,
|
||||
});
|
||||
continue;
|
||||
} else {
|
||||
value = predicateTranslations.status[value];
|
||||
}
|
||||
} else if (operator === OPERATOR_HAS) {
|
||||
let negated = false;
|
||||
const m = value.match(reNegatedOperator);
|
||||
if (m) {
|
||||
value = m.groups.operator;
|
||||
negated = true;
|
||||
}
|
||||
if (!predicateTranslations.has[value]) {
|
||||
this.errors.addError(OPERATOR_HAS, {
|
||||
tag: 'operator-has-invalid',
|
||||
value,
|
||||
});
|
||||
continue;
|
||||
} else {
|
||||
value = {
|
||||
field: predicateTranslations.has[value],
|
||||
exists: !negated,
|
||||
};
|
||||
}
|
||||
} else if (operator === OPERATOR_LIMIT) {
|
||||
const limit = parseInt(value, 10);
|
||||
if (isNaN(limit) || limit < 1) {
|
||||
this.errors.addError(OPERATOR_LIMIT, {
|
||||
tag: 'operator-limit-invalid',
|
||||
value,
|
||||
});
|
||||
continue;
|
||||
} else {
|
||||
value = limit;
|
||||
}
|
||||
}
|
||||
|
||||
this.queryParams.addPredicate(operator, value);
|
||||
} else {
|
||||
this.errors.addError(OPERATOR_UNKNOWN, {
|
||||
tag: 'operator-unknown-error',
|
||||
value: op,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
m = queryText.match(reQuotedText);
|
||||
if (!m) {
|
||||
m = queryText.match(reText);
|
||||
if (m) {
|
||||
queryText = queryText.replace(reText, '');
|
||||
}
|
||||
} else {
|
||||
queryText = queryText.replace(reQuotedText, '');
|
||||
}
|
||||
if (m) {
|
||||
text += (text ? ' ' : '') + m.groups.text;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log('text:', text);
|
||||
this.queryParams.text = text;
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('queryParams:', this.queryParams);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ FlowRouter.route('/', {
|
||||
Session.set('currentCard', null);
|
||||
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
EscapeActions.executeAll();
|
||||
|
||||
Utils.manageCustomUI();
|
||||
@@ -35,6 +36,7 @@ FlowRouter.route('/public', {
|
||||
Session.set('currentCard', null);
|
||||
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
EscapeActions.executeAll();
|
||||
|
||||
Utils.manageCustomUI();
|
||||
@@ -59,6 +61,7 @@ FlowRouter.route('/b/:id/:slug', {
|
||||
// want to excape every current actions (filters, etc.)
|
||||
if (previousBoard !== currentBoard) {
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
EscapeActions.executeAll();
|
||||
} else {
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
@@ -115,8 +118,10 @@ FlowRouter.route('/shortcuts', {
|
||||
|
||||
FlowRouter.route('/my-cards', {
|
||||
name: 'my-cards',
|
||||
triggersEnter: [AccountsTemplates.ensureSignedIn],
|
||||
action() {
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
// EscapeActions.executeAll();
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
|
||||
@@ -133,8 +138,10 @@ FlowRouter.route('/my-cards', {
|
||||
|
||||
FlowRouter.route('/due-cards', {
|
||||
name: 'due-cards',
|
||||
triggersEnter: [AccountsTemplates.ensureSignedIn],
|
||||
action() {
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
// EscapeActions.executeAll();
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
|
||||
@@ -151,8 +158,10 @@ FlowRouter.route('/due-cards', {
|
||||
|
||||
FlowRouter.route('/global-search', {
|
||||
name: 'global-search',
|
||||
triggersEnter: [AccountsTemplates.ensureSignedIn],
|
||||
action() {
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
// EscapeActions.executeAll();
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
|
||||
@@ -179,6 +188,7 @@ FlowRouter.route('/broken-cards', {
|
||||
const brokenCardsTemplate = 'brokenCards';
|
||||
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
// EscapeActions.executeAll();
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
|
||||
@@ -205,6 +215,7 @@ FlowRouter.route('/import/:source', {
|
||||
Session.set('importSource', params.source);
|
||||
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
EscapeActions.executeAll();
|
||||
BlazeLayout.render('defaultLayout', {
|
||||
headerBar: 'importHeaderBar',
|
||||
@@ -223,6 +234,7 @@ FlowRouter.route('/setting', {
|
||||
Session.set('currentCard', null);
|
||||
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
EscapeActions.executeAll();
|
||||
},
|
||||
],
|
||||
@@ -245,6 +257,7 @@ FlowRouter.route('/information', {
|
||||
Session.set('currentCard', null);
|
||||
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
EscapeActions.executeAll();
|
||||
},
|
||||
],
|
||||
@@ -266,6 +279,7 @@ FlowRouter.route('/people', {
|
||||
Session.set('currentCard', null);
|
||||
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
EscapeActions.executeAll();
|
||||
},
|
||||
],
|
||||
@@ -277,6 +291,28 @@ FlowRouter.route('/people', {
|
||||
},
|
||||
});
|
||||
|
||||
FlowRouter.route('/admin-reports', {
|
||||
name: 'admin-reports',
|
||||
triggersEnter: [
|
||||
AccountsTemplates.ensureSignedIn,
|
||||
() => {
|
||||
Session.set('currentBoard', null);
|
||||
Session.set('currentList', null);
|
||||
Session.set('currentCard', null);
|
||||
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
EscapeActions.executeAll();
|
||||
},
|
||||
],
|
||||
action() {
|
||||
BlazeLayout.render('defaultLayout', {
|
||||
headerBar: 'settingHeaderBar',
|
||||
content: 'adminReports',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
FlowRouter.notFound = {
|
||||
action() {
|
||||
BlazeLayout.render('defaultLayout', { content: 'notFound' });
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
export const DEFAULT_LIMIT = 25;
|
||||
export const OPERATOR_ASSIGNEE = 'assignees';
|
||||
export const OPERATOR_COMMENT = 'comment';
|
||||
export const OPERATOR_CREATED_AT = 'createdAt';
|
||||
export const OPERATOR_CREATOR = 'userId';
|
||||
export const OPERATOR_DUE = 'dueAt';
|
||||
export const OPERATOR_BOARD = 'board';
|
||||
export const OPERATOR_HAS = 'has';
|
||||
export const OPERATOR_LABEL = 'label';
|
||||
export const OPERATOR_LIMIT = 'limit';
|
||||
export const OPERATOR_LIST = 'list';
|
||||
export const OPERATOR_MEMBER = 'members';
|
||||
export const OPERATOR_MODIFIED_AT = 'modifiedAt';
|
||||
export const OPERATOR_SORT = 'sort';
|
||||
export const OPERATOR_STATUS = 'status';
|
||||
export const OPERATOR_SWIMLANE = 'swimlane';
|
||||
export const OPERATOR_UNKNOWN = 'unknown';
|
||||
export const OPERATOR_USER = 'user';
|
||||
export const ORDER_ASCENDING = 'asc';
|
||||
export const ORDER_DESCENDING = 'des';
|
||||
export const PREDICATE_ALL = 'all';
|
||||
export const PREDICATE_ARCHIVED = 'archived';
|
||||
export const PREDICATE_ASSIGNEES = 'assignees';
|
||||
export const PREDICATE_ATTACHMENT = 'attachment';
|
||||
export const PREDICATE_CHECKLIST = 'checklist';
|
||||
export const PREDICATE_CREATED_AT = 'createdAt';
|
||||
export const PREDICATE_DESCRIPTION = 'description';
|
||||
export const PREDICATE_DUE_AT = 'dueAt';
|
||||
export const PREDICATE_END_AT = 'endAt';
|
||||
export const PREDICATE_ENDED = 'ended';
|
||||
export const PREDICATE_MEMBERS = 'members';
|
||||
export const PREDICATE_MODIFIED_AT = 'modifiedAt';
|
||||
export const PREDICATE_MONTH = 'month';
|
||||
export const PREDICATE_OPEN = 'open';
|
||||
export const PREDICATE_OVERDUE = 'overdue';
|
||||
export const PREDICATE_PRIVATE = 'private';
|
||||
export const PREDICATE_PUBLIC = 'public';
|
||||
export const PREDICATE_QUARTER = 'quarter';
|
||||
export const PREDICATE_START_AT = 'startAt';
|
||||
export const PREDICATE_SYSTEM = 'system';
|
||||
export const PREDICATE_WEEK = 'week';
|
||||
export const PREDICATE_YEAR = 'year';
|
||||
+5
-2
@@ -118,11 +118,11 @@ services:
|
||||
#-------------------------------------------------------------------------------------
|
||||
# ==== MONGODB AND METEOR VERSION ====
|
||||
# a) Quay automatic builds do work work, https://quay.io/wekan/wekan
|
||||
# image: quay.io/wekan/wekan
|
||||
image: quay.io/wekan/wekan
|
||||
# b) Using specific version tag:
|
||||
# image: quay.io/wekan/wekan:v4.52
|
||||
# c) Docker Hub builds do work https://hub.docker.com/r/wekanteam/wekan
|
||||
image: wekanteam/wekan
|
||||
# image: wekanteam/wekan
|
||||
#-------------------------------------------------------------------------------------
|
||||
container_name: wekan-app
|
||||
restart: always
|
||||
@@ -209,6 +209,9 @@ services:
|
||||
# There is Feature Request: Logging date and time of all activity with summary reports,
|
||||
# and requesting reason for changing card to other column https://github.com/wekan/wekan/issues/1598
|
||||
#---------------------------------------------------------------
|
||||
# ==== NUMBER OF SEARCH RESULTS PER PAGE BY DEFAULT ====
|
||||
#- RESULTS_PER_PAGE=20
|
||||
#---------------------------------------------------------------
|
||||
# ==== WEKAN API AND EXPORT BOARD ====
|
||||
# Wekan Export Board works when WITH_API=true.
|
||||
# https://github.com/wekan/wekan/wiki/REST-API
|
||||
|
||||
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+28
-2
@@ -344,6 +344,12 @@
|
||||
"list-label-short-sort": "(M)",
|
||||
"filter": "Filter",
|
||||
"filter-cards": "Karten oder Listen filtern",
|
||||
"filter-dates-label": "Nach Datum filtern",
|
||||
"filter-no-due-date": "Kein Fälligkeitsdatum",
|
||||
"filter-overdue": "Überfällig",
|
||||
"filter-due-today": "Heute fällig",
|
||||
"filter-due-this-week": "Diese Woche fällig",
|
||||
"filter-due-tomorrow": "Morgen fällig",
|
||||
"list-filter-label": "Liste nach Titel filtern",
|
||||
"filter-clear": "Filter entfernen",
|
||||
"filter-labels-label": "Nach Label filtern",
|
||||
@@ -900,6 +906,7 @@
|
||||
"operator-member-abbrev": "m",
|
||||
"operator-assignee": "Zugewiesener",
|
||||
"operator-assignee-abbrev": "a",
|
||||
"operator-creator": "Ersteller",
|
||||
"operator-status": "Status",
|
||||
"operator-due": "Fällig",
|
||||
"operator-created": "erstellt",
|
||||
@@ -946,11 +953,12 @@
|
||||
"globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` – Karten in Swimlanes, auf die das angebene *<title>* passt",
|
||||
"globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` – Karten mit einem Kommentar, das *<text>* enthält.",
|
||||
"globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` – Karten, die ein Label haben, auf das *<color>* oder *<name> passt",
|
||||
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name | color>` – Kurzform von `__operator_label__:<color>` oder `__operator_label__:<name>`",
|
||||
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<Name|Farbe>` – Kurzform für `__operator_label__:<color>` oder `__operator_label__:<name>`",
|
||||
"globalSearch-instructions-operator-user": "`__operator_user__:<username>` – Karten, für die *<username>* ein *Mitglied* oder ein *Zugewiesener* ist",
|
||||
"globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` – Kurzform für `user:<username>`",
|
||||
"globalSearch-instructions-operator-member": "`__operator_member__:<username>` – Karten, von denen *<username>* *Mitglied* ist",
|
||||
"globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` – Karten, denen *<username>* *zugewiesen* ist",
|
||||
"globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` – Karten, die von *<username>* angelegt wurden",
|
||||
"globalSearch-instructions-operator-due": "`__operator_due__:<n>` – Karten, die spätestens in *<n>* Tagen fällig sind. `__operator_due__:__predicate_overdue__` zeigt alle Karten, für die die Fälligkeit überschritten ist.",
|
||||
"globalSearch-instructions-operator-created": "`__operator_created__:<n>` – Karten, die vor maximal *<n>* Tagen angelegt wurden",
|
||||
"globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` – Karten, die vor maximal *<n>* Tagen geändert wurden",
|
||||
@@ -978,7 +986,25 @@
|
||||
"sort-cards": "Sortiere Karten",
|
||||
"cardsSortPopup-title": "Sortiere Karten",
|
||||
"due-date": "Fälligkeitsdatum",
|
||||
"server-error": "Server-Fehler",
|
||||
"server-error-troubleshooting": "Bitte übermitteln Sie den Fehler, den der Server erzeugt hat.\nRufen Sie für eine Snap-Installation auf: `sudo snap logs wekan.wekan`\nRufen Sie für eine Docker-Installation auf: `sudo docker logs wekan-app`",
|
||||
"title-alphabetically": "Überschrift (alphabetisch)",
|
||||
"created-at-newest-first": "Erstelldatum (neueste zuerst)",
|
||||
"created-at-oldest-first": "Erstelldatum (älteste zuerst)"
|
||||
"created-at-oldest-first": "Erstelldatum (älteste zuerst)",
|
||||
"links-heading": "Links",
|
||||
"hide-system-messages-of-all-users": "Alle System-Nachrichten aller Nutzer verbergen",
|
||||
"now-system-messages-of-all-users-are-hidden": "Alle System-Nachrichten aller Nutzer sind nun verborgen",
|
||||
"move-swimlane": "Swimlane verschieben",
|
||||
"moveSwimlanePopup-title": "Swimlane verschieben",
|
||||
"custom-field-stringtemplate": "String-Vorlage",
|
||||
"custom-field-stringtemplate-format": "Format (verwende %{value} als Platzhalter)",
|
||||
"custom-field-stringtemplate-separator": "Trenner (verwende   oder für einen Leerschritt)",
|
||||
"custom-field-stringtemplate-item-placeholder": "Drücke die Eingabetaste, um weitere Einträge hinzuzufügen",
|
||||
"creator": "Ersteller",
|
||||
"filesReportTitle": "Dateien-Bericht",
|
||||
"orphanedFilesReportTitle": "Verwaister Datei-Bericht",
|
||||
"reports": "Berichte",
|
||||
"rulesReportTitle": "Regeln-Bericht",
|
||||
"copy-swimlane": "Kopiere Swimlane",
|
||||
"copySwimlanePopup-title": "Swimlane kopieren"
|
||||
}
|
||||
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+28
-2
@@ -344,6 +344,12 @@
|
||||
"list-label-short-sort": "(M)",
|
||||
"filter": "Filter",
|
||||
"filter-cards": "Filter Cards or Lists",
|
||||
"filter-dates-label": "Filter by date",
|
||||
"filter-no-due-date": "No due date",
|
||||
"filter-overdue": "Overdue",
|
||||
"filter-due-today": "Due today",
|
||||
"filter-due-this-week": "Due this week",
|
||||
"filter-due-tomorrow": "Due tomorrow",
|
||||
"list-filter-label": "Filter List by Title",
|
||||
"filter-clear": "Clear filter",
|
||||
"filter-labels-label": "Filter by label",
|
||||
@@ -901,6 +907,7 @@
|
||||
"operator-member-abbrev": "m",
|
||||
"operator-assignee": "assignee",
|
||||
"operator-assignee-abbrev": "a",
|
||||
"operator-creator": "creator",
|
||||
"operator-status": "status",
|
||||
"operator-due": "due",
|
||||
"operator-created": "created",
|
||||
@@ -947,11 +954,12 @@
|
||||
"globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*",
|
||||
"globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.",
|
||||
"globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>",
|
||||
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name | color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`",
|
||||
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`",
|
||||
"globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*",
|
||||
"globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`",
|
||||
"globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*",
|
||||
"globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*",
|
||||
"globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator",
|
||||
"globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.",
|
||||
"globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less",
|
||||
"globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less",
|
||||
@@ -979,7 +987,25 @@
|
||||
"sort-cards": "Sort Cards",
|
||||
"cardsSortPopup-title": "Sort Cards",
|
||||
"due-date": "Due Date",
|
||||
"server-error": "Server Error",
|
||||
"server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`",
|
||||
"title-alphabetically": "Title (Alphabetically)",
|
||||
"created-at-newest-first": "Created At (Newest First)",
|
||||
"created-at-oldest-first": "Created At (Oldest First)"
|
||||
"created-at-oldest-first": "Created At (Oldest First)",
|
||||
"links-heading": "Links",
|
||||
"hide-system-messages-of-all-users": "Hide system messages of all users",
|
||||
"now-system-messages-of-all-users-are-hidden": "Now system messages of all users are hidden",
|
||||
"move-swimlane": "Move Swimlane",
|
||||
"moveSwimlanePopup-title": "Move Swimlane",
|
||||
"custom-field-stringtemplate": "String Template",
|
||||
"custom-field-stringtemplate-format": "Format (use %{value} as placeholder)",
|
||||
"custom-field-stringtemplate-separator": "Separator (use   or for a space)",
|
||||
"custom-field-stringtemplate-item-placeholder": "Press enter to add more items",
|
||||
"creator": "Creator",
|
||||
"filesReportTitle": "Files Report",
|
||||
"orphanedFilesReportTitle": "Orphaned Files Report",
|
||||
"reports": "Reports",
|
||||
"rulesReportTitle": "Rules Report",
|
||||
"copy-swimlane": "Copy Swimlane",
|
||||
"copySwimlanePopup-title": "Copy Swimlane"
|
||||
}
|
||||
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+27
-1
@@ -344,6 +344,12 @@
|
||||
"list-label-short-sort": "(M)",
|
||||
"filter": "Filtrer",
|
||||
"filter-cards": "Filtrer les cartes ou listes",
|
||||
"filter-dates-label": "Filtrer par date",
|
||||
"filter-no-due-date": "Pas de date d'échéance",
|
||||
"filter-overdue": "Échue",
|
||||
"filter-due-today": "Arrive à échéance aujourd'hui",
|
||||
"filter-due-this-week": "Arrive à échéance cette semaine",
|
||||
"filter-due-tomorrow": "Arrive à échéance demain",
|
||||
"list-filter-label": "Filtrer la liste par titre",
|
||||
"filter-clear": "Supprimer les filtres",
|
||||
"filter-labels-label": "Filtrer par étiquette",
|
||||
@@ -900,6 +906,7 @@
|
||||
"operator-member-abbrev": "m",
|
||||
"operator-assignee": "personne assignée",
|
||||
"operator-assignee-abbrev": "a",
|
||||
"operator-creator": "créateur",
|
||||
"operator-status": "statut",
|
||||
"operator-due": "échéance",
|
||||
"operator-created": "créé",
|
||||
@@ -951,6 +958,7 @@
|
||||
"globalSearch-instructions-operator-at": "`__operator_user_abbrev__nom` - raccourci pour `__operator_user__:<nom>`",
|
||||
"globalSearch-instructions-operator-member": "`__operator_member__:<nom>` - cartes pour lesquelles l'utilisateur *<nom>* est *participant*",
|
||||
"globalSearch-instructions-operator-assignee": "`__operator_assignee__:<nom>` - cartes *assignées* à l'utilisateur *<nom>*",
|
||||
"globalSearch-instructions-operator-creator": "`__operator_creator__:<utilisateur>` - cartes dont le créateur est *<utilisateur>*",
|
||||
"globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cartes qui arrive à échéance dans moins de *<n>* jours à partir d'aujourd'hui.\n`__operator_due__:__predicate_overdue__` liste toutes les cartes ayant passé la date d'échéance.",
|
||||
"globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cartes qui ont été créées il y a *<n>* jours ou moins",
|
||||
"globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cartes qui ont été modifiées il y a *<n>* jours ou moins",
|
||||
@@ -978,7 +986,25 @@
|
||||
"sort-cards": "Trier les cartes",
|
||||
"cardsSortPopup-title": "Trier les cartes",
|
||||
"due-date": "Date d'échéance",
|
||||
"server-error": "Erreur serveur",
|
||||
"server-error-troubleshooting": "Merci de soumettre l'erreur générée par le serveur. Pour une installation snap, lancer `sudo snap logs wekan.wekan`. Pour une installation docker, lancer `sudo docker logs wekan-app`",
|
||||
"title-alphabetically": "Titre (Alphabétiquement)",
|
||||
"created-at-newest-first": "Date de création (Plus récentes en premier)",
|
||||
"created-at-oldest-first": "Date de création (Plus anciennes en premier)"
|
||||
"created-at-oldest-first": "Date de création (Plus anciennes en premier)",
|
||||
"links-heading": "Liens",
|
||||
"hide-system-messages-of-all-users": "Masquer les messages système de tous les utilisateurs",
|
||||
"now-system-messages-of-all-users-are-hidden": "Les messages système de tous les utilisateurs seront dorénavant masqués",
|
||||
"move-swimlane": "Déplacer le couloir",
|
||||
"moveSwimlanePopup-title": "Déplacer le Couloir",
|
||||
"custom-field-stringtemplate": "Modèle de chaîne",
|
||||
"custom-field-stringtemplate-format": "Format (utiliser %{valeur} pour marquer un emplacement)",
|
||||
"custom-field-stringtemplate-separator": "Séparateur (utiliser   ou pour un espace)",
|
||||
"custom-field-stringtemplate-item-placeholder": "Appuyez sur Entrée pour ajouter plus d'éléments",
|
||||
"creator": "Créateur",
|
||||
"filesReportTitle": "Rapports sur les fichiers",
|
||||
"orphanedFilesReportTitle": "Rapports sur les fichiers orphelins",
|
||||
"reports": "Rapports",
|
||||
"rulesReportTitle": "Rapports sur les règles",
|
||||
"copy-swimlane": "Copier le couloir",
|
||||
"copySwimlanePopup-title": "Copie de Couloir"
|
||||
}
|
||||
+1009
-983
File diff suppressed because it is too large.
Load diff
+28
-2
@@ -344,6 +344,12 @@
|
||||
"list-label-short-sort": "(י)",
|
||||
"filter": "מסנן",
|
||||
"filter-cards": "סינון כרטיסים או רשימות",
|
||||
"filter-dates-label": "סינון לפי תאריך",
|
||||
"filter-no-due-date": "אין מועד סיום",
|
||||
"filter-overdue": "מועד הסיום עבר",
|
||||
"filter-due-today": "מועד הסיום הוא היום",
|
||||
"filter-due-this-week": "מועד הסיום הוא השבוע",
|
||||
"filter-due-tomorrow": "מועד הסיום הוא מחר",
|
||||
"list-filter-label": "סינון רשימה לפי כותרת",
|
||||
"filter-clear": "ניקוי המסנן",
|
||||
"filter-labels-label": "סינון לפי תווית",
|
||||
@@ -900,6 +906,7 @@
|
||||
"operator-member-abbrev": "ח",
|
||||
"operator-assignee": "אחראי",
|
||||
"operator-assignee-abbrev": "א",
|
||||
"operator-creator": "יוצר",
|
||||
"operator-status": "מצב",
|
||||
"operator-due": "תפוגה",
|
||||
"operator-created": "נוצר",
|
||||
@@ -946,11 +953,12 @@
|
||||
"globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - כרטיסים במסלולים שעונים על *<title>* שצוין",
|
||||
"globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - כרטיסים עם הערה שמכילים *<text>*.",
|
||||
"globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - כרטיסים שיש להם תווית שתואמת את *<color>* או את *<name>",
|
||||
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name | color>` - קיצור של `__operator_label__:<color>` או `__operator_label__:<name>`",
|
||||
"globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - קיצור של `__operator_label__:<color>` או `__operator_label__:<name>`",
|
||||
"globalSearch-instructions-operator-user": "`__operator_user__:<username>` - כרטיסים שהוקצו אל *<username>* או שהוא *חבר* בהם",
|
||||
"globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - קיצור של `user:<username>`",
|
||||
"globalSearch-instructions-operator-member": "`__operator_member__:<username>` - כרטיסים בהם *<username>* *חבר*",
|
||||
"globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - כרטיסים שהוקצו אל *<username>*",
|
||||
"globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - כרטיסים שנוצרו על ידי *<username>*",
|
||||
"globalSearch-instructions-operator-due": "`__operator_due__:<n>` - כרטיסים שהתוקף שלהם יפוג עד עוד *<n>* ימים מעכשיו. `__operator_due__:__predicate_overdue__ מציג את כל הכרטיסים שתאריך התוקף שלהם עבר.",
|
||||
"globalSearch-instructions-operator-created": "`__operator_created__:<n>` - כרטיסים שנוצרו לפני *<n>* ימים או פחות",
|
||||
"globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - כרטיסים שנערכו לפני *<n>* ימים או פחות",
|
||||
@@ -978,7 +986,25 @@
|
||||
"sort-cards": "מיון כרטיסים",
|
||||
"cardsSortPopup-title": "מיון כרטיסים",
|
||||
"due-date": "מועד סיום",
|
||||
"server-error": "שגיאת שרת",
|
||||
"server-error-troubleshooting": "נא להגיש את השגיאה שיצר השרת.\nבהתקנת snap יש להריץ: `sudo snap logs wekan.wekan`\nבהתקנת Docker יש להריץ: `sudo docker logs wekan-app`",
|
||||
"title-alphabetically": "כותרת (לפי האלפבית)",
|
||||
"created-at-newest-first": "מועד יצירה (החדש ביותר בהתחלה)",
|
||||
"created-at-oldest-first": "מועד יצירה (הישן ביותר בהתחלה)"
|
||||
"created-at-oldest-first": "מועד יצירה (הישן ביותר בהתחלה)",
|
||||
"links-heading": "קישורים",
|
||||
"hide-system-messages-of-all-users": "להסתיר את הודעות המערכת של כל המשתמשים",
|
||||
"now-system-messages-of-all-users-are-hidden": "כעת הודעות המערכת של כל המשתמשים מוסתרות",
|
||||
"move-swimlane": "העברת מסלול",
|
||||
"moveSwimlanePopup-title": "העברת מסלול",
|
||||
"custom-field-stringtemplate": "תבנית מחרוזת",
|
||||
"custom-field-stringtemplate-format": "תבנית (להשתמש ב־%{value} כממלא מקום)",
|
||||
"custom-field-stringtemplate-separator": "מפריד (להשתמש ב־  או ב־ כרווח)",
|
||||
"custom-field-stringtemplate-item-placeholder": "יש ללחוץ על enter כדי להוסיף עוד פריטים",
|
||||
"creator": "יוצר",
|
||||
"filesReportTitle": "דוח קבצים",
|
||||
"orphanedFilesReportTitle": "דוח קבצים יתומים",
|
||||
"reports": "דוחות",
|
||||
"rulesReportTitle": "דוח כללים",
|
||||
"copy-swimlane": "העתקת מסלול",
|
||||
"copySwimlanePopup-title": "העתקת מסלול"
|
||||
}
|
||||
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
+1009
-983
File diff suppressed because it is too large.
Load diff
Loaded 100 of 189 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user