Compare commits

..
2 Commits
Author SHA1 Message Date
Lauri Ojansivu 09a95dfef6 v1.69.2 2018-11-05 15:46:43 +02:00
Lauri Ojansivu ea141f62d7 Update meteor 2018-11-05 15:43:35 +02:00
582 changed files with 7740 additions and 60756 deletions

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@
"browser": true
},
"parserOptions": {
"ecmaVersion": 2017,
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true
-5
View File
@@ -1,10 +1,5 @@
## Issue
Add these issues to elsewhere:
- Snap: https://github.com/wekan/wekan-snap/issues
Other Wekan issues can be added here.
**Server Setup Information**:
* Did you test in newest Wekan?:
+1 -13
View File
@@ -8,6 +8,7 @@ npm-debug.log
.vscode/
.idea/
.build/*
packages/
package-lock.json
**/parts/
**/stage
@@ -16,16 +17,3 @@ package-lock.json
snap/.snapcraft/
.idea
.DS_Store
.DS_Store?
.build*
*.browserify.js.cached
*.browserify.js.map
.build*
versions.json
.versions
.npm
.build*
._*
.Trashes
Thumbs.db
ehthumbs.db
+1
View File
@@ -16,3 +16,4 @@ notices-for-facebook-graph-api-2
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base
+23 -25
View File
@@ -3,17 +3,18 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base@1.2.0
meteor-base@1.4.0
# Build system
ecmascript
ecmascript@0.12.0
stylus@2.513.13
standard-minifier-css@1.3.5
standard-minifier-js@2.2.0
standard-minifier-css@1.5.0
standard-minifier-js@2.4.0
mquandalle:jade
coffeescript@2.3.1_2!
# Polyfills
es5-shim@4.6.15
es5-shim@4.8.0
# Collections
aldeed:collection2
@@ -23,7 +24,7 @@ dburles:collection-helpers
idmontie:migrations
matb33:collection-hooks
matteodem:easy-search
mongo@1.3.1
mongo@1.6.0
mquandalle:collection-mutations
# Account system
@@ -31,17 +32,15 @@ kenton:accounts-sandstorm
service-configuration@1.0.11
useraccounts:unstyled
useraccounts:flow-routing
wekan-ldap
wekan-accounts-cas
wekan-accounts-oidc
salleman:accounts-oidc
# Utilities
check@1.2.5
check@1.3.1
jquery@1.11.10
random@1.0.10
reactive-dict@1.2.0
session@1.1.7
tracker@1.1.3
random@1.1.0
reactive-dict@1.2.1
session@1.1.8
tracker@1.2.0
underscore@1.0.10
3stack:presence
alethes:pages
@@ -55,7 +54,7 @@ mquandalle:autofocus
ongoworks:speakingurl
raix:handlebar-helpers
tap:i18n
http@1.3.0
http@1.4.1
# UI components
blaze
@@ -65,30 +64,29 @@ mousetrap:mousetrap
mquandalle:jquery-textcomplete
mquandalle:jquery-ui-drag-drop-sort
mquandalle:mousetrap-bindglobal
mquandalle:perfect-scrollbar
peerlibrary:blaze-components@=0.15.1
perak:markdown
templates:tabs
verron:autosize
simple:json-routes
rajit:bootstrap3-datepicker
shell-server@0.3.0
shell-server@0.4.0
simple:rest-accounts-password
useraccounts:core
email@1.2.3
horka:swipebox
dynamic-import@0.2.0
dynamic-import@0.5.0
staringatlights:fast-render
mixmax:smart-disconnect
accounts-password@1.5.0
accounts-password@1.5.1
cfs:gridfs
eluck:accounts-lockout
rzymek:fullcalendar
momentjs:moment@2.22.2
browser-policy-framing
browser-policy-framing@1.1.0
mquandalle:moment
msavin:usercache
wekan-scrollbar
mquandalle:perfect-scrollbar
mdg:meteor-apm-agent
meteorhacks:unblock
lucasantoniassi:accounts-lockout
wekan-markdown
wekan:wekan-ldap
wekan:accounts-cas
+1 -1
View File
@@ -1 +1 @@
METEOR@1.6.0.1
METEOR@1.8.1-beta.0
+68 -66
View File
@@ -1,7 +1,7 @@
3stack:presence@1.1.2
accounts-base@1.4.0
accounts-oauth@1.1.15
accounts-password@1.5.0
accounts-base@1.4.3
accounts-oauth@1.1.16
accounts-password@1.5.1
aldeed:collection2@2.10.0
aldeed:collection2-core@1.2.0
aldeed:schema-deny@1.1.0
@@ -11,19 +11,19 @@ alethes:pages@1.8.6
allow-deny@1.1.0
arillo:flow-router-helpers@0.5.2
audit-argument-checks@1.0.7
autoupdate@1.3.12
babel-compiler@6.24.7
babel-runtime@1.1.1
base64@1.0.10
binary-heap@1.0.10
blaze@2.3.2
autoupdate@1.5.0
babel-compiler@7.2.0
babel-runtime@1.3.0
base64@1.0.11
binary-heap@1.0.11
blaze@2.3.3
blaze-tools@1.0.10
boilerplate-generator@1.3.1
boilerplate-generator@1.6.0
browser-policy-common@1.0.11
browser-policy-framing@1.1.0
caching-compiler@1.1.9
caching-compiler@1.2.0
caching-html-compiler@1.1.2
callback-hook@1.0.10
callback-hook@1.1.0
cfs:access-point@0.1.49
cfs:base-package@0.0.30
cfs:collection@0.5.5
@@ -41,37 +41,40 @@ cfs:storage-adapter@0.2.3
cfs:tempstore@0.1.5
cfs:upload-http@0.0.20
cfs:worker@0.1.4
check@1.2.5
check@1.3.1
chuangbo:cookie@1.1.0
coffeescript@1.12.7_3
coffeescript-compiler@1.12.7_3
coffeescript@2.3.1_2
coffeescript-compiler@2.3.1_2
cottz:publish-relations@2.0.8
dburles:collection-helpers@1.1.0
ddp@1.4.0
ddp-client@2.2.0
ddp-common@1.3.0
ddp-client@2.3.3
ddp-common@1.4.0
ddp-rate-limiter@1.0.7
ddp-server@2.1.1
ddp-server@2.2.0
deps@1.0.12
diff-sequence@1.0.7
dynamic-import@0.2.1
ecmascript@0.9.0
ecmascript-runtime@0.5.0
ecmascript-runtime-client@0.5.0
ecmascript-runtime-server@0.5.0
diff-sequence@1.1.0
dynamic-import@0.5.0
ecmascript@0.12.0
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.8.0
ecmascript-runtime-server@0.7.1
ejson@1.1.0
eluck:accounts-lockout@0.9.0
email@1.2.3
es5-shim@4.6.15
es5-shim@4.8.0
fastclick@1.0.13
fetch@0.1.0
fortawesome:fontawesome@4.7.0
geojson-utils@1.0.10
horka:swipebox@1.0.2
hot-code-push@1.0.4
html-tools@1.0.11
htmljs@1.0.11
http@1.3.0
id-map@1.0.9
http@1.4.1
id-map@1.1.0
idmontie:migrations@1.0.3
inter-process-messaging@0.1.0
jquery@1.11.10
kadira:blaze-layout@2.3.0
kadira:dochead@1.5.0
@@ -80,34 +83,33 @@ kenton:accounts-sandstorm@0.7.0
launch-screen@1.1.1
livedata@1.0.18
localstorage@1.2.0
logging@1.1.19
lucasantoniassi:accounts-lockout@1.0.0
logging@1.1.20
matb33:collection-hooks@0.8.4
matteodem:easy-search@1.6.4
mdg:meteor-apm-agent@3.1.2
mdg:validation-error@0.5.1
meteor@1.8.2
meteor-base@1.2.0
meteor@1.9.2
meteor-base@1.4.0
meteor-platform@1.2.6
meteorhacks:aggregate@1.3.0
meteorhacks:collection-utils@1.2.0
meteorhacks:meteorx@1.4.1
meteorhacks:picker@1.0.3
meteorhacks:subs-manager@1.6.4
meteorhacks:unblock@1.1.0
meteorspark:util@0.2.0
minifier-css@1.2.16
minifier-js@2.2.2
minifier-css@1.4.0
minifier-js@2.4.0
minifiers@1.1.8-faster-rebuild.0
minimongo@1.4.3
minimongo@1.4.5
mixmax:smart-disconnect@0.0.4
mobile-status-bar@1.0.14
modules@0.11.0
modules-runtime@0.9.1
modern-browsers@0.1.2
modules@0.13.0
modules-runtime@0.10.2
momentjs:moment@2.22.2
mongo@1.3.1
mongo@1.6.0
mongo-decimal@0.1.0
mongo-dev-server@1.1.0
mongo-id@1.0.6
mongo-id@1.0.7
mongo-livedata@1.0.12
mousetrap:mousetrap@1.4.6_1
mquandalle:autofocus@1.0.0
@@ -121,44 +123,48 @@ mquandalle:mousetrap-bindglobal@0.0.1
mquandalle:perfect-scrollbar@0.6.5_2
msavin:usercache@1.0.0
npm-bcrypt@0.9.3
npm-mongo@2.2.33
oauth@1.2.1
oauth2@1.2.0
npm-mongo@3.1.1
oauth@1.2.3
oauth2@1.2.1
observe-sequence@1.0.16
ongoworks:speakingurl@1.1.0
ordered-dict@1.0.9
ordered-dict@1.1.0
peerlibrary:assert@0.2.5
peerlibrary:base-component@0.16.0
peerlibrary:blaze-components@0.15.1
peerlibrary:computed-field@0.7.0
peerlibrary:computed-field@0.9.0
peerlibrary:reactive-field@0.3.0
promise@0.10.0
perak:markdown@1.0.5
promise@0.11.1
raix:eventemitter@0.1.3
raix:handlebar-helpers@0.2.5
rajit:bootstrap3-datepicker@1.7.1
random@1.0.10
rate-limit@1.0.8
reactive-dict@1.2.0
random@1.1.0
rate-limit@1.0.9
reactive-dict@1.2.1
reactive-var@1.0.11
reload@1.1.11
retry@1.0.9
routepolicy@1.0.12
reload@1.2.0
retry@1.1.0
routepolicy@1.1.0
rzymek:fullcalendar@3.8.0
salleman:accounts-oidc@1.0.9
salleman:oidc@1.0.9
service-configuration@1.0.11
session@1.1.7
session@1.1.8
sha@1.0.9
shell-server@0.3.1
shell-server@0.4.0
simple:authenticate-user-by-token@1.0.1
simple:json-routes@2.1.0
simple:rest-accounts-password@1.1.2
simple:rest-bearer-token-parser@1.0.1
simple:rest-json-error-handler@1.0.1
socket-stream-client@0.2.2
softwarerero:accounts-t9n@1.3.11
spacebars@1.0.15
spacebars-compiler@1.1.3
srp@1.0.10
standard-minifier-css@1.3.5
standard-minifier-js@2.2.3
srp@1.0.12
standard-minifier-css@1.5.0
standard-minifier-js@2.4.0
staringatlights:fast-render@2.16.5
staringatlights:inject-data@2.0.5
stylus@2.513.13
@@ -168,21 +174,17 @@ templating@1.3.2
templating-compiler@1.3.3
templating-runtime@1.3.2
templating-tools@1.1.2
tracker@1.1.3
tracker@1.2.0
ui@1.0.13
underscore@1.0.10
url@1.1.0
url@1.2.0
useraccounts:core@1.14.2
useraccounts:flow-routing@1.14.2
useraccounts:unstyled@1.14.2
verron:autosize@3.0.8
webapp@1.4.0
webapp@1.7.0
webapp-hashing@1.0.9
wekan-accounts-oidc@1.0.10
wekan-markdown@1.0.7
wekan-oidc@1.0.12
wekan-scrollbar@3.1.3
wekan-accounts-cas@0.1.0
wekan-ldap@0.0.2
wekan:accounts-cas@0.1.0
wekan:wekan-ldap@0.0.2
yasaricli:slugify@0.0.7
zimme:active-route@2.3.2
+1 -1
View File
@@ -39,7 +39,7 @@ host = https://www.transifex.com
# tap:i18n requires us to use `-` separator in the language identifiers whereas
# Transifex uses a `_` separator, without an option to customize it on one side
# or the other, so we need to do a Manual mapping.
lang_map = bg_BG:bg, en_GB:en-GB, es_AR:es-AR, el_GR:el, fi_FI:fi, hu_HU:hu, id_ID:id, mn_MN:mn, no:nb, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, zh_CN:zh-CN, zh_TW:zh-TW, zh_HK:zh-HK
lang_map = bg_BG:bg, en_GB:en-GB, es_AR:es-AR, el_GR:el, fi_FI:fi, hu_HU:hu, id_ID:id, mn_MN:mn, no:nb, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, zh_CN:zh-CN, zh_TW:zh-TW
[wekan.application]
file_filter = i18n/<lang>.i18n.json
+16 -1408
View File
File diff suppressed because it is too large. Load diff
+76 -71
View File
@@ -1,12 +1,75 @@
FROM ubuntu:disco
FROM debian:buster-slim
LABEL maintainer="wekan"
# Declare Arguments
ARG NODE_VERSION
ARG METEOR_RELEASE
ARG METEOR_EDGE
ARG USE_EDGE
ARG NPM_VERSION
ARG FIBERS_VERSION
ARG ARCHITECTURE
ARG SRC_PATH
ARG WITH_API
ARG MATOMO_ADDRESS
ARG MATOMO_SITE_ID
ARG MATOMO_DO_NOT_TRACK
ARG MATOMO_WITH_USERNAME
ARG BROWSER_POLICY_ENABLED
ARG TRUSTED_URL
ARG WEBHOOKS_ATTRIBUTES
ARG OAUTH2_ENABLED
ARG OAUTH2_CLIENT_ID
ARG OAUTH2_SECRET
ARG OAUTH2_SERVER_URL
ARG OAUTH2_AUTH_ENDPOINT
ARG OAUTH2_USERINFO_ENDPOINT
ARG OAUTH2_TOKEN_ENDPOINT
ARG LDAP_ENABLE
ARG LDAP_PORT
ARG LDAP_HOST
ARG LDAP_BASEDN
ARG LDAP_LOGIN_FALLBACK
ARG LDAP_RECONNECT
ARG LDAP_TIMEOUT
ARG LDAP_IDLE_TIMEOUT
ARG LDAP_CONNECT_TIMEOUT
ARG LDAP_AUTHENTIFICATION
ARG LDAP_AUTHENTIFICATION_USERDN
ARG LDAP_AUTHENTIFICATION_PASSWORD
ARG LDAP_LOG_ENABLED
ARG LDAP_BACKGROUND_SYNC
ARG LDAP_BACKGROUND_SYNC_INTERVAL
ARG LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED
ARG LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS
ARG LDAP_ENCRYPTION
ARG LDAP_CA_CERT
ARG LDAP_REJECT_UNAUTHORIZED
ARG LDAP_USER_SEARCH_FILTER
ARG LDAP_USER_SEARCH_SCOPE
ARG LDAP_USER_SEARCH_FIELD
ARG LDAP_SEARCH_PAGE_SIZE
ARG LDAP_SEARCH_SIZE_LIMIT
ARG LDAP_GROUP_FILTER_ENABLE
ARG LDAP_GROUP_FILTER_OBJECTCLASS
ARG LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE
ARG LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE
ARG LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT
ARG LDAP_GROUP_FILTER_GROUP_NAME
ARG LDAP_UNIQUE_IDENTIFIER_FIELD
ARG LDAP_UTF8_NAMES_SLUGIFY
ARG LDAP_USERNAME_FIELD
ARG LDAP_MERGE_EXISTING_USERS
ARG LDAP_SYNC_USER_DATA
ARG LDAP_SYNC_USER_DATA_FIELDMAP
ARG LDAP_SYNC_GROUP_ROLES
ARG LDAP_DEFAULT_DOMAIN
# Set the environment variables (defaults where required)
# DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
# ENV BUILD_DEPS="paxctl"
ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential python3 python3-pip git ca-certificates gcc-8" \
DEBUG=false \
NODE_VERSION=v8.16.0 \
ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential python git ca-certificates gcc-7" \
NODE_VERSION=v8.12.0 \
METEOR_RELEASE=1.6.0.1 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
@@ -15,13 +78,6 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential pyth
ARCHITECTURE=linux-x64 \
SRC_PATH=./ \
WITH_API=true \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 \
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 \
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 \
EMAIL_NOTIFICATION_TIMEOUT=30000 \
MATOMO_ADDRESS="" \
MATOMO_SITE_ID="" \
MATOMO_DO_NOT_TRACK=true \
@@ -30,19 +86,12 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential pyth
TRUSTED_URL="" \
WEBHOOKS_ATTRIBUTES="" \
OAUTH2_ENABLED=false \
OAUTH2_LOGIN_STYLE=redirect \
OAUTH2_CLIENT_ID="" \
OAUTH2_SECRET="" \
OAUTH2_SERVER_URL="" \
OAUTH2_AUTH_ENDPOINT="" \
OAUTH2_USERINFO_ENDPOINT="" \
OAUTH2_TOKEN_ENDPOINT="" \
OAUTH2_ID_MAP="" \
OAUTH2_USERNAME_MAP="" \
OAUTH2_FULLNAME_MAP="" \
OAUTH2_ID_TOKEN_WHITELIST_FIELDS="" \
OAUTH2_REQUEST_PERMISSIONS='openid profile email' \
OAUTH2_EMAIL_MAP="" \
LDAP_ENABLE=false \
LDAP_PORT=389 \
LDAP_HOST="" \
@@ -63,7 +112,6 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential pyth
LDAP_ENCRYPTION=false \
LDAP_CA_CERT="" \
LDAP_REJECT_UNAUTHORIZED=false \
LDAP_USER_AUTHENTICATION=false \
LDAP_USER_SEARCH_FILTER="" \
LDAP_USER_SEARCH_SCOPE="" \
LDAP_USER_SEARCH_FIELD="" \
@@ -78,30 +126,11 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential pyth
LDAP_UNIQUE_IDENTIFIER_FIELD="" \
LDAP_UTF8_NAMES_SLUGIFY=true \
LDAP_USERNAME_FIELD="" \
LDAP_FULLNAME_FIELD="" \
LDAP_MERGE_EXISTING_USERS=false \
LDAP_EMAIL_FIELD="" \
LDAP_EMAIL_MATCH_ENABLE=false \
LDAP_EMAIL_MATCH_REQUIRE=false \
LDAP_EMAIL_MATCH_VERIFIED=false \
LDAP_SYNC_USER_DATA=false \
LDAP_SYNC_USER_DATA_FIELDMAP="" \
LDAP_SYNC_GROUP_ROLES="" \
LDAP_DEFAULT_DOMAIN="" \
LDAP_SYNC_ADMIN_STATUS="" \
LDAP_SYNC_ADMIN_GROUPS="" \
HEADER_LOGIN_ID="" \
HEADER_LOGIN_FIRSTNAME="" \
HEADER_LOGIN_LASTNAME="" \
HEADER_LOGIN_EMAIL="" \
LOGOUT_WITH_TIMER=false \
LOGOUT_IN="" \
LOGOUT_ON_HOURS="" \
LOGOUT_ON_MINUTES="" \
CORS="" \
CORS_ALLOW_HEADERS="" \
CORS_EXPOSE_HEADERS="" \
DEFAULT_AUTHENTICATION_METHOD=""
LDAP_DEFAULT_DOMAIN=""
# Copy the app to the image
COPY ${SRC_PATH} /home/wekan/app
@@ -113,7 +142,6 @@ RUN \
\
# OS dependencies
apt-get update -y && apt-get install -y --no-install-recommends ${BUILD_DEPS} && \
pip3 install -U pip setuptools wheel && \
\
# Meteor installer doesn't work with the default tar binary, so using bsdtar while installing.
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
@@ -131,7 +159,7 @@ RUN \
# Also see beginning of wekan/server/authentication.js
# import Fiber from "fibers";
# Fiber.poolSize = 1e9;
# OLD: Download node version 8.12.0 prerelease that has fix included, => Official 8.12.0 has been released
# OLD: Download node version 8.12.0 prerelease that has fix included, => Official 8.12.0 has been released
# Description at https://releases.wekan.team/node.txt
#wget https://releases.wekan.team/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
#echo "1ed54adb8497ad8967075a0b5d03dd5d0a502be43d4a4d84e5af489c613d7795 node-v8.12.0-linux-x64.tar.gz" >> SHASUMS256.txt.asc && \
@@ -197,43 +225,23 @@ RUN \
fi; \
\
# Get additional packages
#mkdir -p /home/wekan/app/packages && \
#chown wekan:wekan --recursive /home/wekan && \
# REPOS BELOW ARE INCLUDED TO WEKAN REPO
#cd /home/wekan/app/packages && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git && \
#gosu wekan:wekan git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git && \
#gosu wekan:wekan mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan-accounts-oidc && \
#gosu wekan:wekan mv meteor-accounts-oidc/packages/switch_oidc wekan-oidc && \
#gosu wekan:wekan rm -rf meteor-accounts-oidc && \
mkdir -p /home/wekan/app/packages && \
chown wekan:wekan --recursive /home/wekan && \
cd /home/wekan/app/packages && \
gosu wekan:wekan git clone --depth 1 -b master git://github.com/wekan/flow-router.git kadira-flow-router && \
gosu wekan:wekan git clone --depth 1 -b master git://github.com/meteor-useraccounts/core.git meteor-useraccounts-core && \
gosu wekan:wekan git clone --depth 1 -b master git://github.com/wekan/meteor-accounts-cas.git && \
gosu wekan:wekan git clone --depth 1 -b master git://github.com/wekan/wekan-ldap.git && \
sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js && \
cd /home/wekan/.meteor && \
gosu wekan:wekan /home/wekan/.meteor/meteor -- help; \
\
# extract the OpenAPI specification
npm install -g api2html@0.3.3 && \
mkdir -p /home/wekan/python && \
chown wekan:wekan --recursive /home/wekan/python && \
cd /home/wekan/python && \
gosu wekan:wekan git clone --depth 1 -b master https://github.com/Kronuz/esprima-python && \
cd /home/wekan/python/esprima-python && \
python3 setup.py install --record files.txt && \
cd /home/wekan/app &&\
gosu wekan:wekan mkdir -p ./public/api && \
gosu wekan:wekan python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml && \
gosu wekan:wekan /opt/nodejs/bin/api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml; \
# Build app
cd /home/wekan/app && \
gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js && \
gosu wekan:wekan /home/wekan/.meteor/meteor npm install && \
gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build && \
cp /home/wekan/app/fix-download-unicode/cfs_access-point.txt /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \
rm /home/wekan/app_build/bundle/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs && \
chown wekan:wekan /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \
#Removed binary version of bcrypt because of security vulnerability that is not fixed yet.
#https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac
@@ -252,13 +260,10 @@ RUN \
# Cleanup
apt-get remove --purge -y ${BUILD_DEPS} && \
apt-get autoremove -y && \
npm uninstall -g api2html &&\
rm -R /var/lib/apt/lists/* && \
rm -R /home/wekan/.meteor && \
rm -R /home/wekan/app && \
rm -R /home/wekan/app_build && \
cat /home/wekan/python/esprima-python/files.txt | xargs rm -R && \
rm -R /home/wekan/python && \
rm /home/wekan/install_meteor.sh
ENV PORT=8080
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014-2019 The Wekan Team
Copyright (c) 2014-2018 The Wekan Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+62 -104
View File
@@ -1,103 +1,4 @@
# Wekan - Open Source kanban
[![Contributors](https://img.shields.io/github/contributors/wekan/wekan.svg "Contributors")](https://github.com/wekan/wekan/graphs/contributors)
[![Docker Repository on Quay](https://quay.io/repository/wekan/wekan/status "Docker Repository on Quay")](https://quay.io/repository/wekan/wekan)
[![Docker Hub container status](https://img.shields.io/docker/build/wekanteam/wekan.svg "Docker Hub container status")](https://hub.docker.com/r/wekanteam/wekan)
[![Docker Hub pulls](https://img.shields.io/docker/pulls/wekanteam/wekan.svg "Docker Hub Pulls")](https://hub.docker.com/r/wekanteam/wekan)
[![Wekan Build Status][travis_badge]][travis_status]
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/02137ecec4e34c5aa303f57637196a93 "Codacy Badge")](https://www.codacy.com/app/xet7/wekan?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=wekan/wekan&amp;utm_campaign=Badge_Grade)
[![Code Climate](https://codeclimate.com/github/wekan/wekan/badges/gpa.svg "Code Climate")](https://codeclimate.com/github/wekan/wekan)
[![Project Dependencies](https://david-dm.org/wekan/wekan.svg "Project Dependencies")](https://david-dm.org/wekan/wekan)
[![Code analysis at Open Hub](https://img.shields.io/badge/code%20analysis-at%20Open%20Hub-brightgreen.svg "Code analysis at Open Hub")](https://www.openhub.net/p/wekan)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwekan%2Fwekan.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_shield)
## [Translate Wekan at Transifex](https://transifex.com/wekan/wekan)
Translations to non-English languages are accepted only at [Transifex](https://transifex.com/wekan/wekan) using webbrowser.
New English strings of new features can be added as PRs to edge branch file wekan/i18n/en.i18n.json .
## [Wekan feature requests and bugs](https://github.com/wekan/wekan/issues)
Please add most of your questions as GitHub issue: [Wekan feature requests and bugs](https://github.com/wekan/wekan/issues).
It's better than at chat where details get lost when chat scrolls up.
## Chat
[![Wekan Chat][vanila_badge]][wekan_chat] - Most Wekan community and developers are here. Works on webbrowser
and PWA app that can be added as icon on Android and bookmark on iOS, used like native app.
[Wekan IRC FAQ](https://github.com/wekan/wekan/wiki/IRC-FAQ)
## FAQ
**NOTE**:
- Please read the [FAQ](https://github.com/wekan/wekan/wiki/FAQ) first
- Please don't feed the trolls and spammers that are mentioned in the FAQ :)
## About Wekan
Wekan is an completely [Open Source][open_source] and [Free software][free_software]
collaborative kanban board application with MIT license.
Whether you’re maintaining a personal todo list, planning your holidays with some friends,
or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool
to keep your things organized. They give you a visual overview of the current state of your project,
and make you productive by allowing you to focus on the few items that matter the most.
Since Wekan is a free software, you don’t have to trust us with your data and can
install Wekan on your own computer or server. In fact we encourage you to do
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 50 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.
- [Integrations][integrations]: Current possible integrations and future plans.
## Requirements
- 64bit: Linux [Snap](https://github.com/wekan/wekan-snap/wiki/Install) or [Sandstorm](https://sandstorm.io) /
[Mac](https://github.com/wekan/wekan/wiki/Mac) / [Windows](https://github.com/wekan/wekan/wiki/Install-Wekan-from-source-on-Windows).
[More Platforms](https://github.com/wekan/wekan/wiki/Platforms). [ARM progress](https://github.com/wekan/wekan/issues/1053#issuecomment-410919264).
- 1 GB RAM minimum free for Wekan. Production server should have minimum total 4 GB RAM.
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/devel/docker-compose.yml): 3 frontend servers,
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.
Wekan on Sandstorm is not usually affected by any Standalone Wekan (Snap/Docker/Source) security issues.
- [Reporting all new bugs immediately](https://github.com/wekan/wekan/issues).
New features and fixes are added to Wekan [many times a day](https://github.com/wekan/wekan/blob/devel/CHANGELOG.md).
- [Backups](https://github.com/wekan/wekan/wiki/Backup) of Wekan database once a day miminum.
Bugs, updates, users deleting list or card, harddrive full, harddrive crash etc can eat your data. There is no undo yet.
Some bug can cause Wekan board to not load at all, requiring manual fixing of database content.
## Roadmap
[Roadmap Milestones](https://github.com/wekan/wekan/milestones)
[Developer Documentation][dev_docs]
- There is many companies and individuals contributing code to Wekan, to add features and bugfixes
[many times a day](https://github.com/wekan/wekan/blob/devel/CHANGELOG.md).
- [Please add Add new Feature Requests and Bug Reports immediately](https://github.com/wekan/wekan/issues).
- [Commercial Support](https://wekan.team).
- [Bounties](https://wekan.team/bounties/index.html).
We also welcome sponsors for features and bugfixes.
By working directly with Wekan you get the benefit of active maintenance and new features added by growing Wekan developer community.
## Demo
[Wekan demo][roadmap_wefork]
## Screenshot
[More screenshots at Features page](https://github.com/wekan/wekan/wiki/Features)
[![Screenshot of Wekan][screenshot_wefork]][roadmap_wefork]
# Wekan
## Stable
@@ -110,6 +11,66 @@ By working directly with Wekan you get the benefit of active maintenance and new
- edge branch. All new fixes and features are added to here first. [Testing Edge](https://github.com/wekan/wekan-snap/wiki/Snap-Developer-Docs).
[![Translate Wekan at Transifex](https://img.shields.io/badge/Translate%20Wekan-at%20Transifex-brightgreen.svg "Freenode IRC")](https://transifex.com/wekan/wekan)
[![Wekan Vanila Chat][vanila_badge]][vanila_chat]
[![IRC #wekan](https://img.shields.io/badge/IRC%20%23wekan-on%20Freenode-brightgreen.svg "Freenode IRC")](http://webchat.freenode.net?channels=%23wekan&uio=d4)
[![Contributors](https://img.shields.io/github/contributors/wekan/wekan.svg "Contributors")](https://github.com/wekan/wekan/graphs/contributors)
[![Docker Repository on Quay](https://quay.io/repository/wekan/wekan/status "Docker Repository on Quay")](https://quay.io/repository/wekan/wekan)
[![Docker Hub container status](https://img.shields.io/docker/build/wekanteam/wekan.svg "Docker Hub container status")](https://hub.docker.com/r/wekanteam/wekan)
[![Docker Hub pulls](https://img.shields.io/docker/pulls/wekanteam/wekan.svg "Docker Hub Pulls")](https://hub.docker.com/r/wekanteam/wekan)
[![Wekan Build Status][travis_badge]][travis_status]
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/02137ecec4e34c5aa303f57637196a93 "Codacy Badge")](https://www.codacy.com/app/xet7/wekan?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=wekan/wekan&amp;utm_campaign=Badge_Grade)
[![Code Climate](https://codeclimate.com/github/wekan/wekan/badges/gpa.svg "Code Climate")](https://codeclimate.com/github/wekan/wekan)
[![Project Dependencies](https://david-dm.org/wekan/wekan.svg "Project Dependencies")](https://david-dm.org/wekan/wekan)
[![Code analysis at Open Hub](https://img.shields.io/badge/code%20analysis-at%20Open%20Hub-brightgreen.svg "Code analysis at Open Hub")](https://www.openhub.net/p/wekan)
**NOTE**:
- Please read the [FAQ](https://github.com/wekan/wekan/wiki/FAQ) first
- Please don't feed the trolls and spammers that are mentioned in the FAQ :)
Wekan is an completely [Open Source][open_source] and [Free software][free_software]
collaborative kanban board application with MIT license.
Whether you’re maintaining a personal todo list, planning your holidays with some friends, or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool to keep your things organized. They give you a visual overview of the current state of your project, and make you productive by allowing you to focus on the few items that matter the most.
Since Wekan is a free software, you don’t have to trust us with your data and can
install Wekan on your own computer or server. In fact we encourage you to do
that by providing one-click installation on various platforms.
- [Features][features]: Wekan has real-time user interface. Not all features are implemented, yet.
- [Platforms][platforms]: Wekan supports many platforms and plan is to add more. This will be the first place to look if you want to **install** it, test out and learn more in depth.
- [Integrations][integrations]: Current possible integrations and future plans.
- [Team](https://github.com/wekan/wekan/wiki/Team): The people who spends their time and make wekan into what it is right now.
## Roadmap
[Roadmap](https://github.com/wekan/wekan/wiki/Roadmap)
Upcoming Wekan App Development Platform will make possible many use cases. If you don't find your feature or integration in
GitHub issues and [Features][features] or [Integrations][integrations] page at wiki, please add them.
We are very welcoming to new developers and teams to submit new pull requests to devel branch to make this Wekan App Development Platform possible faster. Please see [Developer Documentation][dev_docs] to get started.
We also welcome sponsors for features and bugfixes. By working directly with Wekan you get the benefit of active maintenance and new features added by growing Wekan developer community.
Actual work happens at [Wekan GitHub issues][wekan_issues].
See [Development links on Wekan wiki](https://github.com/wekan/wekan/wiki#Development) bottom of the page for more info.
If you want to know what is going on exactly this moment, you can check out the [project page](https://github.com/wekan/wekan/projects/2).
## Demo
[Wekan demo][roadmap_wefork]
## Screenshot
[More screenshots at Features page](https://github.com/wekan/wekan/wiki/Features)
[![Screenshot of Wekan][screenshot_wefork]][roadmap_wefork]
## License
Wekan is released under the very permissive [MIT license](LICENSE), and made
@@ -133,7 +94,4 @@ with [Meteor](https://www.meteor.com).
[open_source]: https://en.wikipedia.org/wiki/Open-source_software
[free_software]: https://en.wikipedia.org/wiki/Free_software
[vanila_badge]: https://vanila.io/img/join-chat-button2.png
[wekan_chat]: https://community.vanila.io/wekan
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwekan%2Fwekan.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_large)
[vanila_chat]: https://chat.vanila.io/channel/wekan
-9
View File
@@ -1,9 +0,0 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v2.83.0"
files:
userUploads:
- README.md
userScripts:
build: stacksmith/user-scripts/build.sh
boot: stacksmith/user-scripts/boot.sh
run: stacksmith/user-scripts/run.sh
+1 -14
View File
@@ -99,9 +99,6 @@ template(name="boardActivities")
else
| {{{_ 'activity-added' memberLink cardLink}}}.
if($eq activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
if($eq activityType 'moveCard')
| {{{_ 'activity-moved' cardLink oldList.title list.title}}}.
@@ -117,12 +114,6 @@ template(name="boardActivities")
if($eq activityType 'removedLabel')
| {{{_ 'activity-removed-label' lastLabel cardLink}}}.
if($eq activityType 'setCustomField')
| {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}.
if($eq activityType 'unsetCustomField')
| {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}.
if($eq activityType 'unjoinMember')
if($eq user._id member._id)
| {{{_ 'activity-unjoined' cardLink}}}.
@@ -138,7 +129,7 @@ template(name="cardActivities")
p.activity-desc
+memberName(user=user)
if($eq activityType 'createCard')
| {{_ 'activity-added' cardLabel listName}}.
| {{_ 'activity-added' cardLabel list.title}}.
if($eq activityType 'importCard')
| {{{_ 'activity-imported' cardLabel list.title sourceLink}}}.
if($eq activityType 'joinMember')
@@ -179,10 +170,6 @@ template(name="cardActivities")
| {{_ 'activity-sent' cardLabel boardLabel}}.
if($eq activityType 'moveCard')
| {{_ 'activity-moved' cardLabel oldList.title list.title}}.
if($eq activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
if($eq activityType 'addAttachment')
| {{{_ 'activity-attached' attachmentLink cardLabel}}}.
if attachment.isImage
@@ -74,8 +74,6 @@ BlazeComponent.extendComponent({
lastLabel(){
const lastLabelId = this.currentData().labelId;
if (!lastLabelId)
return null;
const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(lastLabelId);
if(lastLabel.name === undefined || lastLabel.name === ''){
return lastLabel.color;
@@ -84,28 +82,6 @@ BlazeComponent.extendComponent({
}
},
lastCustomField(){
const lastCustomField = CustomFields.findOne(this.currentData().customFieldId);
if (!lastCustomField)
return null;
return lastCustomField.name;
},
lastCustomFieldValue(){
const lastCustomField = CustomFields.findOne(this.currentData().customFieldId);
if (!lastCustomField)
return null;
const value = this.currentData().value;
if (lastCustomField.settings.dropdownItems && lastCustomField.settings.dropdownItems.length > 0) {
const dropDownValue = _.find(lastCustomField.settings.dropdownItems, (item) => {
return item._id === value;
});
if (dropDownValue)
return dropDownValue.name;
}
return value;
},
listLabel() {
return this.currentData().list().title;
},
@@ -141,8 +117,6 @@ BlazeComponent.extendComponent({
customField() {
const customField = this.currentData().customField();
if (!customField)
return null;
return customField.name;
},
+6
View File
@@ -1,3 +1,9 @@
Template.boardListHeaderBar.events({
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
},
});
BlazeComponent.extendComponent({
onCreated() {
this.subscribe('archivedBoards');
+8 -14
View File
@@ -20,22 +20,16 @@ template(name="boardBody")
class="{{#if draggingActive.get}}is-dragging-active{{/if}}")
if showOverlay.get
.board-overlay
if currentBoard.isTemplatesBoard
if isViewSwimlanes
each currentBoard.swimlanes
+swimlane(this)
else if isViewSwimlanes
each currentBoard.swimlanes
+swimlane(this)
else if isViewLists
+listsGroup(currentBoard)
else if isViewCalendar
if isViewLists
+listsGroup
if isViewCalendar
+calendarView
else
+listsGroup(currentBoard)
template(name="calendarView")
if isViewCalendar
.calendar-view.swimlane
if currentCard
+cardDetails(currentCard)
+fullcalendar(calendarOptions)
.calendar-view.swimlane
if currentCard
+cardDetails(currentCard)
+fullcalendar(calendarOptions)
+9 -113
View File
@@ -1,6 +1,5 @@
const subManager = new SubsManager();
const { calculateIndex, enableClickOnTouch } = Utils;
const swimlaneWhileSortingHeight = 150;
BlazeComponent.extendComponent({
onCreated() {
@@ -14,7 +13,7 @@ BlazeComponent.extendComponent({
const currentBoardId = Session.get('currentBoard');
if (!currentBoardId)
return;
const handle = subManager.subscribe('board', currentBoardId, false);
const handle = subManager.subscribe('board', currentBoardId);
Tracker.nonreactive(() => {
Tracker.autorun(() => {
this.isBoardReady.set(handle.ready());
@@ -36,37 +35,6 @@ BlazeComponent.extendComponent({
this._isDragging = false;
// Used to set the overlay
this.mouseHasEnterCardDetails = false;
// fix swimlanes sort field if there are null values
const currentBoardData = Boards.findOne(Session.get('currentBoard'));
const nullSortSwimlanes = currentBoardData.nullSortSwimlanes();
if (nullSortSwimlanes.count() > 0) {
const swimlanes = currentBoardData.swimlanes();
let count = 0;
swimlanes.forEach((s) => {
Swimlanes.update(s._id, {
$set: {
sort: count,
},
});
count += 1;
});
}
// fix lists sort field if there are null values
const nullSortLists = currentBoardData.nullSortLists();
if (nullSortLists.count() > 0) {
const lists = currentBoardData.lists();
let count = 0;
lists.forEach((l) => {
Lists.update(l._id, {
$set: {
sort: count,
},
});
count += 1;
});
}
},
onRendered() {
const boardComponent = this;
@@ -75,64 +43,21 @@ BlazeComponent.extendComponent({
$swimlanesDom.sortable({
tolerance: 'pointer',
appendTo: '.board-canvas',
helper(evt, item) {
const helper = $(`<div class="swimlane"
style="flex-direction: column;
height: ${swimlaneWhileSortingHeight}px;
width: $(boardComponent.width)px;
overflow: hidden;"/>`);
helper.append(item.clone());
// Also grab the list of lists of cards
const list = item.next();
helper.append(list.clone());
return helper;
},
helper: 'clone',
handle: '.js-swimlane-header',
items: '.swimlane:not(.placeholder)',
items: '.js-swimlane:not(.placeholder)',
placeholder: 'swimlane placeholder',
distance: 7,
start(evt, ui) {
const listDom = ui.placeholder.next('.js-swimlane');
const parentOffset = ui.item.parent().offset();
ui.placeholder.height(ui.helper.height());
EscapeActions.executeUpTo('popup-close');
listDom.addClass('moving-swimlane');
boardComponent.setIsDragging(true);
ui.placeholder.insertAfter(ui.placeholder.next());
boardComponent.origPlaceholderIndex = ui.placeholder.index();
// resize all swimlanes + headers to be a total of 150 px per row
// this could be achieved by setIsDragging(true) but we want immediate
// result
ui.item.siblings('.js-swimlane').css('height', `${swimlaneWhileSortingHeight - 26}px`);
// set the new scroll height after the resize and insertion of
// the placeholder. We want the element under the cursor to stay
// at the same place on the screen
ui.item.parent().get(0).scrollTop = ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY;
},
beforeStop(evt, ui) {
const parentOffset = ui.item.parent().offset();
const siblings = ui.item.siblings('.js-swimlane');
siblings.css('height', '');
// compute the new scroll height after the resize and removal of
// the placeholder
const scrollTop = ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY;
// then reset the original view of the swimlane
siblings.removeClass('moving-swimlane');
// and apply the computed scrollheight
ui.item.parent().get(0).scrollTop = scrollTop;
},
stop(evt, ui) {
// To attribute the new index number, we need to get the DOM element
// of the previous and the following card -- if any.
const prevSwimlaneDom = ui.item.prevAll('.js-swimlane').get(0);
const nextSwimlaneDom = ui.item.nextAll('.js-swimlane').get(0);
const prevSwimlaneDom = ui.item.prev('.js-swimlane').get(0);
const nextSwimlaneDom = ui.item.next('.js-swimlane').get(0);
const sortIndex = calculateIndex(prevSwimlaneDom, nextSwimlaneDom, 1);
$swimlanesDom.sortable('cancel');
@@ -147,30 +72,6 @@ BlazeComponent.extendComponent({
boardComponent.setIsDragging(false);
},
sort(evt, ui) {
// get the mouse position in the sortable
const parentOffset = ui.item.parent().offset();
const cursorY = evt.pageY - parentOffset.top + ui.item.parent().scrollTop();
// compute the intended index of the placeholder (we need to skip the
// slots between the headers and the list of cards)
const newplaceholderIndex = Math.floor(cursorY / swimlaneWhileSortingHeight);
let destPlaceholderIndex = (newplaceholderIndex + 1) * 2;
// if we are scrolling far away from the bottom of the list
if (destPlaceholderIndex >= ui.item.parent().get(0).childElementCount) {
destPlaceholderIndex = ui.item.parent().get(0).childElementCount - 1;
}
// update the placeholder position in the DOM tree
if (destPlaceholderIndex !== ui.placeholder.index()) {
if (destPlaceholderIndex < boardComponent.origPlaceholderIndex) {
ui.placeholder.insertBefore(ui.placeholder.siblings().slice(destPlaceholderIndex - 2, destPlaceholderIndex - 1));
} else {
ui.placeholder.insertAfter(ui.placeholder.siblings().slice(destPlaceholderIndex - 1, destPlaceholderIndex));
}
}
},
});
// ugly touch event hotfix
@@ -191,19 +92,19 @@ BlazeComponent.extendComponent({
isViewSwimlanes() {
const currentUser = Meteor.user();
if (!currentUser) return false;
return ((currentUser.profile || {}).boardView === 'board-view-swimlanes');
return (currentUser.profile.boardView === 'board-view-swimlanes');
},
isViewLists() {
const currentUser = Meteor.user();
if (!currentUser) return true;
return ((currentUser.profile || {}).boardView === 'board-view-lists');
return (currentUser.profile.boardView === 'board-view-lists');
},
isViewCalendar() {
const currentUser = Meteor.user();
if (!currentUser) return false;
return ((currentUser.profile || {}).boardView === 'board-view-cal');
if (!currentUser) return true;
return (currentUser.profile.boardView === 'board-view-cal');
},
openNewListForm() {
@@ -332,9 +233,4 @@ BlazeComponent.extendComponent({
},
};
},
isViewCalendar() {
const currentUser = Meteor.user();
if (!currentUser) return false;
return ((currentUser.profile || {}).boardView === 'board-view-cal');
},
}).register('calendarView');
+1 -1
View File
@@ -50,6 +50,6 @@ position()
display: flex
flex-direction: column
margin: 0
padding: 0 0px 0px 0
padding: 0 40px 0px 0
overflow-x: hidden
overflow-y: auto
+186 -71
View File
@@ -7,69 +7,71 @@ template(name="boardHeaderBar")
.board-header-btns.left
unless isMiniScreen
if currentBoard
if currentUser
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
if showStarCounter
span
= currentBoard.stars
unless isSandstorm
if currentBoard
if currentUser
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
if showStarCounter
span
= currentBoard.stars
a.board-header-btn(
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
title="{{_ currentBoard.permission}}")
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
span {{_ currentBoard.permission}}
a.board-header-btn(
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
title="{{_ currentBoard.permission}}")
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
span {{_ currentBoard.permission}}
a.board-header-btn.js-watch-board(
title="{{_ watchLevel }}")
if $eq watchLevel "watching"
i.fa.fa-eye
if $eq watchLevel "tracking"
i.fa.fa-bell
if $eq watchLevel "muted"
i.fa.fa-bell-slash
span {{_ watchLevel}}
a.board-header-btn.js-watch-board(
title="{{_ watchLevel }}")
if $eq watchLevel "watching"
i.fa.fa-eye
if $eq watchLevel "tracking"
i.fa.fa-bell
if $eq watchLevel "muted"
i.fa.fa-bell-slash
span {{_ watchLevel}}
else
a.board-header-btn.js-log-in(
title="{{_ 'log-in'}}")
i.fa.fa-sign-in
span {{_ 'log-in'}}
else
a.board-header-btn.js-log-in(
title="{{_ 'log-in'}}")
i.fa.fa-sign-in
span {{_ 'log-in'}}
.board-header-btns.right
if currentBoard
if isMiniScreen
if currentUser
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
if showStarCounter
span
= currentBoard.stars
unless isSandstorm
if currentUser
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
if showStarCounter
span
= currentBoard.stars
a.board-header-btn(
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
title="{{_ currentBoard.permission}}")
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
span {{_ currentBoard.permission}}
a.board-header-btn(
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
title="{{_ currentBoard.permission}}")
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
span {{_ currentBoard.permission}}
a.board-header-btn.js-watch-board(
title="{{_ watchLevel }}")
if $eq watchLevel "watching"
i.fa.fa-eye
if $eq watchLevel "tracking"
i.fa.fa-bell
if $eq watchLevel "muted"
i.fa.fa-bell-slash
span {{_ watchLevel}}
a.board-header-btn.js-watch-board(
title="{{_ watchLevel }}")
if $eq watchLevel "watching"
i.fa.fa-eye
if $eq watchLevel "tracking"
i.fa.fa-bell
if $eq watchLevel "muted"
i.fa.fa-bell-slash
span {{_ watchLevel}}
else
a.board-header-btn.js-log-in(
title="{{_ 'log-in'}}")
i.fa.fa-sign-in
span {{_ 'log-in'}}
else
a.board-header-btn.js-log-in(
title="{{_ 'log-in'}}")
i.fa.fa-sign-in
span {{_ 'log-in'}}
if isSandstorm
if currentUser
@@ -85,25 +87,19 @@ template(name="boardHeaderBar")
if Filter.isActive
a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}")
i.fa.fa-times-thin
if currentUser.isAdmin
a.board-header-btn.js-open-rules-view(title="{{_ 'rules'}}")
i.fa.fa-magic
span {{_ 'rules'}}
else if currentUser.isBoardAdmin
a.board-header-btn.js-open-rules-view(title="{{_ 'rules'}}")
i.fa.fa-magic
span {{_ 'rules'}}
a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
i.fa.fa-search
span {{_ 'search'}}
unless currentBoard.isTemplatesBoard
a.board-header-btn.js-toggle-board-view(
title="{{_ 'board-view'}}")
i.fa.fa-th-large
span {{#if currentUser.profile.boardView}}{{_ currentUser.profile.boardView}}{{else}}{{_ 'board-view-lists'}}{{/if}}
a.board-header-btn.js-toggle-board-view(
title="{{_ 'board-view'}}")
i.fa.fa-th-large
span {{_ currentUser.profile.boardView}}
if canModifyBoard
a.board-header-btn.js-multiselection-activate(
@@ -116,8 +112,40 @@ template(name="boardHeaderBar")
i.fa.fa-times-thin
.separator
a.board-header-btn.js-toggle-sidebar
i.fa.fa-navicon
a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}")
i.board-header-btn-icon.fa.fa-navicon
template(name="boardMenuPopup")
ul.pop-over-list
li: a.js-custom-fields {{_ 'custom-fields'}}
li: a.js-open-archives {{_ 'archived-items'}}
if currentUser.isBoardAdmin
li: a.js-change-board-color {{_ 'board-change-color'}}
//-
XXX Language should be handled by sandstorm, but for now display a
language selection link in the board menu. This link is normally present
in the header bar that is not displayed on sandstorm.
if isSandstorm
li: a.js-change-language {{_ 'language'}}
unless isSandstorm
if currentUser.isBoardAdmin
hr
ul.pop-over-list
li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
li: a.js-archive-board {{_ 'archive-board'}}
li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
hr
ul.pop-over-list
li: a.js-subtask-settings {{_ 'subtask-settings'}}
if isSandstorm
hr
ul.pop-over-list
li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
li: a.js-import-board {{_ 'import-board-c'}}
hr
ul.pop-over-list
li: a.js-subtask-settings {{_ 'subtask-settings'}}
template(name="boardVisibilityList")
ul.pop-over-list
@@ -168,6 +196,64 @@ template(name="boardChangeWatchPopup")
i.fa.fa-check
span.sub-name {{_ 'muted-info'}}
template(name="boardChangeColorPopup")
.board-backgrounds-list.clearfix
each backgroundColors
.board-background-select.js-select-background
span.background-box(class="board-color-{{this}}")
if isSelected
i.fa.fa-check
template(name="boardSubtaskSettingsPopup")
form.board-subtask-settings
h3 {{_ 'show-parent-in-minicard'}}
a#prefix-with-full-path.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'prefix-with-full-path'}}is-checked{{/if}}")
.materialCheckBox(class="{{#if $eq presentParentTask 'prefix-with-full-path'}}is-checked{{/if}}")
span {{_ 'prefix-with-full-path'}}
a#prefix-with-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'prefix-with-parent'}}is-checked{{/if}}")
.materialCheckBox(class="{{#if $eq presentParentTask 'prefix-with-parent'}}is-checked{{/if}}")
span {{_ 'prefix-with-parent'}}
a#subtext-with-full-path.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'subtext-with-full-path'}}is-checked{{/if}}")
.materialCheckBox(class="{{#if $eq presentParentTask 'subtext-with-full-path'}}is-checked{{/if}}")
span {{_ 'subtext-with-full-path'}}
a#subtext-with-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'subtext-with-parent'}}is-checked{{/if}}")
.materialCheckBox(class="{{#if $eq presentParentTask 'subtext-with-parent'}}is-checked{{/if}}")
span {{_ 'subtext-with-parent'}}
a#no-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'no-parent'}}is-checked{{/if}}")
.materialCheckBox(class="{{#if $eq presentParentTask 'no-parent'}}is-checked{{/if}}")
span {{_ 'no-parent'}}
div
hr
div.check-div
a.flex.js-field-has-subtasks(class="{{#if allowsSubtasks}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsSubtasks}}is-checked{{/if}}")
span {{_ 'show-subtasks-field'}}
label
| {{_ 'deposit-subtasks-board'}}
select.js-field-deposit-board(disabled="{{#unless allowsSubtasks}}disabled{{/unless}}")
each boards
if isBoardSelected
option(value=_id selected="selected") {{title}}
else
option(value=_id) {{title}}
if isNullBoardSelected
option(value='null' selected="selected") {{_ 'custom-field-dropdown-none'}}
else
option(value='null') {{_ 'custom-field-dropdown-none'}}
div
hr
label
| {{_ 'deposit-subtasks-list'}}
select.js-field-deposit-list(disabled="{{#unless hasLists}}disabled{{/unless}}")
each lists
if isListSelected
option(value=_id selected="selected") {{title}}
else
option(value=_id) {{title}}
template(name="createBoard")
form
label
@@ -189,21 +275,50 @@ template(name="createBoard")
input.primary.wide(type="submit" value="{{_ 'create'}}")
span.quiet
| {{_ 'or'}}
a.js-import-board {{_ 'import'}}
span.quiet
| /
a.js-board-template {{_ 'template'}}
a.js-import-board {{_ 'import-board'}}
template(name="chooseBoardSource")
ul.pop-over-list
li
a(href="{{pathFor '/import/trello'}}") {{_ 'from-trello'}}
li
a(href="{{pathFor '/import/wekan'}}") {{_ 'from-wekan'}}
template(name="boardChangeTitlePopup")
form
label
| {{_ 'title'}}
input.js-board-name(type="text" value=title autofocus dir="auto")
input.js-board-name(type="text" value=title autofocus)
label
| {{_ 'description'}}
textarea.js-board-desc(dir="auto")= description
textarea.js-board-desc= description
input.primary.wide(type="submit" value="{{_ 'rename'}}")
template(name="boardCreateRulePopup")
p {{_ 'close-board-pop'}}
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
template(name="archiveBoardPopup")
p {{_ 'close-board-pop'}}
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
template(name="outgoingWebhooksPopup")
each integrations
form.integration-form
if title
h4 {{title}}
else
h4 {{_ 'no-name'}}
label
| URL
input.js-outgoing-webhooks-url(type="text" name="url" value=url)
input(type="hidden" value=_id name="id")
input.primary.wide(type="submit" value="{{_ 'save'}}")
form.integration-form
h4
| {{_ 'new-outgoing-webhook'}}
label
| URL
input.js-outgoing-webhooks-url(type="text" name="url" autofocus)
input.primary.wide(type="submit" value="{{_ 'save'}}")
+174 -9
View File
@@ -89,19 +89,14 @@ BlazeComponent.extendComponent({
},
'click .js-toggle-board-view'() {
const currentUser = Meteor.user();
if ((currentUser.profile || {}).boardView === 'board-view-swimlanes') {
if (currentUser.profile.boardView === 'board-view-swimlanes') {
currentUser.setBoardView('board-view-cal');
} else if ((currentUser.profile || {}).boardView === 'board-view-lists') {
} else if (currentUser.profile.boardView === 'board-view-lists') {
currentUser.setBoardView('board-view-swimlanes');
} else if ((currentUser.profile || {}).boardView === 'board-view-cal') {
} else if (currentUser.profile.boardView === 'board-view-cal') {
currentUser.setBoardView('board-view-lists');
} else {
currentUser.setBoardView('board-view-swimlanes');
}
},
'click .js-toggle-sidebar'() {
Sidebar.toggle();
},
'click .js-open-filter-view'() {
Sidebar.setView('filter');
},
@@ -140,6 +135,124 @@ Template.boardHeaderBar.helpers({
},
});
BlazeComponent.extendComponent({
backgroundColors() {
return Boards.simpleSchema()._schema.color.allowedValues;
},
isSelected() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
return currentBoard.color === this.currentData().toString();
},
events() {
return [{
'click .js-select-background'(evt) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const newColor = this.currentData().toString();
currentBoard.setColor(newColor);
evt.preventDefault();
},
}];
},
}).register('boardChangeColorPopup');
BlazeComponent.extendComponent({
onCreated() {
this.currentBoard = Boards.findOne(Session.get('currentBoard'));
},
allowsSubtasks() {
return this.currentBoard.allowsSubtasks;
},
isBoardSelected() {
return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
},
isNullBoardSelected() {
return (this.currentBoard.subtasksDefaultBoardId === null) || (this.currentBoard.subtasksDefaultBoardId === undefined);
},
boards() {
return Boards.find({
archived: false,
'members.userId': Meteor.userId(),
}, {
sort: ['title'],
});
},
lists() {
return Lists.find({
boardId: this.currentBoard._id,
archived: false,
}, {
sort: ['title'],
});
},
hasLists() {
return this.lists().count() > 0;
},
isListSelected() {
return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
},
presentParentTask() {
let result = this.currentBoard.presentParentTask;
if ((result === null) || (result === undefined)) {
result = 'no-parent';
}
return result;
},
events() {
return [{
'click .js-field-has-subtasks'(evt) {
evt.preventDefault();
this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks;
this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks);
$('.js-field-has-subtasks .materialCheckBox').toggleClass('is-checked', this.currentBoard.allowsSubtasks);
$('.js-field-has-subtasks').toggleClass('is-checked', this.currentBoard.allowsSubtasks);
$('.js-field-deposit-board').prop('disabled', !this.currentBoard.allowsSubtasks);
},
'change .js-field-deposit-board'(evt) {
let value = evt.target.value;
if (value === 'null') {
value = null;
}
this.currentBoard.setSubtasksDefaultBoardId(value);
evt.preventDefault();
},
'change .js-field-deposit-list'(evt) {
this.currentBoard.setSubtasksDefaultListId(evt.target.value);
evt.preventDefault();
},
'click .js-field-show-parent-in-minicard'(evt) {
const value = evt.target.id || $(evt.target).parent()[0].id || $(evt.target).parent()[0].parent()[0].id;
const options = [
'prefix-with-full-path',
'prefix-with-parent',
'subtext-with-full-path',
'subtext-with-parent',
'no-parent'];
options.forEach(function(element) {
if (element !== value) {
$(`#${element} .materialCheckBox`).toggleClass('is-checked', false);
$(`#${element}`).toggleClass('is-checked', false);
}
});
$(`#${value} .materialCheckBox`).toggleClass('is-checked', true);
$(`#${value}`).toggleClass('is-checked', true);
this.currentBoard.setPresentParentTask(value);
evt.preventDefault();
},
}];
},
}).register('boardSubtaskSettingsPopup');
const CreateBoard = BlazeComponent.extendComponent({
template() {
return 'createBoard';
@@ -191,11 +304,16 @@ const CreateBoard = BlazeComponent.extendComponent({
'click .js-import': Popup.open('boardImportBoard'),
submit: this.onSubmit,
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-board-template': Popup.open('searchElement'),
}];
},
}).register('createBoardPopup');
BlazeComponent.extendComponent({
template() {
return 'chooseBoardSource';
},
}).register('chooseBoardSourcePopup');
(class HeaderBarCreateBoard extends CreateBoard {
onSubmit(evt) {
super.onSubmit(evt);
@@ -245,3 +363,50 @@ BlazeComponent.extendComponent({
}];
},
}).register('boardChangeWatchPopup');
BlazeComponent.extendComponent({
integrations() {
const boardId = Session.get('currentBoard');
return Integrations.find({ boardId: `${boardId}` }).fetch();
},
integration(id) {
const boardId = Session.get('currentBoard');
return Integrations.findOne({ _id: id, boardId: `${boardId}` });
},
events() {
return [{
'submit'(evt) {
evt.preventDefault();
const url = evt.target.url.value;
const boardId = Session.get('currentBoard');
let id = null;
let integration = null;
if (evt.target.id) {
id = evt.target.id.value;
integration = this.integration(id);
if (url) {
Integrations.update(integration._id, {
$set: {
url: `${url}`,
},
});
} else {
Integrations.remove(integration._id);
}
} else if (url) {
Integrations.insert({
userId: Meteor.userId(),
enabled: true,
type: 'outgoing-webhooks',
url: `${url}`,
boardId: `${boardId}`,
activities: ['all'],
});
}
Popup.close();
},
}];
},
}).register('outgoingWebhooksPopup');
+4 -15
View File
@@ -18,25 +18,17 @@ template(name="boardList")
else
a.js-open-board.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name
+viewer
= title
span.board-list-item-name= title
i.fa.js-star-board(
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
title="{{_ 'star-board-title'}}")
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
p.board-list-item-desc= description
template(name="boardListHeaderBar")
h1 {{_ 'my-boards'}}
@@ -44,6 +36,3 @@ template(name="boardListHeaderBar")
a.board-header-btn.js-open-archived-board
i.fa.fa-archive
span {{_ 'archives'}}
a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
i.fa.fa-clone
span {{_ 'templates'}}
+6 -39
View File
@@ -1,20 +1,5 @@
const subManager = new SubsManager();
Template.boardListHeaderBar.events({
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
},
});
Template.boardListHeaderBar.helpers({
templatesBoardId() {
return Meteor.user().getTemplatesBoardId();
},
templatesBoardSlug() {
return Meteor.user().getTemplatesBoardSlug();
},
});
BlazeComponent.extendComponent({
onCreated() {
Meteor.subscribe('setting');
@@ -24,21 +9,23 @@ BlazeComponent.extendComponent({
return Boards.find({
archived: false,
'members.userId': Meteor.userId(),
type: 'board',
}, { sort: ['title'] });
}, {
sort: ['title'],
});
},
isStarred() {
const user = Meteor.user();
return user && user.hasStarred(this.currentData()._id);
},
hasOvertimeCards() {
subManager.subscribe('board', this.currentData()._id, false);
subManager.subscribe('board', this.currentData()._id);
return this.currentData().hasOvertimeCards();
},
hasSpentTimeCards() {
subManager.subscribe('board', this.currentData()._id, false);
subManager.subscribe('board', this.currentData()._id);
return this.currentData().hasSpentTimeCards();
},
@@ -55,26 +42,6 @@ BlazeComponent.extendComponent({
Meteor.user().toggleBoardStar(boardId);
evt.preventDefault();
},
'click .js-clone-board'(evt) {
Meteor.call('cloneBoard',
this.currentData()._id,
Session.get('fromBoard'),
(err, res) => {
if (err) {
this.setError(err.error);
} else {
Session.set('fromBoard', null);
Utils.goBoardId(res);
}
}
);
evt.preventDefault();
},
'click .js-archive-board'(evt) {
const boardId = this.currentData()._id;
Meteor.call('archiveBoard', boardId);
evt.preventDefault();
},
'click .js-accept-invite'() {
const boardId = this.currentData()._id;
Meteor.user().removeInvite(boardId);
-28
View File
@@ -94,41 +94,13 @@ $spaceBetweenTiles = 16px
.is-star-active
color: white
.fa-clone
position: absolute;
bottom: 0
font-size: 14px
height: 18px
line-height: 18px
opacity: 0
right: 0
padding: 9px 9px
transition-duration: .15s
transition-property: color, font-size, background
.fa-archive
position: absolute;
bottom: 0
font-size: 14px
height: 18px
line-height: 18px
opacity: 0
left: 0
padding: 9px 9px
transition-duration: .15s
transition-property: color, font-size, background
li:hover a
&:hover
.fa-star,
.fa-clone,
.fa-archive,
.fa-star-o
color: white
.fa-star,
.fa-clone,
.fa-archive,
.fa-star-o
color: white
opacity: .75
-8
View File
@@ -1,8 +0,0 @@
template(name="miniboard")
.minicard(
class="minicard-{{colorClass}}")
.minicard-title
.handle
.fa.fa-arrows
+viewer
= title
@@ -4,10 +4,9 @@ template(name="cardCustomFieldsPopup")
li.item(class="")
a.name.js-select-field(href="#")
span.full-name
+viewer
= name
if hasCustomField
i.fa.fa-check
i.fa.fa-check
hr
a.quiet-button.full.js-settings
i.fa.fa-cog
@@ -62,13 +61,9 @@ template(name="cardCustomField-dropdown")
select.inline
each items
if($eq data.value this._id)
option(value=_id selected="selected")
+viewer
= name
option(value=_id selected="selected") {{name}}
else
option(value=_id)
+viewer
= name
option(value=_id) {{name}}
.edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
@@ -78,4 +73,4 @@ template(name="cardCustomField-dropdown")
+viewer
= selectedItem
else
| {{_ 'edit'}}
| {{_ 'edit'}}
+32 -40
View File
@@ -1,6 +1,6 @@
template(name="cardDetails")
section.card-details.js-card-details.js-perfect-scrollbar: .card-details-canvas
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
.card-details-header
+inlinedForm(classNames="js-card-details-title")
+editCardTitleForm
else
@@ -19,14 +19,16 @@ template(name="cardDetails")
a.js-parent-card(href=linkForCard) {{title}}
// else
{{_ 'top-level-card'}}
if isLinkedCard
h3.linked-card-location
+viewer
| {{getBoardTitle}} > {{getTitle}}
unless isSandstorm
if isLinkedCard
h3.linked-card-location
+viewer
| {{getBoardTitle}} > {{getTitle}}
if getArchived
if isLinkedBoard
p.warning {{_ 'board-archived'}}
unless isSandstorm
p.warning {{_ 'board-archived'}}
else
p.warning {{_ 'card-archived'}}
@@ -77,9 +79,7 @@ template(name="cardDetails")
h3.card-details-item-title {{_ 'labels'}}
a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
each labels
span.card-label(class="card-label-{{color}}" title=name)
+viewer
= name
span.card-label(class="card-label-{{color}}" title=name)= name
if canModifyCard
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
i.fa.fa-plus
@@ -88,8 +88,7 @@ template(name="cardDetails")
each customFieldsWD
.card-details-item.card-details-item-customfield
h3.card-details-item-title
+viewer
= definition.name
= definition.name
+cardCustomField
.card-details-items
@@ -193,27 +192,29 @@ template(name="cardDetails")
unless currentUser.isNoComments
if isLoaded.get
if isLinkedCard
+activities(card=this mode="linkedcard")
unless isSandstorm
+activities(card=this mode="linkedcard")
else if isLinkedBoard
+activities(card=this mode="linkedboard")
unless isSandstorm
+activities(card=this mode="linkedboard")
else
+activities(card=this mode="card")
template(name="editCardTitleForm")
textarea.js-edit-card-title(rows='1' autofocus dir="auto")
textarea.js-edit-card-title(rows='1' autofocus)
= getTitle
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-card-title-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
template(name="editCardRequesterForm")
input.js-edit-card-requester(type='text' autofocus value=getRequestedBy dir="auto")
input.js-edit-card-requester(type='text' autofocus value=getRequestedBy)
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-card-requester-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
template(name="editCardAssignerForm")
input.js-edit-card-assigner(type='text' autofocus value=getAssignedBy dir="auto")
input.js-edit-card-assigner(type='text' autofocus value=getAssignedBy)
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-card-assigner-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
@@ -224,16 +225,15 @@ template(name="cardDetailsActionsPopup")
if canModifyCard
hr
ul.pop-over-list
//li: a.js-members {{_ 'card-edit-members'}}
//li: a.js-labels {{_ 'card-edit-labels'}}
//li: a.js-attachments {{_ 'card-edit-attachments'}}
li: a.js-members {{_ 'card-edit-members'}}
li: a.js-labels {{_ 'card-edit-labels'}}
li: a.js-attachments {{_ 'card-edit-attachments'}}
li: a.js-custom-fields {{_ 'card-edit-custom-fields'}}
//li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
//li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
//li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
//li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
li: a.js-spent-time {{_ 'editCardSpentTimePopup-title'}}
li: a.js-set-card-color {{_ 'setCardColorPopup-title'}}
hr
ul.pop-over-list
li: a.js-move-card-to-top {{_ 'moveCardToTop-title'}}
@@ -264,7 +264,7 @@ template(name="copyChecklistToManyCardsPopup")
template(name="boardsAndLists")
label {{_ 'boards'}}:
select.js-select-boards(autofocus)
select.js-select-boards
each boards
if $eq _id currentBoard._id
option(value="{{_id}}" selected) {{_ 'current'}}
@@ -302,48 +302,40 @@ template(name="cardMorePopup")
span {{_ 'link-card'}}
= ' '
i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
input.inline-input(type="text" id="cardURL" readonly value="{{ absoluteUrl }}" autofocus="autofocus")
input.inline-input(type="text" id="cardURL" readonly value="{{ absoluteUrl }}")
button.js-copy-card-link-to-clipboard(class="btn") {{_ 'copy-card-link-to-clipboard'}}
span.clearfix
br
h2 {{_ 'change-card-parent'}}
label {{_ 'source-board'}}:
select.js-field-parent-board
if isTopLevel
option(value="none" selected) {{_ 'custom-field-dropdown-none'}}
else
option(value="none") {{_ 'custom-field-dropdown-none'}}
each boards
if isParentBoard
option(value="{{_id}}" selected) {{title}}
else
option(value="{{_id}}") {{title}}
if isTopLevel
option(value="none" selected) {{_ 'custom-field-dropdown-none'}}
else
option(value="none") {{_ 'custom-field-dropdown-none'}}
label {{_ 'parent-card'}}:
select.js-field-parent-card
if isTopLevel
option(value="none" selected) {{_ 'custom-field-dropdown-none'}}
else
option(value="none") {{_ 'custom-field-dropdown-none'}}
each cards
if isParentCard
option(value="{{_id}}" selected) {{title}}
else
option(value="{{_id}}") {{title}}
option(value="none") {{_ 'custom-field-dropdown-none'}}
br
| {{_ 'added'}}
span.date(title=card.createdAt) {{ moment createdAt 'LLL' }}
a.js-delete(title="{{_ 'card-delete-notice'}}") {{_ 'delete'}}
template(name="setCardColorPopup")
form.edit-label
.palette-colors: each colors
unless $eq color 'white'
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
if(isSelected color)
i.fa.fa-check
button.primary.confirm.js-submit {{_ 'save'}}
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
template(name="cardDeletePopup")
p {{_ "card-delete-pop"}}
+89 -84
View File
@@ -1,11 +1,6 @@
const subManager = new SubsManager();
const { calculateIndexData, enableClickOnTouch } = Utils;
let cardColors;
Meteor.startup(() => {
cardColors = Cards.simpleSchema()._schema.color.allowedValues;
});
BlazeComponent.extendComponent({
mixins() {
return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar];
@@ -78,13 +73,14 @@ BlazeComponent.extendComponent({
//Scroll top
const cardViewStartTop = $cardView.offset().top;
const cardContainerScrollTop = $cardContainer.scrollTop();
let topOffset = false;
if(cardViewStartTop !== 100){
topOffset = cardViewStartTop - 100;
if(cardViewStartTop < 0){
topOffset = 0;
} else if(cardViewStartTop - cardContainerScrollTop > 100) {
topOffset = cardViewStartTop - cardContainerScrollTop - 100;
}
if(topOffset !== false) {
bodyBoardComponent.scrollTop(cardContainerScrollTop + topOffset);
bodyBoardComponent.scrollTop(topOffset);
}
},
@@ -116,7 +112,6 @@ BlazeComponent.extendComponent({
onRendered() {
if (!Utils.isMiniScreen()) {
Meteor.setTimeout(() => {
$('.card-details').mCustomScrollbar({theme:'minimal-dark', setWidth: false, setLeft: 0, scrollbarPosition: 'outside', mouseWheel: true });
this.scrollParentContainer();
}, 500);
}
@@ -341,7 +336,6 @@ Template.cardDetailsActionsPopup.events({
'click .js-move-card': Popup.open('moveCard'),
'click .js-copy-card': Popup.open('copyCard'),
'click .js-copy-checklist-cards': Popup.open('copyChecklistToManyCards'),
'click .js-set-card-color': Popup.open('setCardColor'),
'click .js-move-card-to-top' (evt) {
evt.preventDefault();
const minOrder = _.min(this.list().cards(this.swimlaneId).map((c) => c.sort));
@@ -412,19 +406,17 @@ Template.moveCardPopup.events({
// XXX We should *not* get the currentCard from the global state, but
// instead from a “component” state.
const card = Cards.findOne(Session.get('currentCard'));
const bSelect = $('.js-select-boards')[0];
const boardId = bSelect.options[bSelect.selectedIndex].value;
const lSelect = $('.js-select-lists')[0];
const listId = lSelect.options[lSelect.selectedIndex].value;
const newListId = lSelect.options[lSelect.selectedIndex].value;
const slSelect = $('.js-select-swimlanes')[0];
const swimlaneId = slSelect.options[slSelect.selectedIndex].value;
card.move(boardId, swimlaneId, listId, 0);
card.swimlaneId = slSelect.options[slSelect.selectedIndex].value;
card.move(card.swimlaneId, newListId, 0);
Popup.close();
},
});
BlazeComponent.extendComponent({
onCreated() {
subManager.subscribe('board', Session.get('currentBoard'), false);
subManager.subscribe('board', Session.get('currentBoard'));
this.selectedBoardId = new ReactiveVar(Session.get('currentBoard'));
},
@@ -432,7 +424,6 @@ BlazeComponent.extendComponent({
const boards = Boards.find({
archived: false,
'members.userId': Meteor.userId(),
_id: {$ne: Meteor.user().getTemplatesBoardId()},
}, {
sort: ['title'],
});
@@ -453,21 +444,38 @@ BlazeComponent.extendComponent({
return [{
'change .js-select-boards'(evt) {
this.selectedBoardId.set($(evt.currentTarget).val());
subManager.subscribe('board', this.selectedBoardId.get(), false);
subManager.subscribe('board', this.selectedBoardId.get());
},
}];
},
}).register('boardsAndLists');
function cloneCheckList(_id, checklist) {
'use strict';
const checklistId = checklist._id;
checklist.cardId = _id;
checklist._id = null;
const newChecklistId = Checklists.insert(checklist);
ChecklistItems.find({checklistId}).forEach(function(item) {
item._id = null;
item.checklistId = newChecklistId;
item.cardId = _id;
ChecklistItems.insert(item);
});
}
Template.copyCardPopup.events({
'click .js-done'() {
const card = Cards.findOne(Session.get('currentCard'));
const oldId = card._id;
card._id = null;
const lSelect = $('.js-select-lists')[0];
listId = lSelect.options[lSelect.selectedIndex].value;
card.listId = lSelect.options[lSelect.selectedIndex].value;
const slSelect = $('.js-select-swimlanes')[0];
const swimlaneId = slSelect.options[slSelect.selectedIndex].value;
card.swimlaneId = slSelect.options[slSelect.selectedIndex].value;
const bSelect = $('.js-select-boards')[0];
const boardId = bSelect.options[bSelect.selectedIndex].value;
card.boardId = bSelect.options[bSelect.selectedIndex].value;
const textarea = $('#copy-card-title');
const title = textarea.val().trim();
// insert new card to the bottom of new list
@@ -476,13 +484,38 @@ Template.copyCardPopup.events({
if (title) {
card.title = title;
card.coverId = '';
const _id = card.copy(boardId, swimlaneId, listId);
const _id = Cards.insert(card);
// In case the filter is active we need to add the newly inserted card in
// the list of exceptions -- cards that are not filtered. Otherwise the
// card will disappear instantly.
// See https://github.com/wekan/wekan/issues/80
Filter.addException(_id);
// copy checklists
let cursor = Checklists.find({cardId: oldId});
cursor.forEach(function() {
cloneCheckList(_id, arguments[0]);
});
// copy subtasks
cursor = Cards.find({parentId: oldId});
cursor.forEach(function() {
'use strict';
const subtask = arguments[0];
subtask.parentId = _id;
subtask._id = null;
/* const newSubtaskId = */ Cards.insert(subtask);
});
// copy card comments
cursor = CardComments.find({cardId: oldId});
cursor.forEach(function () {
'use strict';
const comment = arguments[0];
comment.cardId = _id;
comment._id = null;
CardComments.insert(comment);
});
Popup.close();
}
},
@@ -519,8 +552,9 @@ Template.copyChecklistToManyCardsPopup.events({
Filter.addException(_id);
// copy checklists
Checklists.find({cardId: oldId}).forEach((ch) => {
ch.copy(_id);
let cursor = Checklists.find({cardId: oldId});
cursor.forEach(function() {
cloneCheckList(_id, arguments[0]);
});
// copy subtasks
@@ -534,8 +568,13 @@ Template.copyChecklistToManyCardsPopup.events({
});
// copy card comments
CardComments.find({cardId: oldId}).forEach((cmt) => {
cmt.copy(_id);
cursor = CardComments.find({cardId: oldId});
cursor.forEach(function () {
'use strict';
const comment = arguments[0];
comment.cardId = _id;
comment._id = null;
CardComments.insert(comment);
});
}
Popup.close();
@@ -546,48 +585,11 @@ Template.copyChecklistToManyCardsPopup.events({
BlazeComponent.extendComponent({
onCreated() {
this.currentCard = this.currentData();
this.currentColor = new ReactiveVar(this.currentCard.color);
},
colors() {
return cardColors.map((color) => ({ color, name: '' }));
},
isSelected(color) {
if (this.currentColor.get() === null) {
return color === 'white';
}
return this.currentColor.get() === color;
},
events() {
return [{
'click .js-palette-color'() {
this.currentColor.set(this.currentData().color);
},
'click .js-submit' () {
this.currentCard.setColor(this.currentColor.get());
Popup.close();
},
'click .js-remove-color'() {
this.currentCard.setColor(null);
Popup.close();
},
}];
},
}).register('setCardColorPopup');
BlazeComponent.extendComponent({
onCreated() {
this.currentCard = this.currentData();
this.parentBoard = new ReactiveVar(null);
this.parentCard = this.currentCard.parentCard();
if (this.parentCard) {
const list = $('.js-field-parent-card');
list.val(this.parentCard._id);
this.parentBoard.set(this.parentCard.board()._id);
this.parentBoard = this.parentCard.board();
} else {
this.parentBoard.set(null);
this.parentBoard = null;
}
},
@@ -595,9 +597,6 @@ BlazeComponent.extendComponent({
const boards = Boards.find({
archived: false,
'members.userId': Meteor.userId(),
_id: {
$ne: Meteor.user().getTemplatesBoardId(),
},
}, {
sort: ['title'],
});
@@ -605,12 +604,8 @@ BlazeComponent.extendComponent({
},
cards() {
const currentId = Session.get('currentCard');
if (this.parentBoard.get()) {
return Cards.find({
boardId: this.parentBoard.get(),
_id: {$ne: currentId},
});
if (this.parentBoard) {
return this.parentBoard.cards();
} else {
return [];
}
@@ -618,8 +613,8 @@ BlazeComponent.extendComponent({
isParentBoard() {
const board = this.currentData();
if (this.parentBoard.get()) {
return board._id === this.parentBoard.get();
if (this.parentBoard) {
return board._id === this.parentBoard;
}
return false;
},
@@ -633,10 +628,11 @@ BlazeComponent.extendComponent({
},
setParentCardId(cardId) {
if (cardId) {
this.parentCard = Cards.findOne(cardId);
} else {
if (cardId === 'null') {
cardId = null;
this.parentCard = null;
} else {
this.parentCard = Cards.findOne(cardId);
}
this.currentCard.setParentId(cardId);
},
@@ -673,14 +669,23 @@ BlazeComponent.extendComponent({
'change .js-field-parent-board'(evt) {
const selection = $(evt.currentTarget).val();
const list = $('.js-field-parent-card');
list.empty();
if (selection === 'none') {
this.parentBoard.set(null);
this.parentBoard = null;
list.prop('disabled', true);
} else {
subManager.subscribe('board', $(evt.currentTarget).val(), false);
this.parentBoard.set(selection);
this.parentBoard = Boards.findOne(selection);
this.parentBoard.cards().forEach(function(card) {
list.append(
$('<option></option>').val(card._id).html(card.title)
);
});
list.prop('disabled', false);
}
this.setParentCardId(null);
list.append(
`<option value='none' selected='selected'>${TAPi18n.__('custom-field-dropdown-none')}</option>`
);
this.setParentCardId('null');
},
'change .js-field-parent-card'(evt) {
const selection = $(evt.currentTarget).val();
+3 -93
View File
@@ -1,9 +1,9 @@
@import 'nib'
.card-details
padding: 0
padding: 0 20px
flex-shrink: 0
flex-basis: 510px
flex-basis: 470px
will-change: flex-basis
overflow-y: scroll
overflow-x: hidden
@@ -14,16 +14,8 @@
box-shadow: 0 0 7px 0 darken(white, 30%)
transition: flex-basis 0.1s
.mCustomScrollBox
padding-left: 0
.ps-scrollbar-y-rail
pointer-event: all
position: absolute;
.card-details-canvas
width: 470px
padding-left: 20px;
.card-details-header
margin: 0 -20px 5px
@@ -133,92 +125,10 @@ input[type="submit"].attachment-add-link-submit
.card-details-canvas
width: 100%
padding-left: 0px;
.card-details-header
.close-card-details
margin-right: 0px
.card-details-menu
margin-right: 10px
card-details-color(background, color...)
background: background !important
if color
color: color !important //overwrite text for better visibility
.card-details-white
card-details-color(unset, #000) //Black text for better visibility
border: 1px solid #eee
.card-details-green
card-details-color(#3cb500, #ffffff) //White text for better visibility
.card-details-yellow
card-details-color(#fad900, #000) //Black text for better visibility
.card-details-orange
card-details-color(#ff9f19, #000) //Black text for better visibility
.card-details-red
card-details-color(#eb4646, #ffffff) //White text for better visibility
.card-details-purple
card-details-color(#a632db, #ffffff) //White text for better visibility
.card-details-blue
card-details-color(#0079bf, #ffffff) //White text for better visibility
.card-details-pink
card-details-color(#ff78cb, #000) //Black text for better visibility
.card-details-sky
card-details-color(#00c2e0, #ffffff) //White text for better visibility
.card-details-black
card-details-color(#4d4d4d, #ffffff) //White text for better visibility
.card-details-lime
card-details-color(#51e898, #000) //Black text for better visibility
.card-details-silver
card-details-color(#c0c0c0, #000) //Black text for better visibility
.card-details-peachpuff
card-details-color(#ffdab9, #000) //Black text for better visibility
.card-details-crimson
card-details-color(#dc143c, #ffffff) //White text for better visibility
.card-details-plum
card-details-color(#dda0dd, #000) //Black text for better visibility
.card-details-darkgreen
card-details-color(#006400, #ffffff) //White text for better visibility
.card-details-slateblue
card-details-color(#6a5acd, #ffffff) //White text for better visibility
.card-details-magenta
card-details-color(#ff00ff, #ffffff) //White text for better visibility
.card-details-gold
card-details-color(#ffd700, #000) //Black text for better visibility
.card-details-navy
card-details-color(#000080, #ffffff) //White text for better visibility
.card-details-gray
card-details-color(#808080, #ffffff) //White text for better visibility
.card-details-saddlebrown
card-details-color(#8b4513, #ffffff) //White text for better visibility
.card-details-paleturquoise
card-details-color(#afeeee, #000) //Black text for better visibility
.card-details-mistyrose
card-details-color(#ffe4e1, #000) //Black text for better visibility
.card-details-indigo
card-details-color(#4b0082, #ffffff) //White text for better visibility
+1 -1
View File
@@ -56,7 +56,7 @@ template(name="addChecklistItemForm")
a.fa.fa-times-thin.js-close-inlined-form
template(name="editChecklistItemForm")
textarea.js-edit-checklist-item(rows='1' autofocus dir="auto")
textarea.js-edit-checklist-item(rows='1' autofocus)
if $eq type 'item'
= item.title
else
+1 -2
View File
@@ -31,8 +31,7 @@ template(name="cardLabelsPopup")
a.card-label-edit-button.fa.fa-pencil.js-edit-label
span.card-label.card-label-selectable.js-select-label(class="card-label-{{color}}"
class="{{# if isLabelSelected ../_id }}active{{/if}}")
+viewer
= name
= name
if(isLabelSelected ../_id)
i.card-label-selectable-icon.fa.fa-check
if currentUser.isBoardAdmin
+3 -8
View File
@@ -1,8 +1,7 @@
template(name="minicard")
.minicard(
class="{{#if isLinkedCard}}linked-card{{/if}}"
class="{{#if isLinkedBoard}}linked-board{{/if}}"
class="minicard-{{colorClass}}")
class="{{#if isLinkedBoard}}linked-board{{/if}}")
if cover
.minicard-cover(style="background-image: url('{{cover.url}}');")
if labels
@@ -10,8 +9,6 @@ template(name="minicard")
each labels
.minicard-label(class="card-label-{{color}}" title="{{name}}")
.minicard-title
.handle
.fa.fa-arrows
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
.parent-prefix
| {{ parentString ' > ' }}
@@ -56,10 +53,8 @@ template(name="minicard")
each customFieldsWD
if definition.showOnCard
.minicard-custom-field
if definition.showLabelOnMiniCard
.minicard-custom-field-item
+viewer
= definition.name
.minicard-custom-field-item
= definition.name
.minicard-custom-field-item
+viewer
= trueValue
-96
View File
@@ -94,19 +94,6 @@
.minicard-custom-field-item
max-width:50%;
flex-grow:1;
.handle
width: 20px;
height: 20px;
position: absolute;
right: 5px;
top: 5px;
display:none;
@media only screen and (max-width: 1199px) {
display:block;
}
.fa-arrows
font-size:20px;
color: #ccc;
.minicard-title
p:last-child
margin-bottom: 0
@@ -202,86 +189,3 @@
border-top-right-radius: 0
z-index: 15
box-shadow: 0 1px 2px rgba(0,0,0,.15)
minicard-color(background, color...)
background-color: background
if color
color: color //overwrite text for better visibility
&:hover:not(.minicard-composer),
.is-selected &,
.draggable-hover-card &
background: darken(background, 3%)
.draggable-hover-card &
background: darken(background, 7%)
.minicard-green
minicard-color(#3cb500, #ffffff) //White text for better visibility
.minicard-yellow
minicard-color(#fad900)
.minicard-orange
minicard-color(#ff9f19)
.minicard-red
minicard-color(#eb4646, #ffffff) //White text for better visibility
.minicard-purple
minicard-color(#a632db, #ffffff) //White text for better visibility
.minicard-blue
minicard-color(#0079bf, #ffffff) //White text for better visibility
.minicard-pink
minicard-color(#ff78cb)
.minicard-sky
minicard-color(#00c2e0, #ffffff) //White text for better visibility
.minicard-black
minicard-color(#4d4d4d, #ffffff) //White text for better visibility
.minicard-lime
minicard-color(#51e898)
.minicard-silver
minicard-color(#c0c0c0)
.minicard-peachpuff
minicard-color(#ffdab9)
.minicard-crimson
minicard-color(#dc143c, #ffffff) //White text for better visibility
.minicard-plum
minicard-color(#dda0dd)
.minicard-darkgreen
minicard-color(#006400, #ffffff) //White text for better visibility
.minicard-slateblue
minicard-color(#6a5acd, #ffffff) //White text for better visibility
.minicard-magenta
minicard-color(#ff00ff, #ffffff) //White text for better visibility
.minicard-gold
minicard-color(#ffd700)
.minicard-navy
minicard-color(#000080, #ffffff) //White text for better visibility
.minicard-gray
minicard-color(#808080, #ffffff) //White text for better visibility
.minicard-saddlebrown
minicard-color(#8b4513, #ffffff) //White text for better visibility
.minicard-paleturquoise
minicard-color(#afeeee)
.minicard-mistyrose
minicard-color(#ffe4e1)
.minicard-indigo
minicard-color(#4b0082, #ffffff) //White text for better visibility
+3 -3
View File
@@ -50,13 +50,13 @@ template(name="subtaskDeleteDialog")
button.toggle-delete-subtask-dialog(type="button") {{_ 'cancel'}}
template(name="addSubtaskItemForm")
textarea.js-add-subtask-item(rows='1' autofocus dir="auto")
textarea.js-add-subtask-item(rows='1' autofocus)
.edit-controls.clearfix
button.primary.confirm.js-submit-add-subtask-item-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
template(name="editSubtaskItemForm")
textarea.js-edit-subtask-item(rows='1' autofocus dir="auto")
textarea.js-edit-subtask-item(rows='1' autofocus)
if $eq type 'item'
= item.title
else
@@ -76,7 +76,7 @@ template(name="subtasksItems")
else
+subtaskItemDetail(item = item subtasks = subtasks)
if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-subtask-item" subtasks = subtasks dir="auto")
+inlinedForm(autoclose=false classNames="js-add-subtask-item" subtasks = subtasks)
+addSubtaskItemForm
else
a.add-subtask-item.js-open-inlined-form
+2 -2
View File
@@ -12,11 +12,11 @@ template(name="import")
template(name="importTextarea")
form
p: label(for='import-textarea') {{_ instruction}} {{_ 'import-board-instruction-about-errors'}}
p: label(for='import-textarea') {{_ instruction}}
textarea.js-import-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus)
| {{jsonText}}
if isSandstorm
h1.warning {{_ 'import-sandstorm-backup-warning'}}
h1.warning DANGER !!! THIS DESTROYS YOUR IMPORTED DATA, CAUSES BOARD NOT FOUND ERROR WHEN YOU OPEN THIS GRAIN AGAIN https://github.com/wekan/wekan/issues/1430
p.warning {{_ 'import-sandstorm-warning'}}
input.primary.wide(type="submit" value="{{_ 'import'}}")
+3 -16
View File
@@ -26,13 +26,6 @@ BlazeComponent.extendComponent({
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
const $cards = this.$('.js-minicards');
if(window.matchMedia('(max-width: 1199px)').matches) {
$( '.js-minicards' ).sortable({
handle: '.handle',
});
}
$cards.sortable({
connectWith: '.js-minicards:not(.js-list-full)',
tolerance: 'pointer',
@@ -67,13 +60,7 @@ BlazeComponent.extendComponent({
const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1;
const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards);
const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
const currentBoard = Boards.findOne(Session.get('currentBoard'));
let swimlaneId = '';
const boardView = (Meteor.user().profile || {}).boardView;
if (boardView === 'board-view-swimlanes' || currentBoard.isTemplatesBoard())
swimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))._id;
else if ((boardView === 'board-view-lists') || (boardView === 'board-view-cal') || !boardView)
swimlaneId = currentBoard.getDefaultSwimline()._id;
const swimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))._id;
// Normally the jquery-ui sortable library moves the dragged DOM element
// to its new position, which disrupts Blaze reactive updates mechanism
@@ -86,12 +73,12 @@ BlazeComponent.extendComponent({
if (MultiSelection.isActive()) {
Cards.find(MultiSelection.getMongoSelector()).forEach((card, i) => {
card.move(currentBoard._id, swimlaneId, listId, sortIndex.base + i * sortIndex.increment);
card.move(swimlaneId, listId, sortIndex.base + i * sortIndex.increment);
});
} else {
const cardDomElement = ui.item.get(0);
const card = Blaze.getData(cardDomElement);
card.move(currentBoard._id, swimlaneId, listId, sortIndex.base);
card.move(swimlaneId, listId, sortIndex.base);
}
boardComponent.setIsDragging(false);
},
+8 -122
View File
@@ -10,6 +10,7 @@
// transparent, because that won't work during a list drag.
background: darken(white, 13%)
border-left: 1px solid darken(white, 20%)
border-bottom: 1px solid #CCC
padding: 0
float: left
@@ -43,23 +44,11 @@
background: white
margin: -3px 0 8px
.list-header-card-count
height: 35px
.list-header-add
flex: 0 0 auto
padding: 20px 12px 4px
position: relative
min-height: 20px
.list-header
flex: 0 0 auto
padding: 20px 12px 4px
margin: 20px 12px 4px
position: relative
min-height: 20px
background-color: #e4e4e4;
border-bottom: 6px solid #e4e4e4;
&.ui-sortable-handle
cursor: grab
@@ -79,18 +68,16 @@
text-overflow: ellipsis
word-wrap: break-word
.list-header-watch-icon
padding-left: 10px
color: #a6a6a6
.list-header-menu
position: absolute
padding: 27px 19px
padding: 7px
margin-top: 1px
top: -7px
right: -7px
top: -@padding
right: -@padding
.list-header-plus-icon
color: #a6a6a6
@@ -99,10 +86,6 @@
.highlight
color: #ce1414
.cardCount
color: #8c8c8c
font-size: 0.8em
.list-body
flex: 1 1 auto
flex-direction: column
@@ -160,12 +143,9 @@
float: left
@media screen and (max-width: 800px)
.list-header-menu
margin-right: 30px
.mini-list
flex: 0 0 60px
height: auto
height: 60px
width: 100%
border-left: 0px
border-bottom: 1px solid darken(white, 20%)
@@ -174,8 +154,6 @@
display: block
width: 100%
border-left: 0px
&:first-child
margin-left: 0px
&.ui-sortable-helper
flex: 0 0 60px
@@ -194,16 +172,8 @@
border-left: 0px
border-bottom: 1px solid darken(white, 20%)
.list-body
padding: 15px 19px;
.list-header
padding: 0 12px 0px
border-bottom: 0px solid #e4e4e4
height: 60px
margin-top: 10px
display: flex
align-items: center
.list-header-left-icon
display: inline
padding: 7px
@@ -215,10 +185,8 @@
.list-header-menu-icon
position: absolute
padding: 7px
top: 50%
transform: translateY(-50%)
top: -@padding
right: 17px
font-size: 20px
.link-board-wrapper
display: flex
@@ -230,85 +198,3 @@
.search-card-results
max-height: 250px
overflow: hidden
.sk-spinner-list
margin-top: unset !important
list-header-color(background, color...)
border-bottom: 6px solid background
.list-header-white
list-header-color(#ffffff, #4d4d4d) //Black text for better visibility
border: 1px solid #eee
.list-header-green
list-header-color(#3cb500, #ffffff) //White text for better visibility
.list-header-yellow
list-header-color(#fad900, #4d4d4d) //Black text for better visibility
.list-header-orange
list-header-color(#ff9f19, #4d4d4d) //Black text for better visibility
.list-header-red
list-header-color(#eb4646, #ffffff) //White text for better visibility
.list-header-purple
list-header-color(#a632db, #ffffff) //White text for better visibility
.list-header-blue
list-header-color(#0079bf, #ffffff) //White text for better visibility
.list-header-pink
list-header-color(#ff78cb, #4d4d4d) //Black text for better visibility
.list-header-sky
list-header-color(#00c2e0, #ffffff) //White text for better visibility
.list-header-black
list-header-color(#4d4d4d, #ffffff) //White text for better visibility
.list-header-lime
list-header-color(#51e898, #4d4d4d) //Black text for better visibility
.list-header-silver
list-header-color(unset, #4d4d4d) //Black text for better visibility
.list-header-peachpuff
list-header-color(#ffdab9, #4d4d4d) //Black text for better visibility
.list-header-crimson
list-header-color(#dc143c, #ffffff) //White text for better visibility
.list-header-plum
list-header-color(#dda0dd, #4d4d4d) //Black text for better visibility
.list-header-darkgreen
list-header-color(#006400, #ffffff) //White text for better visibility
.list-header-slateblue
list-header-color(#6a5acd, #ffffff) //White text for better visibility
.list-header-magenta
list-header-color(#ff00ff, #ffffff) //White text for better visibility
.list-header-gold
list-header-color(#ffd700, #4d4d4d) //Black text for better visibility
.list-header-navy
list-header-color(#000080, #ffffff) //White text for better visibility
.list-header-gray
list-header-color(#808080, #ffffff) //White text for better visibility
.list-header-saddlebrown
list-header-color(#8b4513, #ffffff) //White text for better visibility
.list-header-paleturquoise
list-header-color(#afeeee, #4d4d4d) //Black text for better visibility
.list-header-mistyrose
list-header-color(#ffe4e1, #4d4d4d) //Black text for better visibility
.list-header-indigo
list-header-color(#4b0082, #ffffff) //White text for better visibility
+28 -63
View File
@@ -4,7 +4,7 @@ template(name="listBody")
if cards.count
+inlinedForm(autoclose=false position="top")
+addCardForm(listId=_id position="top")
each (cardsWithLimit (idOrNull ../../_id))
each (cards (idOrNull ../../_id))
a.minicard-wrapper.js-minicard(href=absoluteUrl
class="{{#if cardIsSelected}}is-selected{{/if}}"
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
@@ -12,9 +12,6 @@ template(name="listBody")
.materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection(
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
+minicard(this)
if (showSpinner (idOrNull ../../_id))
+spinnerList
if canSeeAddCard
+inlinedForm(autoclose=false position="bottom")
+addCardForm(listId=_id position="bottom")
@@ -23,23 +20,13 @@ template(name="listBody")
i.fa.fa-plus
| {{_ 'add-card'}}
template(name="spinnerList")
.sk-spinner.sk-spinner-wave.sk-spinner-list(
class=currentBoard.colorClass
id="showMoreResults")
.sk-rect1
.sk-rect2
.sk-rect3
.sk-rect4
.sk-rect5
template(name="addCardForm")
.minicard.minicard-composer.js-composer
if getLabels
.minicard-labels
each getLabels
.minicard-label(class="card-label-{{color}}" title="{{name}}")
textarea.minicard-composer-textarea.js-card-title(autofocus dir="auto")
textarea.minicard-composer-textarea.js-card-title(autofocus)
if members.get
.minicard-members.js-minicard-composer-members
each members.get
@@ -47,19 +34,14 @@ template(name="addCardForm")
.add-controls.clearfix
button.primary.confirm(type="submit") {{_ 'add'}}
unless currentBoard.isTemplatesBoard
unless currentBoard.isTemplateBoard
span.quiet
| {{_ 'or'}}
a.js-link {{_ 'link'}}
span.quiet
| &nbsp;
| /
a.js-search {{_ 'search'}}
span.quiet
| &nbsp;
| /
a.js-card-template {{_ 'template'}}
unless isSandstorm
span.quiet
| {{_ 'or'}}
a.js-link {{_ 'link'}}
span.quiet
| &nbsp;
| /
a.js-search {{_ 'search'}}
template(name="autocompleteLabelLine")
.minicard-label(class="card-label-{{colorName}}" title=labelName)
@@ -69,9 +51,11 @@ template(name="linkCardPopup")
label {{_ 'boards'}}:
.link-board-wrapper
select.js-select-boards
option(value="")
each boards
option(value="{{_id}}") {{title}}
if $eq _id currentBoard._id
option(value="{{_id}}" selected) {{_ 'current'}}
else
option(value="{{_id}}") {{title}}
input.primary.confirm.js-link-board(type="button" value="{{_ 'link'}}")
label {{_ 'swimlanes'}}:
@@ -90,41 +74,22 @@ template(name="linkCardPopup")
option(value="{{getId}}") {{getTitle}}
.edit-controls.clearfix
input.primary.confirm.js-done(type="button" value="{{_ 'link'}}")
unless isSandstorm
input.primary.confirm.js-done(type="button" value="{{_ 'link'}}")
template(name="searchElementPopup")
form
label
| {{_ 'title'}}
input.js-element-title(type="text" placeholder="{{_ 'title'}}" autofocus required dir="auto")
unless isTemplateSearch
label {{_ 'boards'}}:
.link-board-wrapper
select.js-select-boards
option(value="")
each boards
template(name="searchCardPopup")
label {{_ 'boards'}}:
.link-board-wrapper
select.js-select-boards
each boards
if $eq _id currentBoard._id
option(value="{{_id}}" selected) {{_ 'current'}}
else
option(value="{{_id}}") {{title}}
form.js-search-term-form
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto")
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus)
.list-body.js-perfect-scrollbar.search-card-results
.minicards.clearfix.js-minicards
if isBoardTemplateSearch
each results
a.minicard-wrapper.js-minicard
+miniboard(this)
if isListTemplateSearch
each results
a.minicard-wrapper.js-minicard
+minilist(this)
if isSwimlaneTemplateSearch
each results
a.minicard-wrapper.js-minicard
+miniswimlane(this)
if isCardTemplateSearch
each results
a.minicard-wrapper.js-minicard
+minicard(this)
unless isTemplateSearch
each results
a.minicard-wrapper.js-minicard
+minicard(this)
each results
a.minicard-wrapper.js-minicard
+minicard(this)
+71 -237
View File
@@ -1,12 +1,6 @@
const subManager = new SubsManager();
const InfiniteScrollIter = 10;
BlazeComponent.extendComponent({
onCreated() {
// for infinite scrolling
this.cardlimit = new ReactiveVar(InfiniteScrollIter);
},
mixins() {
return [Mixins.PerfectScrollbar];
},
@@ -45,57 +39,26 @@ BlazeComponent.extendComponent({
const labelIds = formComponent.labels.get();
const customFields = formComponent.customFields.get();
const board = this.data().board();
let linkedId = '';
const boardId = this.data().board();
let swimlaneId = '';
const boardView = (Meteor.user().profile || {}).boardView;
let cardType = 'cardType-card';
if (title) {
if (board.isTemplatesBoard()) {
swimlaneId = this.parentComponent().parentComponent().data()._id; // Always swimlanes view
const swimlane = Swimlanes.findOne(swimlaneId);
// If this is the card templates swimlane, insert a card template
if (swimlane.isCardTemplatesSwimlane())
cardType = 'template-card';
// If this is the board templates swimlane, insert a board template and a linked card
else if (swimlane.isBoardTemplatesSwimlane()) {
linkedId = Boards.insert({
title,
permission: 'private',
type: 'template-board',
});
Swimlanes.insert({
title: TAPi18n.__('default'),
boardId: linkedId,
});
cardType = 'cardType-linkedBoard';
}
} else if (boardView === 'board-view-swimlanes')
swimlaneId = this.parentComponent().parentComponent().data()._id;
else if ((boardView === 'board-view-lists') || (boardView === 'board-view-cal') || !boardView)
swimlaneId = board.getDefaultSwimline()._id;
const boardView = Meteor.user().profile.boardView;
if (boardView === 'board-view-swimlanes')
swimlaneId = this.parentComponent().parentComponent().data()._id;
else if ((boardView === 'board-view-lists') || (boardView === 'board-view-cal'))
swimlaneId = boardId.getDefaultSwimline()._id;
if (title) {
const _id = Cards.insert({
title,
members,
labelIds,
customFields,
listId: this.data()._id,
boardId: board._id,
boardId: boardId._id,
sort: sortIndex,
swimlaneId,
type: cardType,
linkedId,
type: 'cardType-card',
});
// if the displayed card count is less than the total cards in the list,
// we need to increment the displayed card count to prevent the spinner
// to appear
const cardCount = this.data().cards(this.idOrNull(swimlaneId)).count();
if (this.cardlimit.get() < cardCount) {
this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter);
}
// In case the filter is active we need to add the newly inserted card in
// the list of exceptions -- cards that are not filtered. Otherwise the
// card will disappear instantly.
@@ -127,9 +90,9 @@ BlazeComponent.extendComponent({
const methodName = evt.shiftKey ? 'toggleRange' : 'toggle';
MultiSelection[methodName](this.currentData()._id);
// If the card is already selected, we want to de-select it.
// XXX We should probably modify the minicard href attribute instead of
// overwriting the event in case the card is already selected.
// If the card is already selected, we want to de-select it.
// XXX We should probably modify the minicard href attribute instead of
// overwriting the event in case the card is already selected.
} else if (Session.equals('currentCard', this.currentData()._id)) {
evt.stopImmediatePropagation();
evt.preventDefault();
@@ -149,31 +112,11 @@ BlazeComponent.extendComponent({
idOrNull(swimlaneId) {
const currentUser = Meteor.user();
if ((currentUser.profile || {}).boardView === 'board-view-swimlanes' ||
this.data().board().isTemplatesBoard())
if (currentUser.profile.boardView === 'board-view-swimlanes')
return swimlaneId;
return undefined;
},
cardsWithLimit(swimlaneId) {
const limit = this.cardlimit.get();
const selector = {
listId: this.currentData()._id,
archived: false,
};
if (swimlaneId)
selector.swimlaneId = swimlaneId;
return Cards.find(Filter.mongoSelector(selector), {
sort: ['sort'],
limit,
});
},
showSpinner(swimlaneId) {
const list = Template.currentData();
return list.cards(swimlaneId).count() > this.cardlimit.get();
},
canSeeAddCard() {
return !this.reachedWipLimit() && Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
@@ -209,14 +152,6 @@ BlazeComponent.extendComponent({
this.labels = new ReactiveVar([]);
this.members = new ReactiveVar([]);
this.customFields = new ReactiveVar([]);
const currentBoardId = Session.get('currentBoard');
arr = [];
_.forEach(Boards.findOne(currentBoardId).customFields().fetch(), function(field){
if(field.automaticallyOnCard)
arr.push({_id: field._id, value: null});
});
this.customFields.set(arr);
},
reset() {
@@ -234,7 +169,7 @@ BlazeComponent.extendComponent({
pressKey(evt) {
// Pressing Enter should submit the card
if (evt.keyCode === 13 && !evt.shiftKey) {
if (evt.keyCode === 13) {
evt.preventDefault();
const $form = $(evt.currentTarget).closest('form');
// XXX For some reason $form.submit() does not work (it's probably a bug
@@ -243,8 +178,8 @@ BlazeComponent.extendComponent({
// work.
$form.find('button[type=submit]').click();
// Pressing Tab should open the form of the next column, and Maj+Tab go
// in the reverse order
// Pressing Tab should open the form of the next column, and Maj+Tab go
// in the reverse order
} else if (evt.keyCode === 9) {
evt.preventDefault();
const isReverse = evt.shiftKey;
@@ -266,8 +201,7 @@ BlazeComponent.extendComponent({
return [{
keydown: this.pressKey,
'click .js-link': Popup.open('linkCard'),
'click .js-search': Popup.open('searchElement'),
'click .js-card-template': Popup.open('searchElement'),
'click .js-search': Popup.open('searchCard'),
}];
},
@@ -305,7 +239,7 @@ BlazeComponent.extendComponent({
const currentBoard = Boards.findOne(Session.get('currentBoard'));
callback($.map(currentBoard.labels, (label) => {
if (label.name.indexOf(term) > -1 ||
label.color.indexOf(term) > -1) {
label.color.indexOf(term) > -1) {
return label;
}
return null;
@@ -342,13 +276,23 @@ BlazeComponent.extendComponent({
BlazeComponent.extendComponent({
onCreated() {
this.selectedBoardId = new ReactiveVar('');
// Prefetch first non-current board id
const boardId = Boards.findOne({
archived: false,
'members.userId': Meteor.userId(),
_id: {$ne: Session.get('currentBoard')},
}, {
sort: ['title'],
})._id;
// Subscribe to this board
subManager.subscribe('board', boardId);
this.selectedBoardId = new ReactiveVar(boardId);
this.selectedSwimlaneId = new ReactiveVar('');
this.selectedListId = new ReactiveVar('');
this.boardId = Session.get('currentBoard');
// In order to get current board info
subManager.subscribe('board', this.boardId, false);
subManager.subscribe('board', this.boardId);
this.board = Boards.findOne(this.boardId);
// List where to insert card
const list = $(Popup._getTopStack().openerElement).closest('.js-list');
@@ -356,10 +300,10 @@ BlazeComponent.extendComponent({
// Swimlane where to insert card
const swimlane = $(Popup._getTopStack().openerElement).closest('.js-swimlane');
this.swimlaneId = '';
const boardView = (Meteor.user().profile || {}).boardView;
const boardView = Meteor.user().profile.boardView;
if (boardView === 'board-view-swimlanes')
this.swimlaneId = Blaze.getData(swimlane[0])._id;
else if (boardView === 'board-view-lists' || !boardView)
else if (boardView === 'board-view-lists')
this.swimlaneId = Swimlanes.findOne({boardId: this.boardId})._id;
},
@@ -368,7 +312,6 @@ BlazeComponent.extendComponent({
archived: false,
'members.userId': Meteor.userId(),
_id: {$ne: Session.get('currentBoard')},
type: 'board',
}, {
sort: ['title'],
});
@@ -376,7 +319,7 @@ BlazeComponent.extendComponent({
},
swimlanes() {
if (!this.selectedBoardId.get()) {
if (!this.selectedBoardId) {
return [];
}
const swimlanes = Swimlanes.find({boardId: this.selectedBoardId.get()});
@@ -386,7 +329,7 @@ BlazeComponent.extendComponent({
},
lists() {
if (!this.selectedBoardId.get()) {
if (!this.selectedBoardId) {
return [];
}
const lists = Lists.find({boardId: this.selectedBoardId.get()});
@@ -407,14 +350,13 @@ BlazeComponent.extendComponent({
archived: false,
linkedId: {$nin: ownCardsIds},
_id: {$nin: ownCardsIds},
type: {$nin: ['template-card']},
});
},
events() {
return [{
'change .js-select-boards'(evt) {
subManager.subscribe('board', $(evt.currentTarget).val(), false);
subManager.subscribe('board', $(evt.currentTarget).val());
this.selectedBoardId.set($(evt.currentTarget).val());
},
'change .js-select-swimlanes'(evt) {
@@ -475,49 +417,35 @@ BlazeComponent.extendComponent({
},
onCreated() {
this.isCardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-card-template');
this.isListTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-list-template');
this.isSwimlaneTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-open-add-swimlane-menu');
this.isBoardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-add-board');
this.isTemplateSearch = this.isCardTemplateSearch ||
this.isListTemplateSearch ||
this.isSwimlaneTemplateSearch ||
this.isBoardTemplateSearch;
let board = {};
if (this.isTemplateSearch) {
board = Boards.findOne((Meteor.user().profile || {}).templatesBoardId);
} else {
// Prefetch first non-current board id
board = Boards.findOne({
archived: false,
'members.userId': Meteor.userId(),
_id: {$nin: [Session.get('currentBoard'), (Meteor.user().profile || {}).templatesBoardId]},
});
}
// Prefetch first non-current board id
let board = Boards.findOne({
archived: false,
'members.userId': Meteor.userId(),
_id: {$ne: Session.get('currentBoard')},
});
if (!board) {
Popup.close();
return;
}
const boardId = board._id;
// Subscribe to this board
subManager.subscribe('board', boardId, false);
subManager.subscribe('board', boardId);
this.selectedBoardId = new ReactiveVar(boardId);
if (!this.isBoardTemplateSearch) {
this.boardId = Session.get('currentBoard');
// In order to get current board info
subManager.subscribe('board', this.boardId, false);
this.swimlaneId = '';
// Swimlane where to insert card
const swimlane = $(Popup._getTopStack().openerElement).parents('.js-swimlane');
if ((Meteor.user().profile || {}).boardView === 'board-view-swimlanes')
this.swimlaneId = Blaze.getData(swimlane[0])._id;
else
this.swimlaneId = Swimlanes.findOne({boardId: this.boardId})._id;
// List where to insert card
const list = $(Popup._getTopStack().openerElement).closest('.js-list');
this.listId = Blaze.getData(list[0])._id;
}
this.boardId = Session.get('currentBoard');
// In order to get current board info
subManager.subscribe('board', this.boardId);
board = Boards.findOne(this.boardId);
// List where to insert card
const list = $(Popup._getTopStack().openerElement).closest('.js-list');
this.listId = Blaze.getData(list[0])._id;
// Swimlane where to insert card
const swimlane = $(Popup._getTopStack().openerElement).closest('.js-swimlane');
this.swimlaneId = '';
if (board.view === 'board-view-swimlanes')
this.swimlaneId = Blaze.getData(swimlane[0])._id;
else
this.swimlaneId = Swimlanes.findOne({boardId: this.boardId})._id;
this.term = new ReactiveVar('');
},
@@ -526,7 +454,6 @@ BlazeComponent.extendComponent({
archived: false,
'members.userId': Meteor.userId(),
_id: {$ne: Session.get('currentBoard')},
type: 'board',
}, {
sort: ['title'],
});
@@ -538,27 +465,13 @@ BlazeComponent.extendComponent({
return [];
}
const board = Boards.findOne(this.selectedBoardId.get());
if (!this.isTemplateSearch || this.isCardTemplateSearch) {
return board.searchCards(this.term.get(), false);
} else if (this.isListTemplateSearch) {
return board.searchLists(this.term.get());
} else if (this.isSwimlaneTemplateSearch) {
return board.searchSwimlanes(this.term.get());
} else if (this.isBoardTemplateSearch) {
const boards = board.searchBoards(this.term.get());
boards.forEach((board) => {
subManager.subscribe('board', board.linkedId, false);
});
return boards;
} else {
return [];
}
return board.searchCards(this.term.get(), false);
},
events() {
return [{
'change .js-select-boards'(evt) {
subManager.subscribe('board', $(evt.currentTarget).val(), false);
subManager.subscribe('board', $(evt.currentTarget).val());
this.selectedBoardId.set($(evt.currentTarget).val());
},
'submit .js-search-term-form'(evt) {
@@ -566,99 +479,20 @@ BlazeComponent.extendComponent({
this.term.set(evt.target.searchTerm.value);
},
'click .js-minicard'(evt) {
// 0. Common
const title = $('.js-element-title').val().trim();
if (!title)
return;
const element = Blaze.getData(evt.currentTarget);
element.title = title;
let _id = '';
if (!this.isTemplateSearch || this.isCardTemplateSearch) {
// Card insertion
// 1. Common
element.sort = Lists.findOne(this.listId).cards().count();
// 1.A From template
if (this.isTemplateSearch) {
element.type = 'cardType-card';
element.linkedId = '';
_id = element.copy(this.boardId, this.swimlaneId, this.listId);
// 1.B Linked card
} else {
delete element._id;
element.type = 'cardType-linkedCard';
element.linkedId = element.linkedId || element._id;
_id = Cards.insert(element);
}
Filter.addException(_id);
// List insertion
} else if (this.isListTemplateSearch) {
element.sort = Swimlanes.findOne(this.swimlaneId).lists().count();
element.type = 'list';
_id = element.copy(this.boardId, this.swimlaneId);
} else if (this.isSwimlaneTemplateSearch) {
element.sort = Boards.findOne(this.boardId).swimlanes().count();
element.type = 'swimlalne';
_id = element.copy(this.boardId);
} else if (this.isBoardTemplateSearch) {
board = Boards.findOne(element.linkedId);
board.sort = Boards.find({archived: false}).count();
board.type = 'board';
board.title = element.title;
delete board.slug;
_id = board.copy();
}
// LINK CARD
const card = Blaze.getData(evt.currentTarget);
const _id = Cards.insert({
title: card.title, //dummy
listId: this.listId,
swimlaneId: this.swimlaneId,
boardId: this.boardId,
sort: Lists.findOne(this.listId).cards().count(),
type: 'cardType-linkedCard',
linkedId: card.linkedId || card._id,
});
Filter.addException(_id);
Popup.close();
},
}];
},
}).register('searchElementPopup');
BlazeComponent.extendComponent({
onCreated() {
this.cardlimit = this.parentComponent().cardlimit;
this.listId = this.parentComponent().data()._id;
this.swimlaneId = '';
const boardView = (Meteor.user().profile || {}).boardView;
if (boardView === 'board-view-swimlanes')
this.swimlaneId = this.parentComponent().parentComponent().parentComponent().data()._id;
},
onRendered() {
this.spinner = this.find('.sk-spinner-list');
this.container = this.$(this.spinner).parents('.js-perfect-scrollbar')[0];
$(this.container).on(`scroll.spinner_${this.swimlaneId}_${this.listId}`, () => this.updateList());
$(window).on(`resize.spinner_${this.swimlaneId}_${this.listId}`, () => this.updateList());
this.updateList();
},
onDestroyed() {
$(this.container).off(`scroll.spinner_${this.swimlaneId}_${this.listId}`);
$(window).off(`resize.spinner_${this.swimlaneId}_${this.listId}`);
},
updateList() {
if (this.spinnerInView()) {
this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter);
window.requestIdleCallback(() => this.updateList());
}
},
spinnerInView() {
const parentViewHeight = this.container.clientHeight;
const bottomViewPosition = this.container.scrollTop + parentViewHeight;
const threshold = this.spinner.offsetTop;
// spinner deleted
if (!this.spinner.offsetTop) {
return false;
}
return bottomViewPosition > threshold;
},
}).register('spinnerList');
}).register('searchCardPopup');
+3 -19
View File
@@ -1,7 +1,5 @@
template(name="listHeader")
.list-header.js-list-header(
class="{{#if limitToShowCardsCount}}list-header-card-count{{/if}}"
class="{{#if colorClass}}list-header-{{colorClass}}{{/if}}")
.list-header.js-list-header
+inlinedForm
+editListTitleForm
else
@@ -10,8 +8,7 @@ template(name="listHeader")
a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
h2.list-header-name(
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
+viewer
= title
= title
if wipLimit.enabled
|&nbsp;(
span(class="{{#if reachedWipLimit}}highlight{{/if}}") {{cards.count}}
@@ -19,7 +16,7 @@ template(name="listHeader")
if showCardsCountForList cards.count
|&nbsp;
span(class="cardCount") {{cardsCount}} {{_ 'cards-count'}}
p.quiet.small {{cardsCount}} {{_ 'cards-count'}}
if isMiniScreen
if currentList
if isWatching
@@ -52,9 +49,6 @@ template(name="listActionPopup")
li: a.js-toggle-watch-list {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
unless currentUser.isCommentOnly
hr
ul.pop-over-list
li: a.js-set-color-list {{_ 'set-color-list'}}
hr
ul.pop-over-list
if cards.count
li: a.js-select-cards {{_ 'list-select-cards'}}
@@ -117,13 +111,3 @@ template(name="wipLimitErrorPopup")
p {{_ 'wipLimitErrorPopup-dialog-pt1'}}
p {{_ 'wipLimitErrorPopup-dialog-pt2'}}
button.full.js-back-view(type="submit") {{_ 'cancel'}}
template(name="setListColorPopup")
form.edit-label
.palette-colors: each colors
// note: we use the swimlane palette to have more than just the border
span.card-label.palette-color.js-palette-color(class="swimlane-{{color}}")
if(isSelected color)
i.fa.fa-check
button.primary.confirm.js-submit {{_ 'save'}}
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
+1 -38
View File
@@ -1,8 +1,3 @@
let listsColors;
Meteor.startup(() => {
listsColors = Lists.simpleSchema()._schema.color.allowedValues;
});
BlazeComponent.extendComponent({
canSeeAddCard() {
const list = Template.currentData();
@@ -30,7 +25,7 @@ BlazeComponent.extendComponent({
cardsCount() {
const list = Template.currentData();
let swimlaneId = '';
const boardView = (Meteor.user().profile || {}).boardView;
const boardView = Meteor.user().profile.boardView;
if (boardView === 'board-view-swimlanes')
swimlaneId = this.parentComponent().parentComponent().data()._id;
@@ -77,7 +72,6 @@ Template.listActionPopup.helpers({
Template.listActionPopup.events({
'click .js-list-subscribe' () {},
'click .js-set-color-list': Popup.open('setListColor'),
'click .js-select-cards' () {
const cardIds = this.allCards().map((card) => card._id);
MultiSelection.add(cardIds);
@@ -160,34 +154,3 @@ Template.listMorePopup.events({
Utils.goBoardId(this.boardId);
}),
});
BlazeComponent.extendComponent({
onCreated() {
this.currentList = this.currentData();
this.currentColor = new ReactiveVar(this.currentList.color);
},
colors() {
return listsColors.map((color) => ({ color, name: '' }));
},
isSelected(color) {
return this.currentColor.get() === color;
},
events() {
return [{
'click .js-palette-color'() {
this.currentColor.set(this.currentData().color);
},
'click .js-submit' () {
this.currentList.setColor(this.currentColor.get());
Popup.close();
},
'click .js-remove-color'() {
this.currentList.setColor(null);
Popup.close();
},
}];
},
}).register('setListColorPopup');
-8
View File
@@ -1,8 +0,0 @@
template(name="minilist")
.minicard(
class="minicard-{{colorClass}}")
.minicard-title
.handle
.fa.fa-arrows
+viewer
= title
+1 -2
View File
@@ -1,6 +1,5 @@
template(name="editor")
textarea.editor(
dir="auto"
class="{{class}}"
id=id
autofocus=autofocus
@@ -8,7 +7,7 @@ template(name="editor")
+Template.contentBlock
template(name="viewer")
.viewer(dir="auto")
.viewer
+mentions
+markdown
{{> UI.contentBlock }}
+1 -3
View File
@@ -36,10 +36,7 @@ import sanitizeXss from 'xss';
const at = HTML.CharRef({html: '&commat;', str: '@'});
Blaze.Template.registerHelper('mentions', new Template('mentions', function() {
const view = this;
let content = Blaze.toHTML(view.templateContentBlock);
const currentBoard = Boards.findOne(Session.get('currentBoard'));
if (!currentBoard)
return HTML.Raw(sanitizeXss(content));
const knowedUsers = currentBoard.members.map((member) => {
const u = Users.findOne(member.userId);
if(u){
@@ -48,6 +45,7 @@ Blaze.Template.registerHelper('mentions', new Template('mentions', function() {
return member;
});
const mentionRegex = /\B@([\w.]*)/gi;
let content = Blaze.toHTML(view.templateContentBlock);
let currentMention;
while ((currentMention = mentionRegex.exec(content)) !== null) {
+36 -34
View File
@@ -4,38 +4,39 @@ template(name="header")
list all starred boards with a link to go there. This is inspired by the
Reddit "subreddit" bar.
The first link goes to the boards page.
if currentUser
#header-quick-access(class=currentBoard.colorClass)
if isMiniScreen
ul
li
a(href="{{pathFor 'home'}}")
span.fa.fa-home
unless isSandstorm
if currentUser
#header-quick-access(class=currentBoard.colorClass)
if isMiniScreen
ul
li
a(href="{{pathFor 'home'}}")
span.fa.fa-home
if currentList
each currentBoard.lists
li(class="{{#if $.Session.equals 'currentList' _id}}current{{/if}}")
a.js-select-list
if currentList
each currentBoard.lists
li(class="{{#if $.Session.equals 'currentList' _id}}current{{/if}}")
a.js-select-list
= title
#header-new-board-icon
else
ul
li
a(href="{{pathFor 'home'}}")
span.fa.fa-home
| {{_ 'all-boards'}}
each currentUser.starredBoards
li.separator -
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
a(href="{{pathFor 'board' id=_id slug=slug}}")
= title
#header-new-board-icon
else
ul
li
a(href="{{pathFor 'home'}}")
span.fa.fa-home
| {{_ 'all-boards'}}
each currentUser.starredBoards
li.separator -
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
a(href="{{pathFor 'board' id=_id slug=slug}}")
= title
else
li.current {{_ 'quick-access-description'}}
else
li.current {{_ 'quick-access-description'}}
a#header-new-board-icon.js-create-board
i.fa.fa-plus(title="Create a new board")
a#header-new-board-icon.js-create-board
i.fa.fa-plus(title="Create a new board")
+headerUserBar
+headerUserBar
#header(class=currentBoard.colorClass)
//-
@@ -45,16 +46,17 @@ template(name="header")
#header-main-bar(class="{{#if wrappedHeader}}wrapper{{/if}}")
+Template.dynamic(template=headerBar)
//unless hideLogo
unless hideLogo
//-
On sandstorm, the logo shouldn't be clickable, because we only have one
page/document on it, and we don't want to see the home page containing
the list of all boards.
// unless currentSetting.hideLogo
// a.wekan-logo(href="{{pathFor 'home'}}" title="{{_ 'header-logo-title'}}")
// img(src="{{pathFor '/logo-header.png'}}" alt="")
if isSandstorm
.wekan-logo
img(src="{{pathFor '/wekan-logo-header.png'}}" alt="Wekan")
else
a.wekan-logo(href="{{pathFor 'home'}}" title="{{_ 'header-logo-title'}}")
img(src="{{pathFor '/wekan-logo-header.png'}}" alt="Wekan")
if appIsOffline
+offlineWarning
-5
View File
@@ -1,16 +1,11 @@
Meteor.subscribe('user-admin');
Meteor.subscribe('boards');
Meteor.subscribe('setting');
Template.header.helpers({
wrappedHeader() {
return !Session.get('currentBoard');
},
currentSetting() {
return Settings.findOne();
},
hideLogo() {
return Utils.isMiniScreen() && Session.get('currentBoard');
},
+2
View File
@@ -188,6 +188,8 @@
width: 100%
padding: 10px 0px
z-index: 30
position: absolute
bottom: 0px
ul
width: calc(100% - 60px)
+18 -29
View File
@@ -1,6 +1,7 @@
head
title
meta(name="viewport" content="maximum-scale=1.0,width=device-width,initial-scale=1.0,user-scalable=0")
title Wekan
meta(name="viewport"
content="maximum-scale=1.0,width=device-width,initial-scale=1.0,user-scalable=0")
meta(http-equiv="X-UA-Compatible" content="IE=edge")
//- XXX We should use pathFor in the following `href` to support the case
where the application is deployed with a path prefix, but it seems to be
@@ -8,32 +9,31 @@ head
packages.
link(rel="shortcut icon" href="/wekan-favicon.png")
link(rel="apple-touch-icon" href="/wekan-favicon.png")
link(rel="mask-icon" href="/wekan-logo-150.svg")
link(rel="mask-icon" href="/wekan-150.svg")
link(rel="manifest" href="/wekan-manifest.json")
template(name="userFormsLayout")
section.auth-layout
h1.at-form-landing-logo
img(src="{{pathFor '/wekan-logo.png'}}" alt="Wekan")
section.auth-dialog
if isLoading
+loader
else
+Template.dynamic(template=content)
if currentSetting.displayAuthenticationMethod
+connectionMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)
div.at-form-lang
select.select-lang.js-userform-set-language
each languages
if isCurrentLanguage
option(value="{{tag}}" selected="selected") {{name}}
else
option(value="{{tag}}") {{name}}
+Template.dynamic(template=content)
+connectionMethod
if isCas
.at-form
button#cas(class='at-btn submit' type='submit') {{casSignInLabel}}
div.at-form-lang
select.select-lang.js-userform-set-language
each languages
if isCurrentLanguage
option(value="{{tag}}" selected="selected") {{name}}
else
option(value="{{tag}}") {{name}}
template(name="defaultLayout")
+header
#content
| {{{afterBodyStart}}}
+Template.dynamic(template=content)
| {{{beforeBodyEnd}}}
if (Modal.isOpen)
#modal
.overlay
@@ -59,14 +59,3 @@ template(name="message")
unless currentUser
with(pathFor route='atSignIn')
p {{{_ 'page-maybe-private' this}}}
template(name="loader")
h1.loadingText {{_ 'loading'}}
.lds-roller
div
div
div
div
div
div
div
+54 -99
View File
@@ -20,19 +20,6 @@ const validator = {
},
};
Template.userFormsLayout.onCreated(function() {
const instance = this;
instance.currentSetting = new ReactiveVar();
instance.isLoading = new ReactiveVar(false);
Meteor.subscribe('setting', {
onReady() {
instance.currentSetting.set(Settings.findOne());
return this.stop();
},
});
});
Template.userFormsLayout.onRendered(() => {
AccountsTemplates.state.form.keys = new Proxy(AccountsTemplates.state.form.keys, validator);
@@ -44,22 +31,6 @@ Template.userFormsLayout.onRendered(() => {
});
Template.userFormsLayout.helpers({
currentSetting() {
return Template.instance().currentSetting.get();
},
isLoading() {
return Template.instance().isLoading.get();
},
afterBodyStart() {
return currentSetting.customHTMLafterBodyStart;
},
beforeBodyEnd() {
return currentSetting.customHTMLbeforeBodyEnd;
},
languages() {
return _.map(TAPi18n.getLanguages(), (lang, code) => {
const tag = code;
@@ -68,8 +39,6 @@ Template.userFormsLayout.helpers({
name = 'Brezhoneg';
} else if (lang.name === 'ig') {
name = 'Igbo';
} else if (lang.name === 'oc') {
name = 'Occitan';
}
return { tag, name };
}).sort(function(a, b) {
@@ -86,6 +55,17 @@ Template.userFormsLayout.helpers({
const curLang = T9n.getLanguage() || 'en';
return t9nTag === curLang;
},
/*
isCas() {
return Meteor.settings.public &&
Meteor.settings.public.cas &&
Meteor.settings.public.cas.loginUrl;
},
casSignInLabel() {
return TAPi18n.__('casSignIn', {}, T9n.getLanguage() || 'en');
},
*/
});
Template.userFormsLayout.events({
@@ -94,13 +74,49 @@ Template.userFormsLayout.events({
T9n.setLanguage(i18nTagToT9n(i18nTag));
evt.preventDefault();
},
'click #at-btn'(event, instance) {
if (FlowRouter.getRouteName() === 'atSignIn') {
instance.isLoading.set(true);
authentication(event, instance)
.then(() => {
instance.isLoading.set(false);
});
'click button#cas'() {
Meteor.loginWithCas(function() {
if (FlowRouter.getRouteName() === 'atSignIn') {
FlowRouter.go('/');
}
});
},
'click #at-btn'(event) {
/* All authentication method can be managed/called here.
!! DON'T FORGET to correctly fill the fields of the user during its creation if necessary authenticationMethod : String !!
*/
const authenticationMethodSelected = $('.select-authentication').val();
// Local account
if (authenticationMethodSelected === 'password') {
return;
}
// Stop submit #at-pwd-form
event.preventDefault();
event.stopImmediatePropagation();
const email = $('#at-field-username_and_email').val();
const password = $('#at-field-password').val();
// Ldap account
if (authenticationMethodSelected === 'ldap') {
// Check if the user can use the ldap connection
Meteor.subscribe('user-authenticationMethod', email, {
onReady() {
const user = Users.findOne();
if (user === undefined || user.authenticationMethod === 'ldap') {
// Use the ldap connection package
Meteor.loginWithLDAP(email, password, function(error) {
if (!error) {
// Connection
return FlowRouter.go('/');
}
return error;
});
}
return this.stop();
},
});
}
},
});
@@ -110,64 +126,3 @@ Template.defaultLayout.events({
Modal.close();
},
});
async function authentication(event, instance) {
const match = $('#at-field-username_and_email').val();
const password = $('#at-field-password').val();
if (!match || !password) return undefined;
const result = await getAuthenticationMethod(instance.currentSetting.get(), match);
if (result === 'password') return undefined;
// Stop submit #at-pwd-form
event.preventDefault();
event.stopImmediatePropagation();
switch (result) {
case 'ldap':
return new Promise((resolve) => {
Meteor.loginWithLDAP(match, password, function() {
resolve(FlowRouter.go('/'));
});
});
case 'cas':
return new Promise((resolve) => {
Meteor.loginWithCas(match, password, function() {
resolve(FlowRouter.go('/'));
});
});
default:
return undefined;
}
}
function getAuthenticationMethod({displayAuthenticationMethod, defaultAuthenticationMethod}, match) {
if (displayAuthenticationMethod) {
return $('.select-authentication').val();
}
return getUserAuthenticationMethod(defaultAuthenticationMethod, match);
}
function getUserAuthenticationMethod(defaultAuthenticationMethod, match) {
return new Promise((resolve) => {
try {
Meteor.subscribe('user-authenticationMethod', match, {
onReady() {
const user = Users.findOne();
const authenticationMethod = user
? user.authenticationMethod
: defaultAuthenticationMethod;
resolve(authenticationMethod);
},
});
} catch(error) {
resolve(defaultAuthenticationMethod);
}
});
}
+4 -104
View File
@@ -61,7 +61,7 @@ body
display: block
float: right
font-size: 24px
.modal-content-wide
width: 800px
min-height: 0px
@@ -290,7 +290,7 @@ kbd
// Implement a thiner close icon as suggested in
// https://github.com/FortAwesome/Font-Awesome/issues/1540#issuecomment-68689950
.fa.fa-times-thin:before
content: '\00d7'
content: '\00d7';
.fa.fa-globe.colorful, .fa.fa-bell.colorful
color: #4caf50
@@ -385,8 +385,8 @@ a
@media screen and (max-width: 800px)
#content
margin: 1px 0px 0px 0px
height: calc(100% - 0px)
margin: 1px 0px 49px 0px
height: calc(100% - 96px)
> .wrapper
margin-top: 0px
@@ -399,103 +399,3 @@ a
height: 37px
margin: 8px 10px 0 0
width: 50px
.select-authentication
width: 100%
.auth-layout
display: flex
flex-direction: column
align-items: center
justify-content: center
height: 100%
.auth-dialog
margin: 0 !important
.loadingText
text-align: center
.lds-roller
display: block
margin: auto
position: relative
width: 64px
height: 64px
div
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite
transform-origin: 32px 32px
div:after
content: " "
display: block
position: absolute
width: 6px
height: 6px
border-radius: 50%
background: #dedede
margin: -3px 0 0 -3px
div:nth-child(1)
animation-delay: -0.036s
div:nth-child(1):after
top: 50px
left: 50px
div:nth-child(2)
animation-delay: -0.072s
div:nth-child(2):after
top: 54px
left: 45px
div:nth-child(3)
animation-delay: -0.108s
div:nth-child(3):after
top: 57px
left: 39px
div:nth-child(4)
animation-delay: -0.144s
div:nth-child(4):after
top: 58px
left: 32px
div:nth-child(5)
animation-delay: -0.18s
div:nth-child(5):after
top: 57px
left: 25px
div:nth-child(6)
animation-delay: -0.216s
div:nth-child(6):after
top: 54px
left: 19px
div:nth-child(7)
animation-delay: -0.252s
div:nth-child(7):after
top: 50px
left: 14px
div:nth-child(8)
animation-delay: -0.288s
div:nth-child(8):after
top: 45px
left: 10px
@keyframes lds-roller
0%
transform: rotate(0deg)
100%
transform: rotate(360deg)
@@ -36,33 +36,7 @@ template(name="boardActions")
div.trigger-text
| {{_'r-card'}}
div.trigger-button.js-add-arch-action.js-goto-rules
i.fa.fa-plus
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-add-swimlane'}}
div.trigger-dropdown
input(id="swimlane-name",type=text,placeholder="{{_'r-name'}}")
div.trigger-button.js-add-swimlane-action.js-goto-rules
i.fa.fa-plus
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-create-card'}}
div.trigger-dropdown
input(id="card-name",type=text,placeholder="{{_'r-name'}}")
div.trigger-text
| {{_'r-in-list'}}
div.trigger-dropdown
input(id="list-name",type=text,placeholder="{{_'r-name'}}")
div.trigger-text
| {{_'r-in-swimlane'}}
div.trigger-dropdown
input(id="swimlane-name2",type=text,placeholder="{{_'r-name'}}")
div.trigger-button.js-create-card-action.js-goto-rules
i.fa.fa-plus
i.fa.fa-plus
@@ -5,52 +5,6 @@ BlazeComponent.extendComponent({
events() {
return [{
'click .js-create-card-action' (event) {
const ruleName = this.data().ruleName.get();
const trigger = this.data().triggerVar.get();
const cardName = this.find('#card-name').value;
const listName = this.find('#list-name').value;
const swimlaneName = this.find('#swimlane-name2').value;
const boardId = Session.get('currentBoard');
const desc = Utils.getTriggerActionDesc(event, this);
const triggerId = Triggers.insert(trigger);
const actionId = Actions.insert({
actionType: 'createCard',
swimlaneName,
cardName,
listName,
boardId,
desc,
});
Rules.insert({
title: ruleName,
triggerId,
actionId,
boardId,
});
},
'click .js-add-swimlane-action' (event) {
const ruleName = this.data().ruleName.get();
const trigger = this.data().triggerVar.get();
const swimlaneName = this.find('#swimlane-name').value;
const boardId = Session.get('currentBoard');
const desc = Utils.getTriggerActionDesc(event, this);
const triggerId = Triggers.insert(trigger);
const actionId = Actions.insert({
actionType: 'addSwimlane',
swimlaneName,
boardId,
desc,
});
Rules.insert({
title: ruleName,
triggerId,
actionId,
boardId,
});
},
'click .js-add-spec-move-action' (event) {
const ruleName = this.data().ruleName.get();
const trigger = this.data().triggerVar.get();
@@ -35,21 +35,9 @@ template(name="cardActions")
div.trigger-button.js-add-removeall-action.js-goto-rules
i.fa.fa-plus
div.trigger-item
div.trigger-content
div.trigger-text
| {{{_'r-set-color'}}}
button.trigger-button.trigger-button-color.js-show-color-palette(
id="color-action"
class="card-details-{{cardColorButton}}")
| {{{_ cardColorButtonText }}}
div.trigger-button.js-set-color-action.js-goto-rules
i.fa.fa-plus
template(name="setCardActionsColorPopup")
form.edit-label
.palette-colors: each colors
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
if(isSelected color)
i.fa.fa-check
button.primary.confirm.js-submit {{_ 'save'}}
@@ -1,20 +1,6 @@
let cardColors;
Meteor.startup(() => {
cardColors = Cards.simpleSchema()._schema.color.allowedValues;
});
BlazeComponent.extendComponent({
onCreated() {
this.subscribe('allRules');
this.cardColorButtonValue = new ReactiveVar('green');
},
cardColorButton() {
return this.cardColorButtonValue.get();
},
cardColorButtonText() {
return `color-${ this.cardColorButtonValue.get() }`;
},
labels() {
@@ -126,63 +112,7 @@ BlazeComponent.extendComponent({
boardId,
});
},
'click .js-show-color-palette'(event){
const funct = Popup.open('setCardActionsColor');
const colorButton = this.find('#color-action');
if (colorButton.value === '') {
colorButton.value = 'green';
}
funct.call(this, event);
},
'click .js-set-color-action' (event) {
const ruleName = this.data().ruleName.get();
const trigger = this.data().triggerVar.get();
const selectedColor = this.cardColorButtonValue.get();
const boardId = Session.get('currentBoard');
const desc = Utils.getTriggerActionDesc(event, this);
const triggerId = Triggers.insert(trigger);
const actionId = Actions.insert({
actionType: 'setColor',
selectedColor,
boardId,
desc,
});
Rules.insert({
title: ruleName,
triggerId,
actionId,
boardId,
});
},
}];
},
}).register('cardActions');
BlazeComponent.extendComponent({
onCreated() {
this.currentAction = this.currentData();
this.colorButtonValue = Popup.getOpenerComponent().cardColorButtonValue;
this.currentColor = new ReactiveVar(this.colorButtonValue.get());
},
colors() {
return cardColors.map((color) => ({ color, name: '' }));
},
isSelected(color) {
return this.currentColor.get() === color;
},
events() {
return [{
'click .js-palette-color'() {
this.currentColor.set(this.currentData().color);
},
'click .js-submit' () {
this.colorButtonValue.set(this.currentColor.get());
Popup.close();
},
}];
},
}).register('setCardActionsColorPopup');
@@ -43,25 +43,6 @@ template(name="checklistActions")
div.trigger-button.js-add-check-item-action.js-goto-rules
i.fa.fa-plus
div.trigger-item
div.trigger-content
div.trigger-text
| {{{_'r-add-checklist'}}}
div.trigger-dropdown
input(id="checklist-name-3",type=text,placeholder="{{{_'r-name'}}}")
div.trigger-text
| {{{_'r-with-items'}}}
div.trigger-dropdown
input(id="checklist-items",type=text,placeholder="{{{_'r-items-list'}}}")
div.trigger-button.js-add-checklist-items-action.js-goto-rules
i.fa.fa-plus
div.trigger-item
div.trigger-content
div.trigger-text
| {{{_'r-checklist-note'}}}
@@ -4,29 +4,6 @@ BlazeComponent.extendComponent({
},
events() {
return [{
'click .js-add-checklist-items-action' (event) {
const ruleName = this.data().ruleName.get();
const trigger = this.data().triggerVar.get();
const checklistName = this.find('#checklist-name-3').value;
const checklistItems = this.find('#checklist-items').value;
const boardId = Session.get('currentBoard');
const desc = Utils.getTriggerActionDesc(event, this);
const triggerId = Triggers.insert(trigger);
const actionId = Actions.insert({
actionType: 'addChecklistWithItems',
checklistName,
checklistItems,
boardId,
desc,
});
Rules.insert({
title: ruleName,
triggerId,
actionId,
boardId,
});
},
'click .js-add-checklist-action' (event) {
const ruleName = this.data().ruleName.get();
const trigger = this.data().triggerVar.get();
+1 -24
View File
@@ -10,10 +10,7 @@
display: inline-block
float: left
margin: revert
.hide-element
display:none !important
.user-details
display:inline-block
.rules-btns-group
position: absolute
right: 0
@@ -123,15 +120,6 @@
.trigger-text
font-size: 16px
display:inline-block
.trigger-inline-button
font-size: 16px
display: inline;
padding: 6px;
border: 1px solid #eee
border-radius: 4px
box-shadow: inset -1px -1px 3px rgba(0,0,0,.05)
&:hover, &.is-active
box-shadow: 0 0 0 2px darken(white, 60%) inset
.trigger-text.trigger-text-email
margin-left: 5px;
margin-top: 10px;
@@ -172,17 +160,6 @@
box-shadow: 0 0 0 2px darken(white, 60%) inset
.trigger-button.trigger-button-email
top:30px
.trigger-button.trigger-button-person
right:-40px
.trigger-button.trigger-button-color
top: unset
position: unset
transform: unset
font-size: 16px
width:auto
padding-left: 10px
padding-right: 10px
height:40px
.trigger-item.trigger-item-mail
height:300px
+1 -28
View File
@@ -9,13 +9,7 @@ BlazeComponent.extendComponent({
setTrigger() {
this.rulesCurrentTab.set('trigger');
},
sanitizeObject(obj){
Object.keys(obj).forEach((key) => {
if(obj[key] === '' || obj[key] === undefined){
obj[key] = '*';
}}
);
},
setRulesList() {
this.rulesCurrentTab.set('rulesList');
},
@@ -48,27 +42,8 @@ BlazeComponent.extendComponent({
},
'click .js-goto-action' (event) {
event.preventDefault();
// Add user to the trigger
const username = $(event.currentTarget.offsetParent).find('.user-name').val();
let trigger = this.triggerVar.get();
trigger.userId = '*';
if(username !== undefined ){
const userFound = Users.findOne({username});
if(userFound !== undefined){
trigger.userId = userFound._id;
this.triggerVar.set(trigger);
}
}
// Sanitize trigger
trigger = this.triggerVar.get();
this.sanitizeObject(trigger);
this.triggerVar.set(trigger);
this.setAction();
},
'click .js-show-user-field' (event) {
event.preventDefault();
$(event.currentTarget.offsetParent).find('.user-details').removeClass('hide-element');
},
'click .js-goto-rules' (event) {
event.preventDefault();
this.setRulesList();
@@ -93,5 +68,3 @@ BlazeComponent.extendComponent({
},
}).register('rulesMain');
@@ -1,58 +1,43 @@
template(name="boardTriggers")
div.trigger-item#trigger-two
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-when-a-card'}}
div.trigger-inline-button.js-open-card-title-popup
i.fa.fa-filter
| {{_'r-when-a-card-is'}}
div.trigger-dropdown
select(id="gen-action")
option(value="created") {{_'r-added-to'}}
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-is'}}
| {{_'r-the-board'}}
div.trigger-button.js-add-gen-trigger.js-goto-action
i.fa.fa-plus
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-added-to'}}
| {{_'r-when-a-card-is'}}
div.trigger-dropdown
select(id="create-action")
option(value="created") {{_'r-added-to'}}
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-list'}}
div.trigger-dropdown
input(id="create-list-name",type=text,placeholder="{{_'r-list-name'}}")
div.trigger-text
| {{_'r-in-swimlane'}}
div.trigger-dropdown
input(id="create-swimlane-name",type=text,placeholder="{{_'r-swimlane-name'}}")
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
input(id="create-list-name",type=text,placeholder="{{_'r-list-name'}}")
div.trigger-button.js-add-create-trigger.js-goto-action
i.fa.fa-plus
div.trigger-item#trigger-three
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-when-a-card'}}
div.trigger-inline-button.js-open-card-title-popup
i.fa.fa-filter
div.trigger-text
| {{_'r-is-moved'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
| {{_'r-when-a-card-is-moved'}}
div.trigger-button.js-add-gen-moved-trigger.js-goto-action
i.fa.fa-plus
div.trigger-item#trigger-four
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-when-a-card'}}
div.trigger-inline-button.js-open-card-title-popup
i.fa.fa-filter
div.trigger-text
| {{_'r-is'}}
| {{_'r-when-a-card-is'}}
div.trigger-dropdown
select(id="move-action")
option(value="moved-to") {{_'r-moved-to'}}
@@ -60,54 +45,21 @@ template(name="boardTriggers")
div.trigger-text
| {{_'r-list'}}
div.trigger-dropdown
input(id="move-list-name",type=text,placeholder="{{_'r-list-name'}}")
div.trigger-text
| {{_'r-in-swimlane'}}
div.trigger-dropdown
input(id="create-swimlane-name-2",type=text,placeholder="{{_'r-swimlane-name'}}")
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
input(id="move-list-name",type=text,placeholder="{{_'r-list-name'}}")
div.trigger-button.js-add-moved-trigger.js-goto-action
i.fa.fa-plus
div.trigger-item#trigger-five
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-when-a-card'}}
div.trigger-inline-button.js-open-card-title-popup
i.fa.fa-filter
div.trigger-text
| {{_'r-is'}}
| {{_'r-when-a-card-is'}}
div.trigger-dropdown
select(id="arch-action")
option(value="archived") {{_'r-archived'}}
option(value="unarchived") {{_'r-unarchived'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-arch-trigger.js-goto-action
i.fa.fa-plus
div.trigger-item
div.trigger-content
div.trigger-text
| {{{_'r-board-note'}}}
template(name="boardCardTitlePopup")
form
label
| Card Title Filter
input.js-card-filter-name(type="text" value=title autofocus)
input.js-card-filter-button.primary.wide(type="submit" value="{{_ 'set-filter'}}")
@@ -1,57 +1,67 @@
BlazeComponent.extendComponent({
onCreated() {
this.provaVar = new ReactiveVar('');
this.currentPopupTriggerId = 'def';
this.cardTitleFilters = {};
},
setNameFilter(name){
this.cardTitleFilters[this.currentPopupTriggerId] = name;
},
events() {
return [{
'click .js-open-card-title-popup'(event){
const funct = Popup.open('boardCardTitle');
const divId = $(event.currentTarget.parentNode.parentNode).attr('id');
//console.log('current popup');
//console.log(this.currentPopupTriggerId);
this.currentPopupTriggerId = divId;
funct.call(this, event);
'click .js-add-gen-trigger' (event) {
const desc = Utils.getTriggerActionDesc(event, this);
const datas = this.data();
const actionSelected = this.find('#gen-action').value;
const boardId = Session.get('currentBoard');
if (actionSelected === 'created') {
datas.triggerVar.set({
activityType: 'createCard',
boardId,
'listName': '*',
desc,
});
}
if (actionSelected === 'removed') {
datas.triggerVar.set({
activityType: 'removeCard',
boardId,
desc,
});
}
},
'click .js-add-create-trigger' (event) {
const desc = Utils.getTriggerActionDesc(event, this);
const datas = this.data();
const actionSelected = this.find('#create-action').value;
const listName = this.find('#create-list-name').value;
const swimlaneName = this.find('#create-swimlane-name').value;
const boardId = Session.get('currentBoard');
const divId = $(event.currentTarget.parentNode).attr('id');
const cardTitle = this.cardTitleFilters[divId];
// move to generic funciont
datas.triggerVar.set({
activityType: 'createCard',
boardId,
cardTitle,
swimlaneName,
listName,
desc,
});
if (actionSelected === 'created') {
datas.triggerVar.set({
activityType: 'createCard',
boardId,
listName,
desc,
});
}
if (actionSelected === 'removed') {
datas.triggerVar.set({
activityType: 'removeCard',
boardId,
listName,
desc,
});
}
},
'click .js-add-moved-trigger' (event) {
const datas = this.data();
const desc = Utils.getTriggerActionDesc(event, this);
const swimlaneName = this.find('#create-swimlane-name-2').value;
const actionSelected = this.find('#move-action').value;
const listName = this.find('#move-list-name').value;
const boardId = Session.get('currentBoard');
const divId = $(event.currentTarget.parentNode).attr('id');
const cardTitle = this.cardTitleFilters[divId];
if (actionSelected === 'moved-to') {
datas.triggerVar.set({
activityType: 'moveCard',
boardId,
listName,
cardTitle,
swimlaneName,
'oldListName': '*',
desc,
});
@@ -60,8 +70,6 @@ BlazeComponent.extendComponent({
datas.triggerVar.set({
activityType: 'moveCard',
boardId,
cardTitle,
swimlaneName,
'listName': '*',
'oldListName': listName,
desc,
@@ -74,9 +82,8 @@ BlazeComponent.extendComponent({
const boardId = Session.get('currentBoard');
datas.triggerVar.set({
'activityType': 'moveCard',
activityType: 'moveCard',
boardId,
'swimlaneName': '*',
'listName':'*',
'oldListName': '*',
desc,
@@ -107,13 +114,3 @@ BlazeComponent.extendComponent({
},
}).register('boardTriggers');
Template.boardCardTitlePopup.events({
submit(evt, tpl) {
const title = tpl.$('.js-card-filter-name').val().trim();
Popup.getOpenerComponent().setNameFilter(title);
evt.preventDefault();
Popup.close();
},
});
@@ -1,114 +1,79 @@
template(name="cardTriggers")
div.trigger-item
div.trigger-content
div.trigger-text
div.trigger-text
| {{_'r-when-a-label-is'}}
div.trigger-dropdown
select(id="label-action")
option(value="added") {{_'r-added-to'}}
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-label-trigger.js-goto-action
i.fa.fa-plus
div.trigger-item
div.trigger-content
div.trigger-text
div.trigger-text
| {{_'r-when-the-label-is'}}
div.trigger-dropdown
select(id="spec-label")
each labels
option(value="#{_id}" style="background-color: #{name}")
= translatedname
div.trigger-text
option(value="#{_id}")
= name
div.trigger-text
| {{_'r-is'}}
div.trigger-dropdown
select(id="spec-label-action")
option(value="added") {{_'r-added-to'}}
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-spec-label-trigger.js-goto-action
i.fa.fa-plus
div.trigger-item
div.trigger-content
div.trigger-text
div.trigger-text
| {{_'r-when-a-member'}}
div.trigger-dropdown
select(id="gen-member-action")
option(value="added") {{_'r-added-to'}}
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-member-trigger.js-goto-action
i.fa.fa-plus
div.trigger-item
div.trigger-content
div.trigger-text
div.trigger-text
| {{_'r-when-the-member'}}
div.trigger-dropdown
input(id="spec-member",type=text,placeholder="{{_'r-name'}}")
div.trigger-text
input(id="spec-member",type=text,placeholder="{{_'r-name'}}")
div.trigger-text
| {{_'r-is'}}
div.trigger-dropdown
select(id="spec-member-action")
option(value="added") {{_'r-added-to'}}
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-spec-member-trigger.js-goto-action
i.fa.fa-plus
div.trigger-item
div.trigger-content
div.trigger-text
div.trigger-text
| {{_'r-when-a-attach'}}
div.trigger-text
div.trigger-text
| {{_'r-is'}}
div.trigger-dropdown
select(id="attach-action")
option(value="added") {{_'r-added-to'}}
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-attachment-trigger.js-goto-action
i.fa.fa-plus
@@ -6,10 +6,7 @@ BlazeComponent.extendComponent({
const labels = Boards.findOne(Session.get('currentBoard')).labels;
for (let i = 0; i < labels.length; i++) {
if (labels[i].name === '' || labels[i].name === undefined) {
labels[i].name = labels[i].color;
labels[i].translatedname = `${TAPi18n.__(`color-${ labels[i].color}`)}`;
} else {
labels[i].translatedname = labels[i].name;
labels[i].name = labels[i].color.toUpperCase();
}
}
return labels;
@@ -9,13 +9,6 @@ template(name="checklistTriggers")
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-check-trigger.js-goto-action
i.fa.fa-plus
@@ -34,13 +27,6 @@ template(name="checklistTriggers")
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-spec-check-trigger.js-goto-action
i.fa.fa-plus
@@ -52,13 +38,6 @@ template(name="checklistTriggers")
select(id="gen-comp-check-action")
option(value="completed") {{_'r-completed'}}
option(value="uncompleted") {{_'r-made-incomplete'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-comp-trigger.js-goto-action
i.fa.fa-plus
@@ -74,13 +53,6 @@ template(name="checklistTriggers")
select(id="spec-comp-check-action")
option(value="completed") {{_'r-completed'}}
option(value="uncompleted") {{_'r-made-incomplete'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-spec-comp-trigger.js-goto-action
i.fa.fa-plus
@@ -92,13 +64,6 @@ template(name="checklistTriggers")
select(id="check-item-gen-action")
option(value="checked") {{_'r-checked'}}
option(value="unchecked") {{_'r-unchecked'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-check-item-trigger.js-goto-action
i.fa.fa-plus
@@ -114,12 +79,5 @@ template(name="checklistTriggers")
select(id="check-item-spec-action")
option(value="checked") {{_'r-checked'}}
option(value="unchecked") {{_'r-unchecked'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-spec-check-item-trigger.js-goto-action
i.fa.fa-plus
@@ -2,8 +2,5 @@ template(name='connectionMethod')
div.at-form-authentication
label {{_ 'authentication-method'}}
select.select-authentication
each authentications
if isSelected value
option(value="{{value}}" selected) {{_ value}}
else
option(value="{{value}}") {{_ value}}
each authentications
option(value="{{value}}") {{_ value}}
@@ -31,7 +31,4 @@ Template.connectionMethod.helpers({
authentications() {
return Template.instance().authenticationMethods.get();
},
isSelected(match) {
return Template.instance().data.authenticationMethod === match;
},
});
@@ -17,7 +17,7 @@ template(name='statistics')
table
tbody
tr
th Wekan {{_ 'info'}}
th {{_ 'Wekan_version'}}
td {{statistics.version}}
tr
th {{_ 'Node_version'}}
+3 -11
View File
@@ -3,13 +3,8 @@ template(name="people")
unless currentUser.isAdmin
| {{_ 'error-notAuthorized'}}
else
.content-title.ext-box
.ext-box-left
span {{_ 'people'}}
input#searchInput(placeholder="{{_ 'search'}}")
button#searchButton {{_ 'search'}}
.ext-box-right
span {{_ 'people-number'}} #{peopleNumber}
.content-title
span {{_ 'people'}}
.content-body
.side-menu
ul
@@ -107,8 +102,5 @@ template(name="editUserPopup")
label
| {{_ 'password'}}
input.js-profile-password(type="password")
div.buttonsContainer
input.primary.wide(type="submit" value="{{_ 'save'}}")
div
input#deleteButton.primary.wide(type="button" value="{{_ 'delete'}}")
input.primary.wide(type="submit" value="{{_ 'save'}}")
+1 -44
View File
@@ -8,8 +8,6 @@ BlazeComponent.extendComponent({
this.error = new ReactiveVar('');
this.loading = new ReactiveVar(false);
this.people = new ReactiveVar(true);
this.findUsersOptions = new ReactiveVar({});
this.number = new ReactiveVar(0);
this.page = new ReactiveVar(1);
this.loadNextPageLocked = false;
@@ -28,33 +26,6 @@ BlazeComponent.extendComponent({
});
});
},
events() {
return [{
'click #searchButton'() {
this.filterPeople();
},
'keydown #searchInput'(event) {
if (event.keyCode === 13 && !event.shiftKey) {
this.filterPeople();
}
},
}];
},
filterPeople() {
const value = $('#searchInput').first().val();
if (value === '') {
this.findUsersOptions.set({});
} else {
const regex = new RegExp(value, 'i');
this.findUsersOptions.set({
$or: [
{ username: regex },
{ 'profile.fullname': regex },
{ 'emails.address': regex },
],
});
}
},
loadNextPage() {
if (this.loadNextPageLocked === false) {
this.page.set(this.page.get() + 1);
@@ -78,14 +49,9 @@ BlazeComponent.extendComponent({
this.loading.set(w);
},
peopleList() {
const users = Users.find(this.findUsersOptions.get(), {
return Users.find({}, {
fields: {_id: true},
});
this.number.set(users.count());
return users;
},
peopleNumber() {
return this.number.get();
},
}).register('people');
@@ -98,7 +64,6 @@ Template.peopleRow.helpers({
Template.editUserPopup.onCreated(function() {
this.authenticationMethods = new ReactiveVar([]);
this.errorMessage = new ReactiveVar('');
Meteor.call('getAuthenticationsEnabled', (_, result) => {
if (result) {
@@ -130,9 +95,6 @@ Template.editUserPopup.helpers({
const selected = Users.findOne(userId).authenticationMethod;
return selected === 'ldap';
},
errorMessage() {
return Template.instance().errorMessage.get();
},
});
BlazeComponent.extendComponent({
@@ -224,9 +186,4 @@ Template.editUserPopup.events({
});
} else Popup.close();
},
'click #deleteButton'() {
Users.remove(this.userId);
Popup.close();
},
});
@@ -13,36 +13,3 @@ table
tr:nth-child(even)
background-color: #dddddd;
.ext-box
display: flex;
flex-direction: row;
height: 34px;
.ext-box-left
display: flex;
width: 40%
span
vertical-align: center;
line-height: 34px;
margin-right: 10px;
input, button
margin: 0 10px 0 0;
padding: 0;
button
min-width: 60px;
.content-wrapper
margin-top: 10px
.buttonsContainer
display: flex
input
margin: 0
div
margin: auto
+2 -33
View File
@@ -134,41 +134,10 @@ template(name='announcementSettings')
template(name='layoutSettings')
ul#layout-setting.setting-detail
//li.layout-form
.title {{_ 'hide-logo'}}
.form-group.flex
input.form-control#hide-logo(type="radio" name="hideLogo" value="true" checked="{{#if currentSetting.hideLogo}}checked{{/if}}")
span {{_ 'yes'}}
input.form-control#hide-logo(type="radio" name="hideLogo" value="false" checked="{{#unless currentSetting.hideLogo}}checked{{/unless}}")
span {{_ 'no'}}
li.layout-form
.title {{_ 'display-authentication-method'}}
.form-group.flex
input.form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="true" checked="{{#if currentSetting.displayAuthenticationMethod}}checked{{/if}}")
span {{_ 'yes'}}
input.form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="false" checked="{{#unless currentSetting.displayAuthenticationMethod}}checked{{/unless}}")
span {{_ 'no'}}
li.layout-form
.title {{_ 'default-authentication-method'}}
+selectAuthenticationMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)
li.layout-form
.title {{_ 'custom-product-name'}}
.form-group
input.form-control#product-name(type="text", placeholder="" value="{{currentSetting.productName}}")
li.layout-form
.title {{_ 'add-custom-html-after-body-start'}}
textarea#customHTMLafterBodyStart.form-control= currentSetting.customHTMLafterBodyStart
li.layout-form
.title {{_ 'add-custom-html-before-body-end'}}
textarea#customHTMLbeforeBodyEnd.form-control= currentSetting.customHTMLbeforeBodyEnd
input.form-control#product-name(type="text", placeholder="Wekan" value="{{currentSetting.productName}}")
li
button.js-save-layout.primary {{_ 'save'}}
template(name='selectAuthenticationMethod')
select#defaultAuthenticationMethod
each authentications
if isSelected value
option(value="{{value}}" selected) {{_ value}}
else
option(value="{{value}}") {{_ value}}
+6 -50
View File
@@ -59,12 +59,6 @@ BlazeComponent.extendComponent({
toggleTLS() {
$('#mail-server-tls').toggleClass('is-checked');
},
toggleHideLogo() {
$('#hide-logo').toggleClass('is-checked');
},
toggleDisplayAuthenticationMethod() {
$('#display-authentication-method').toggleClass('is-checked');
},
switchMenu(event) {
const target = $(event.target);
if (!target.hasClass('active')) {
@@ -90,7 +84,7 @@ BlazeComponent.extendComponent({
},
inviteThroughEmail() {
const emails = $('#email-to-invite').val().toLowerCase().trim().split('\n').join(',').split(',');
const emails = $('#email-to-invite').val().trim().split('\n').join(',').split(',');
const boardsToInvite = [];
$('.js-toggle-board-choose .materialCheckBox.is-checked').each(function () {
boardsToInvite.push($(this).data('id'));
@@ -141,22 +135,11 @@ BlazeComponent.extendComponent({
this.setLoading(true);
$('li').removeClass('has-error');
const productName = $('#product-name').val().trim();
const hideLogoChange = ($('input[name=hideLogo]:checked').val() === 'true');
const displayAuthenticationMethod = ($('input[name=displayAuthenticationMethod]:checked').val() === 'true');
const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
const customHTMLafterBodyStart = $('#customHTMLafterBodyStart').val().trim();
const customHTMLbeforeBodyEnd = $('#customHTMLbeforeBodyEnd').val().trim();
try {
const productName = $('#product-name').val().trim();
Settings.update(Settings.findOne()._id, {
$set: {
productName,
hideLogo: hideLogoChange,
customHTMLafterBodyStart,
customHTMLbeforeBodyEnd,
displayAuthenticationMethod,
defaultAuthenticationMethod,
},
});
} catch (e) {
@@ -165,20 +148,21 @@ BlazeComponent.extendComponent({
this.setLoading(false);
}
DocHead.setTitle(productName);
},
sendSMTPTestEmail() {
Meteor.call('sendSMTPTestEmail', (err, ret) => {
if (!err && ret) {
if (!err && ret) { /* eslint-disable no-console */
const message = `${TAPi18n.__(ret.message)}: ${ret.email}`;
console.log(message);
alert(message);
} else {
const reason = err.reason || '';
const message = `${TAPi18n.__(err.error)}\n${reason}`;
console.log(message, err);
alert(message);
}
/* eslint-enable no-console */
});
},
@@ -191,9 +175,7 @@ BlazeComponent.extendComponent({
'click button.js-email-invite': this.inviteThroughEmail,
'click button.js-save': this.saveMailServerInfo,
'click button.js-send-smtp-test-email': this.sendSMTPTestEmail,
'click a.js-toggle-hide-logo': this.toggleHideLogo,
'click button.js-save-layout': this.saveLayout,
'click a.js-toggle-display-authentication-method': this.toggleDisplayAuthenticationMethod,
}];
},
}).register('setting');
@@ -266,29 +248,3 @@ BlazeComponent.extendComponent({
}];
},
}).register('announcementSettings');
Template.selectAuthenticationMethod.onCreated(function() {
this.authenticationMethods = new ReactiveVar([]);
Meteor.call('getAuthenticationsEnabled', (_, result) => {
if (result) {
// TODO : add a management of different languages
// (ex {value: ldap, text: TAPi18n.__('ldap', {}, T9n.getLanguage() || 'en')})
this.authenticationMethods.set([
{value: 'password'},
// Gets only the authentication methods availables
...Object.entries(result).filter((e) => e[1]).map((e) => ({value: e[0]})),
]);
}
});
});
Template.selectAuthenticationMethod.helpers({
authentications() {
return Template.instance().authenticationMethods.get();
},
isSelected(match) {
return Template.instance().data.authenticationMethod === match;
},
});
@@ -52,10 +52,6 @@
.main-body
padding: 0.1em 1em
-webkit-user-select: auto // Safari 3.1+
-moz-user-select: auto // Firefox 2+
-ms-user-select: auto // IE 10+
user-select: auto // Standard syntax
ul
li
+16 -15
View File
@@ -4,21 +4,22 @@ template(name="settingHeaderBar")
.setting-header-btns.left
unless isMiniScreen
if currentUser
a.setting-header-btn.settings(href="{{pathFor 'setting'}}")
i.fa(class="fa-cog")
span {{_ 'settings'}}
unless isSandstorm
if currentUser
a.setting-header-btn.settings(href="{{pathFor 'setting'}}")
i.fa(class="fa-cog")
span {{_ 'settings'}}
a.setting-header-btn.people(href="{{pathFor 'people'}}")
i.fa(class="fa-users")
span {{_ 'people'}}
a.setting-header-btn.people(href="{{pathFor 'people'}}")
i.fa(class="fa-users")
span {{_ 'people'}}
a.setting-header-btn.informations(href="{{pathFor 'information'}}")
i.fa(class="fa-info-circle")
span {{_ 'info'}}
a.setting-header-btn.informations(href="{{pathFor 'information'}}")
i.fa(class="fa-info-circle")
span {{_ 'info'}}
else
a.setting-header-btn.js-log-in(
title="{{_ 'log-in'}}")
i.fa.fa-sign-in
span {{_ 'log-in'}}
else
a.setting-header-btn.js-log-in(
title="{{_ 'log-in'}}")
i.fa.fa-sign-in
span {{_ 'log-in'}}
+17 -145
View File
@@ -1,17 +1,17 @@
template(name="sidebar")
.board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}}")
//a.sidebar-tongue.js-toggle-sidebar(
// class="{{#if isTongueHidden}}is-hidden{{/if}}",
// title="{{showTongueTitle}}")
// i.fa.fa-navicon
a.sidebar-tongue.js-toggle-sidebar(
class="{{#if isTongueHidden}}is-hidden{{/if}}",
title="{{showTongueTitle}}")
i.fa.fa-angle-left
.sidebar-shadow
.sidebar-content.sidebar-shortcuts
a.board-header-btn.js-shortcuts
i.fa.fa-keyboard-o
span {{_ 'keyboard-shortcuts' }}
.sidebar-content.js-board-sidebar-content.js-perfect-scrollbar
//a.hide-btn.js-hide-sidebar
// i.fa.fa-navicon
a.hide-btn.js-hide-sidebar
i.fa.fa-angle-right
unless isDefaultView
h2
a.fa.fa-chevron-left.js-back-home
@@ -34,9 +34,6 @@ template(name="membersWidget")
h3
i.fa.fa-user
| {{_ 'members'}}
a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}").right
i.board-header-btn-icon.fa.fa-cog
.board-widget-content
each currentBoard.activeMembers
+userAvatar(userId=this.userId showStatus=true)
@@ -56,130 +53,6 @@ template(name="membersWidget")
button.js-member-invite-accept.primary {{_ 'accept'}}
button.js-member-invite-decline {{_ 'decline'}}
template(name="boardChangeColorPopup")
.board-backgrounds-list.clearfix
each backgroundColors
.board-background-select.js-select-background
span.background-box(class="board-color-{{this}}")
if isSelected
i.fa.fa-check
template(name="boardSubtaskSettingsPopup")
form.board-subtask-settings
h3 {{_ 'show-parent-in-minicard'}}
a#prefix-with-full-path.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'prefix-with-full-path'}}is-checked{{/if}}")
.materialCheckBox(class="{{#if $eq presentParentTask 'prefix-with-full-path'}}is-checked{{/if}}")
span {{_ 'prefix-with-full-path'}}
a#prefix-with-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'prefix-with-parent'}}is-checked{{/if}}")
.materialCheckBox(class="{{#if $eq presentParentTask 'prefix-with-parent'}}is-checked{{/if}}")
span {{_ 'prefix-with-parent'}}
a#subtext-with-full-path.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'subtext-with-full-path'}}is-checked{{/if}}")
.materialCheckBox(class="{{#if $eq presentParentTask 'subtext-with-full-path'}}is-checked{{/if}}")
span {{_ 'subtext-with-full-path'}}
a#subtext-with-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'subtext-with-parent'}}is-checked{{/if}}")
.materialCheckBox(class="{{#if $eq presentParentTask 'subtext-with-parent'}}is-checked{{/if}}")
span {{_ 'subtext-with-parent'}}
a#no-parent.flex.js-field-show-parent-in-minicard(class="{{#if $eq presentParentTask 'no-parent'}}is-checked{{/if}}")
.materialCheckBox(class="{{#if $eq presentParentTask 'no-parent'}}is-checked{{/if}}")
span {{_ 'no-parent'}}
div
hr
div.check-div
a.flex.js-field-has-subtasks(class="{{#if allowsSubtasks}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsSubtasks}}is-checked{{/if}}")
span {{_ 'show-subtasks-field'}}
label
| {{_ 'deposit-subtasks-board'}}
select.js-field-deposit-board(disabled="{{#unless allowsSubtasks}}disabled{{/unless}}")
each boards
if isBoardSelected
option(value=_id selected="selected") {{title}}
else
option(value=_id) {{title}}
if isNullBoardSelected
option(value='null' selected="selected") {{_ 'custom-field-dropdown-none'}}
else
option(value='null') {{_ 'custom-field-dropdown-none'}}
div
hr
label
| {{_ 'deposit-subtasks-list'}}
select.js-field-deposit-list(disabled="{{#unless hasLists}}disabled{{/unless}}")
each lists
if isListSelected
option(value=_id selected="selected") {{title}}
else
option(value=_id) {{title}}
template(name="chooseBoardSource")
ul.pop-over-list
li
a(href="{{pathFor '/import/trello'}}") {{_ 'from-trello'}}
li
a(href="{{pathFor '/import/wekan'}}") {{_ 'from-wekan'}}
template(name="archiveBoardPopup")
p {{_ 'close-board-pop'}}
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
template(name="outgoingWebhooksPopup")
each integrations
form.integration-form
if title
h4 {{title}}
else
h4 {{_ 'no-name'}}
label
| URL
input.js-outgoing-webhooks-url(type="text" name="url" value=url)
input(type="hidden" value=_id name="id")
input.primary.wide(type="submit" value="{{_ 'save'}}")
form.integration-form
h4
| {{_ 'new-outgoing-webhook'}}
label
| URL
input.js-outgoing-webhooks-url(type="text" name="url" autofocus)
input.primary.wide(type="submit" value="{{_ 'save'}}")
template(name="boardMenuPopup")
ul.pop-over-list
li: a.js-custom-fields {{_ 'custom-fields'}}
li: a.js-open-archives {{_ 'archived-items'}}
if currentUser.isBoardAdmin
li: a.js-change-board-color {{_ 'board-change-color'}}
//-
XXX Language should be handled by sandstorm, but for now display a
language selection link in the board menu. This link is normally present
in the header bar that is not displayed on sandstorm.
if isSandstorm
li: a.js-change-language {{_ 'language'}}
unless isSandstorm
if currentUser.isBoardAdmin
hr
ul.pop-over-list
li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
unless currentBoard.isTemplatesBoard
li: a.js-archive-board {{_ 'archive-board'}}
li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
hr
ul.pop-over-list
li: a.js-subtask-settings {{_ 'subtask-settings'}}
if isSandstorm
hr
ul.pop-over-list
li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
li: a.js-import-board {{_ 'import-board-c'}}
li: a.js-archive-board {{_ 'archive-board'}}
li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
hr
ul.pop-over-list
li: a.js-subtask-settings {{_ 'subtask-settings'}}
template(name="labelsWidget")
.board-widget.board-widget-labels
h3
@@ -189,9 +62,7 @@ template(name="labelsWidget")
each currentBoard.labels
a.card-label(class="card-label-{{color}}"
class="{{#if currentUser.isNotCommentOnly}}js-label{{/if}}")
span.card-label-name
+viewer
= name
span.card-label-name= name
if currentUser.isBoardAdmin
a.card-label.add-label.js-add-label
i.fa.fa-plus
@@ -212,16 +83,17 @@ template(name="memberPopup")
ul.pop-over-list
li
a.js-filter-member {{_ 'filter-cards'}}
if currentUser.isBoardAdmin
unless isSandstorm
if currentUser.isBoardAdmin
li
a.js-change-role
| {{_ 'change-permissions'}}
span.quiet (#{memberType})
li
a.js-change-role
| {{_ 'change-permissions'}}
span.quiet (#{memberType})
li
if $eq currentUser._id userId
a.js-leave-member {{_ 'leave-board'}}
else if currentUser.isBoardAdmin
a.js-remove-member {{_ 'remove-from-board'}}
if $eq currentUser._id userId
a.js-leave-member {{_ 'leave-board'}}
else if currentUser.isBoardAdmin
a.js-remove-member {{_ 'remove-from-board'}}
template(name="removeMemberPopup")
-224
View File
@@ -142,52 +142,6 @@ Template.memberPopup.helpers({
},
});
Template.boardMenuPopup.events({
'click .js-rename-board': Popup.open('boardChangeTitle'),
'click .js-custom-fields'() {
Sidebar.setView('customFields');
Popup.close();
},
'click .js-open-archives'() {
Sidebar.setView('archives');
Popup.close();
},
'click .js-change-board-color': Popup.open('boardChangeColor'),
'click .js-change-language': Popup.open('changeLanguage'),
'click .js-archive-board ': Popup.afterConfirm('archiveBoard', function() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
currentBoard.archive();
// XXX We should have some kind of notification on top of the page to
// confirm that the board was successfully archived.
FlowRouter.go('home');
}),
'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
Popup.close();
Boards.remove(currentBoard._id);
FlowRouter.go('home');
}),
'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
});
Template.boardMenuPopup.helpers({
exportUrl() {
const params = {
boardId: Session.get('currentBoard'),
};
const queryParams = {
authToken: Accounts._storedLoginToken(),
};
return FlowRouter.path('/api/boards/:boardId/export', params, queryParams);
},
exportFilename() {
const boardId = Session.get('currentBoard');
return `wekan-export-board-${boardId}.json`;
},
});
Template.memberPopup.events({
'click .js-filter-member'() {
Filter.members.toggle(this.userId);
@@ -236,14 +190,7 @@ Template.membersWidget.helpers({
Template.membersWidget.events({
'click .js-member': Popup.open('member'),
'click .js-open-board-menu': Popup.open('boardMenu'),
'click .js-manage-board-members': Popup.open('addMember'),
'click .js-import': Popup.open('boardImportBoard'),
submit: this.onSubmit,
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
},
'click .sandstorm-powerbox-request-identity'() {
window.sandstormRequestIdentity();
},
@@ -262,59 +209,6 @@ Template.membersWidget.events({
},
});
BlazeComponent.extendComponent({
integrations() {
const boardId = Session.get('currentBoard');
return Integrations.find({ boardId: `${boardId}` }).fetch();
},
integration(id) {
const boardId = Session.get('currentBoard');
return Integrations.findOne({ _id: id, boardId: `${boardId}` });
},
events() {
return [{
'submit'(evt) {
evt.preventDefault();
const url = evt.target.url.value;
const boardId = Session.get('currentBoard');
let id = null;
let integration = null;
if (evt.target.id) {
id = evt.target.id.value;
integration = this.integration(id);
if (url) {
Integrations.update(integration._id, {
$set: {
url: `${url}`,
},
});
} else {
Integrations.remove(integration._id);
}
} else if (url) {
Integrations.insert({
userId: Meteor.userId(),
enabled: true,
type: 'outgoing-webhooks',
url: `${url}`,
boardId: `${boardId}`,
activities: ['all'],
});
}
Popup.close();
},
}];
},
}).register('outgoingWebhooksPopup');
BlazeComponent.extendComponent({
template() {
return 'chooseBoardSource';
},
}).register('chooseBoardSourcePopup');
Template.labelsWidget.events({
'click .js-label': Popup.open('editLabel'),
'click .js-add-label': Popup.open('createLabel'),
@@ -364,124 +258,6 @@ function draggableMembersLabelsWidgets() {
Template.membersWidget.onRendered(draggableMembersLabelsWidgets);
Template.labelsWidget.onRendered(draggableMembersLabelsWidgets);
BlazeComponent.extendComponent({
backgroundColors() {
return Boards.simpleSchema()._schema.color.allowedValues;
},
isSelected() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
return currentBoard.color === this.currentData().toString();
},
events() {
return [{
'click .js-select-background'(evt) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const newColor = this.currentData().toString();
currentBoard.setColor(newColor);
evt.preventDefault();
},
}];
},
}).register('boardChangeColorPopup');
BlazeComponent.extendComponent({
onCreated() {
this.currentBoard = Boards.findOne(Session.get('currentBoard'));
},
allowsSubtasks() {
return this.currentBoard.allowsSubtasks;
},
isBoardSelected() {
return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
},
isNullBoardSelected() {
return (this.currentBoard.subtasksDefaultBoardId === null) || (this.currentBoard.subtasksDefaultBoardId === undefined);
},
boards() {
return Boards.find({
archived: false,
'members.userId': Meteor.userId(),
}, {
sort: ['title'],
});
},
lists() {
return Lists.find({
boardId: this.currentBoard._id,
archived: false,
}, {
sort: ['title'],
});
},
hasLists() {
return this.lists().count() > 0;
},
isListSelected() {
return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
},
presentParentTask() {
let result = this.currentBoard.presentParentTask;
if ((result === null) || (result === undefined)) {
result = 'no-parent';
}
return result;
},
events() {
return [{
'click .js-field-has-subtasks'(evt) {
evt.preventDefault();
this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks;
this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks);
$('.js-field-has-subtasks .materialCheckBox').toggleClass('is-checked', this.currentBoard.allowsSubtasks);
$('.js-field-has-subtasks').toggleClass('is-checked', this.currentBoard.allowsSubtasks);
$('.js-field-deposit-board').prop('disabled', !this.currentBoard.allowsSubtasks);
},
'change .js-field-deposit-board'(evt) {
let value = evt.target.value;
if (value === 'null') {
value = null;
}
this.currentBoard.setSubtasksDefaultBoardId(value);
evt.preventDefault();
},
'change .js-field-deposit-list'(evt) {
this.currentBoard.setSubtasksDefaultListId(evt.target.value);
evt.preventDefault();
},
'click .js-field-show-parent-in-minicard'(evt) {
const value = evt.target.id || $(evt.target).parent()[0].id || $(evt.target).parent()[0].parent()[0].id;
const options = [
'prefix-with-full-path',
'prefix-with-parent',
'subtext-with-full-path',
'subtext-with-parent',
'no-parent'];
options.forEach(function(element) {
if (element !== value) {
$(`#${element} .materialCheckBox`).toggleClass('is-checked', false);
$(`#${element}`).toggleClass('is-checked', false);
}
});
$(`#${value} .materialCheckBox`).toggleClass('is-checked', true);
$(`#${value}`).toggleClass('is-checked', true);
this.currentBoard.setPresentParentTask(value);
evt.preventDefault();
},
}];
},
}).register('boardSubtaskSettingsPopup');
BlazeComponent.extendComponent({
onCreated() {
this.error = new ReactiveVar('');
+33 -51
View File
@@ -1,56 +1,38 @@
template(name="archivesSidebar")
if isArchiveReady.get
+basicTabs(tabs=tabs)
+tabContent(slug="cards")
+basicTabs(tabs=tabs)
+tabContent(slug="cards")
each archivedCards
.minicard-wrapper.js-minicard
+minicard(this)
if currentUser.isBoardMember
p.quiet
a.js-restore-all-cards {{_ 'restore-all'}}
a.js-restore-card {{_ 'restore'}}
| -
a.js-delete-all-cards {{_ 'delete-all'}}
each archivedCards
.minicard-wrapper.js-minicard
+minicard(this)
a.js-delete-card {{_ 'delete'}}
if cardIsInArchivedList
p.quiet.small ({{_ 'warn-list-archived'}})
else
p.no-items-message {{_ 'no-archived-cards'}}
+tabContent(slug="lists")
ul.archived-lists
each archivedLists
li.archived-lists-item
if currentUser.isBoardMember
p.quiet
a.js-restore-card {{_ 'restore'}}
| -
a.js-delete-card {{_ 'delete'}}
if cardIsInArchivedList
p.quiet.small ({{_ 'warn-list-archived'}})
else
p.no-items-message {{_ 'no-archived-cards'}}
button.js-restore-list
i.fa.fa-undo
= title
else
li.no-items-message {{_ 'no-archived-lists'}}
+tabContent(slug="lists")
p.quiet
a.js-restore-all-lists {{_ 'restore-all'}}
| -
a.js-delete-all-lists {{_ 'delete-all'}}
ul.archived-lists
each archivedLists
li.archived-lists-item
= title
if currentUser.isBoardMember
p.quiet
a.js-restore-list {{_ 'restore'}}
| -
a.js-delete-list {{_ 'delete'}}
else
li.no-items-message {{_ 'no-archived-lists'}}
+tabContent(slug="swimlanes")
p.quiet
a.js-restore-all-swimlanes {{_ 'restore-all'}}
| -
a.js-delete-all-swimlanes {{_ 'delete-all'}}
ul.archived-lists
each archivedSwimlanes
li.archived-lists-item
= title
if currentUser.isBoardMember
p.quiet
a.js-restore-swimlane {{_ 'restore'}}
| -
a.js-delete-swimlane {{_ 'delete'}}
else
li.no-items-message {{_ 'no-archived-swimlanes'}}
else
+spinner
+tabContent(slug="swimlanes")
ul.archived-lists
each archivedSwimlanes
li.archived-lists-item
if currentUser.isBoardMember
button.js-restore-swimlane
i.fa.fa-undo
= title
else
li.no-items-message {{_ 'no-archived-swimlanes'}}
@@ -1,26 +1,4 @@
const subManager = new SubsManager();
BlazeComponent.extendComponent({
onCreated() {
this.isArchiveReady = new ReactiveVar(false);
// The pattern we use to manually handle data loading is described here:
// https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager
// XXX The boardId should be readed from some sort the component "props",
// unfortunatly, Blaze doesn't have this notion.
this.autorun(() => {
const currentBoardId = Session.get('currentBoard');
if (!currentBoardId)
return;
const handle = subManager.subscribe('board', currentBoardId, true);
Tracker.nonreactive(() => {
Tracker.autorun(() => {
this.isArchiveReady.set( handle.ready() );
});
});
});
},
tabs() {
return [
{ name: TAPi18n.__('cards'), slug: 'cards' },
@@ -66,67 +44,19 @@ BlazeComponent.extendComponent({
card.restore();
}
},
'click .js-restore-all-cards'() {
this.archivedCards().forEach((card) => {
if(card.canBeRestored()){
card.restore();
}
});
},
'click .js-delete-card': Popup.afterConfirm('cardDelete', function() {
const cardId = this._id;
Cards.remove(cardId);
Popup.close();
}),
'click .js-delete-all-cards': Popup.afterConfirm('cardDelete', () => {
this.archivedCards().forEach((card) => {
Cards.remove(card._id);
});
Popup.close();
}),
'click .js-restore-list'() {
const list = this.currentData();
list.restore();
},
'click .js-restore-all-lists'() {
this.archivedLists().forEach((list) => {
list.restore();
});
},
'click .js-delete-list': Popup.afterConfirm('listDelete', function() {
this.remove();
Popup.close();
}),
'click .js-delete-all-lists': Popup.afterConfirm('listDelete', () => {
this.archivedLists().forEach((list) => {
list.remove();
});
Popup.close();
}),
'click .js-restore-swimlane'() {
const swimlane = this.currentData();
swimlane.restore();
},
'click .js-restore-all-swimlanes'() {
this.archivedSwimlanes().forEach((swimlane) => {
swimlane.restore();
});
},
'click .js-delete-swimlane': Popup.afterConfirm('swimlaneDelete', function() {
this.remove();
Popup.close();
}),
'click .js-delete-all-swimlanes': Popup.afterConfirm('swimlaneDelete', () => {
this.archivedSwimlanes().forEach((swimlane) => {
swimlane.remove();
});
Popup.close();
}),
}];
},
}).register('archivesSidebar');
@@ -6,9 +6,7 @@ template(name="customFieldsSidebar")
div.minicard
a.fa.fa-pencil.js-edit-custom-field.minicard-edit-button
div.minicard-title
+viewer
=name
| ({{ type }})
| {{ name }} ({{ type }})
if currentUser.isBoardMember
hr
@@ -43,16 +41,6 @@ template(name="createCustomFieldPopup")
.materialCheckBox(class="{{#if showOnCard}}is-checked{{/if}}")
span {{_ 'show-field-on-card'}}
a.flex.js-field-automatically-on-card(class="{{#if automaticallyOnCard}}is-checked{{/if}}")
.materialCheckBox(class="{{#if automaticallyOnCard}}is-checked{{/if}}")
span {{_ 'automatically-field-on-card'}}
a.flex.js-field-showLabel-on-card(class="{{#if showLabelOnMiniCard}}is-checked{{/if}}")
.materialCheckBox(class="{{#if showLabelOnMiniCard}}is-checked{{/if}}")
span {{_ 'showLabel-field-on-card'}}
button.primary.wide.left(type="button")
| {{_ 'save'}}
if _id
@@ -2,7 +2,7 @@ BlazeComponent.extendComponent({
customFields() {
return CustomFields.find({
boardIds: {$in: [Session.get('currentBoard')]},
boardId: Session.get('currentBoard'),
});
},
@@ -83,37 +83,19 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
$target.find('.materialCheckBox').toggleClass('is-checked');
$target.toggleClass('is-checked');
},
'click .js-field-automatically-on-card'(evt) {
let $target = $(evt.target);
if(!$target.hasClass('js-field-automatically-on-card')){
$target = $target.parent();
}
$target.find('.materialCheckBox').toggleClass('is-checked');
$target.toggleClass('is-checked');
},
'click .js-field-showLabel-on-card'(evt) {
let $target = $(evt.target);
if(!$target.hasClass('js-field-showLabel-on-card')){
$target = $target.parent();
}
$target.find('.materialCheckBox').toggleClass('is-checked');
$target.toggleClass('is-checked');
},
'click .primary'(evt) {
evt.preventDefault();
const data = {
boardId: Session.get('currentBoard'),
name: this.find('.js-field-name').value.trim(),
type: this.type.get(),
settings: this.getSettings(),
showOnCard: this.find('.js-field-show-on-card.is-checked') !== null,
showLabelOnMiniCard: this.find('.js-field-showLabel-on-card.is-checked') !== null,
automaticallyOnCard: this.find('.js-field-automatically-on-card.is-checked') !== null,
};
// insert or update
if (!this.data()._id) {
data.boardIds = [Session.get('currentBoard')];
CustomFields.insert(data);
} else {
CustomFields.update(this.data()._id, {$set: data});
@@ -122,16 +104,8 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
Popup.back();
},
'click .js-delete-custom-field': Popup.afterConfirm('deleteCustomField', function() {
const customField = CustomFields.findOne(this._id);
if (customField.boardIds.length > 1) {
CustomFields.update(customField._id, {
$pull: {
boardIds: Session.get('currentBoard'),
},
});
} else {
CustomFields.remove(customField._id);
}
const customFieldId = this._id;
CustomFields.remove(customFieldId);
Popup.close();
}),
}];
@@ -1,6 +1,6 @@
template(name="searchSidebar")
form.js-search-term-form
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto")
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus)
.list-body.js-perfect-scrollbar
.minicards.clearfix.js-minicards
each (results)
@@ -1,8 +0,0 @@
template(name="miniswimlane")
.minicard(
class="minicard-{{colorClass}}")
.minicard-title
.handle
.fa.fa-arrows
+viewer
= title
+10 -47
View File
@@ -1,21 +1,14 @@
template(name="swimlaneHeader")
.swimlane-header-wrap.js-swimlane-header(class='{{#if colorClass}}swimlane-{{colorClass}}{{/if}}')
if this.isTemplateContainer
+swimlaneFixedHeader(this)
.swimlane-header-wrap.js-swimlane-header
+inlinedForm
+editSwimlaneTitleForm
else
+inlinedForm
+editSwimlaneTitleForm
else
+swimlaneFixedHeader(this)
template(name="swimlaneFixedHeader")
.swimlane-header(
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
= title
.swimlane-header-menu
unless currentUser.isCommentOnly
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon
a.fa.fa-navicon.js-open-swimlane-menu
.swimlane-header(
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
= title
.swimlane-header-menu
unless currentUser.isCommentOnly
a.fa.fa-navicon.js-open-swimlane-menu
template(name="editSwimlaneTitleForm")
.list-composer
@@ -27,34 +20,4 @@ template(name="editSwimlaneTitleForm")
template(name="swimlaneActionPopup")
unless currentUser.isCommentOnly
ul.pop-over-list
li: a.js-set-swimlane-color {{_ 'select-color'}}
unless this.isTemplateContainer
hr
ul.pop-over-list
li: a.js-close-swimlane {{_ 'archive-swimlane'}}
template(name="swimlaneAddPopup")
unless currentUser.isCommentOnly
form
input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}"
autocomplete="off" autofocus)
.edit-controls.clearfix
button.primary.confirm(type="submit") {{_ 'add'}}
unless currentBoard.isTemplatesBoard
unless currentBoard.isTemplateBoard
span.quiet
| {{_ 'or'}}
a.js-swimlane-template {{_ 'template'}}
template(name="setSwimlaneColorPopup")
form.edit-label
.palette-colors: each colors
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
if(isSelected color)
i.fa.fa-check
button.primary.confirm.js-submit {{_ 'save'}}
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
template(name="swimlaneDeletePopup")
p {{_ "swimlane-delete-pop"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
li: a.js-close-swimlane {{_ 'archive-swimlane'}}
@@ -1,10 +1,3 @@
const { calculateIndexData } = Utils;
let swimlaneColors;
Meteor.startup(() => {
swimlaneColors = Swimlanes.simpleSchema()._schema.color.allowedValues;
});
BlazeComponent.extendComponent({
editTitle(evt) {
evt.preventDefault();
@@ -18,85 +11,15 @@ BlazeComponent.extendComponent({
events() {
return [{
'click .js-open-swimlane-menu': Popup.open('swimlaneAction'),
'click .js-open-add-swimlane-menu': Popup.open('swimlaneAdd'),
submit: this.editTitle,
}];
},
}).register('swimlaneHeader');
Template.swimlaneActionPopup.events({
'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'),
'click .js-close-swimlane' (evt) {
evt.preventDefault();
this.archive();
Popup.close();
},
});
BlazeComponent.extendComponent({
onCreated() {
this.currentSwimlane = this.currentData();
},
events() {
return [{
submit(evt) {
evt.preventDefault();
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const nextSwimlane = currentBoard.nextSwimlane(this.currentSwimlane);
const titleInput = this.find('.swimlane-name-input');
const title = titleInput.value.trim();
const sortValue = calculateIndexData(this.currentSwimlane, nextSwimlane, 1);
const swimlaneType = (currentBoard.isTemplatesBoard())?'template-swimlane':'swimlane';
if (title) {
Swimlanes.insert({
title,
boardId: Session.get('currentBoard'),
sort: sortValue.base,
type: swimlaneType,
});
titleInput.value = '';
titleInput.focus();
}
// XXX ideally, we should move the popup to the newly
// created swimlane so a user can add more than one swimlane
// with a minimum of interactions
Popup.close();
},
'click .js-swimlane-template': Popup.open('searchElement'),
}];
},
}).register('swimlaneAddPopup');
BlazeComponent.extendComponent({
onCreated() {
this.currentSwimlane = this.currentData();
this.currentColor = new ReactiveVar(this.currentSwimlane.color);
},
colors() {
return swimlaneColors.map((color) => ({ color, name: '' }));
},
isSelected(color) {
return this.currentColor.get() === color;
},
events() {
return [{
'click .js-palette-color'() {
this.currentColor.set(this.currentData().color);
},
'click .js-submit' () {
this.currentSwimlane.setColor(this.currentColor.get());
Popup.close();
},
'click .js-remove-color'() {
this.currentSwimlane.setColor(null);
Popup.close();
},
}];
},
}).register('setSwimlaneColorPopup');
+36 -16
View File
@@ -1,22 +1,21 @@
template(name="swimlane")
.swimlane
+swimlaneHeader
.swimlane.js-lists.js-swimlane
+swimlaneHeader
if isMiniScreen
if currentListIsInThisSwimlane _id
if currentList
+list(currentList)
unless currentList
each lists
else
each currentBoard.lists
+miniList(this)
if currentUser.isBoardMember
+addListForm
else
each lists
each currentBoard.lists
+list(this)
if currentCardIsInThisList _id ../_id
+cardDetails(currentCard)
if currentUser.isBoardMember
+addListForm
+addListAndSwimlaneForm
template(name="listsGroup")
.swimlane.list-group.js-lists
@@ -24,31 +23,52 @@ template(name="listsGroup")
if currentList
+list(currentList)
else
each lists
each currentBoard.lists
+miniList(this)
if currentUser.isBoardMember
+addListForm
else
each lists
each currentBoard.lists
+list(this)
if currentCardIsInThisList _id null
+cardDetails(currentCard)
if currentUser.isBoardMember
+addListForm
template(name="addListForm")
template(name="addListAndSwimlaneForm")
.list.list-composer.js-list-composer
.list-header-add
.list-header
+inlinedForm(autoclose=false)
input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
autocomplete="off" autofocus)
.edit-controls.clearfix
button.primary.confirm(type="submit") {{_ 'save'}}
unless currentBoard.isTemplatesBoard
unless currentBoard.isTemplateBoard
span.quiet
| {{_ 'or'}}
a.js-list-template {{_ 'template'}}
a.fa.fa-times-thin.js-close-inlined-form
else
a.open-list-composer.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-list'}}
.list-header
+inlinedForm(autoclose=false)
input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}"
autocomplete="off" autofocus)
.edit-controls.clearfix
button.primary.confirm(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
else
a.open-list-composer.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-swimlane'}}
template(name="addListForm")
.list.list-composer.js-list-composer
.list-header
+inlinedForm(autoclose=false)
input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
autocomplete="off" autofocus)
.edit-controls.clearfix
button.primary.confirm(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
else
a.open-list-composer.js-open-inlined-form
i.fa.fa-plus
+49 -29
View File
@@ -1,25 +1,16 @@
const { calculateIndex, enableClickOnTouch } = Utils;
function currentListIsInThisSwimlane(swimlaneId) {
const currentList = Lists.findOne(Session.get('currentList'));
return currentList && (currentList.swimlaneId === swimlaneId || currentList.swimlaneId === '');
}
function currentCardIsInThisList(listId, swimlaneId) {
const currentCard = Cards.findOne(Session.get('currentCard'));
const currentUser = Meteor.user();
if (currentUser && currentUser.profile && currentUser.profile.boardView === 'board-view-swimlanes')
return currentCard && currentCard.listId === listId && currentCard.swimlaneId === swimlaneId;
else // Default view: board-view-lists
if (currentUser.profile.boardView === 'board-view-lists')
return currentCard && currentCard.listId === listId;
// https://github.com/wekan/wekan/issues/1623
// https://github.com/ChronikEwok/wekan/commit/cad9b20451bb6149bfb527a99b5001873b06c3de
// TODO: In public board, if you would like to switch between List/Swimlane view, you could
// 1) If there is no view cookie, save to cookie board-view-lists
// board-view-lists / board-view-swimlanes / board-view-cal
// 2) If public user changes clicks board-view-lists then change view and
// then change view and save cookie with view value
// without using currentuser above, because currentuser is null.
else if (currentUser.profile.boardView === 'board-view-swimlanes')
return currentCard && currentCard.listId === listId && currentCard.swimlaneId === swimlaneId;
else if (currentUser.profile.boardView === 'board-view-cal')
return currentCard;
else
return false;
}
function initSortable(boardComponent, $listsDom) {
@@ -119,10 +110,6 @@ BlazeComponent.extendComponent({
return currentCardIsInThisList(listId, swimlaneId);
},
currentListIsInThisSwimlane(swimlaneId) {
return currentListIsInThisSwimlane(swimlaneId);
},
events() {
return [{
// Click-and-drag action
@@ -162,12 +149,6 @@ BlazeComponent.extendComponent({
}).register('swimlane');
BlazeComponent.extendComponent({
onCreated() {
this.currentBoard = Boards.findOne(Session.get('currentBoard'));
this.isListTemplatesSwimlane = this.currentBoard.isTemplatesBoard() && this.currentData().isListTemplatesSwimlane();
this.currentSwimlane = this.currentData();
},
// Proxy
open() {
this.childComponents('inlinedForm')[0].open();
@@ -184,19 +165,58 @@ BlazeComponent.extendComponent({
title,
boardId: Session.get('currentBoard'),
sort: $('.list').length,
type: (this.isListTemplatesSwimlane)?'template-list':'list',
swimlaneId: (this.currentBoard.isTemplatesBoard())?this.currentSwimlane._id:'',
});
titleInput.value = '';
titleInput.focus();
}
},
'click .js-list-template': Popup.open('searchElement'),
}];
},
}).register('addListForm');
BlazeComponent.extendComponent({
// Proxy
open() {
this.childComponents('inlinedForm')[0].open();
},
events() {
return [{
submit(evt) {
evt.preventDefault();
let titleInput = this.find('.list-name-input');
if (titleInput) {
const title = titleInput.value.trim();
if (title) {
Lists.insert({
title,
boardId: Session.get('currentBoard'),
sort: $('.list').length,
});
titleInput.value = '';
titleInput.focus();
}
} else {
titleInput = this.find('.swimlane-name-input');
const title = titleInput.value.trim();
if (title) {
Swimlanes.insert({
title,
boardId: Session.get('currentBoard'),
sort: $('.swimlane').length,
});
titleInput.value = '';
titleInput.focus();
}
}
},
}];
},
}).register('addListAndSwimlaneForm');
Template.swimlane.helpers({
canSeeAddList() {
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
+11 -94
View File
@@ -7,7 +7,7 @@
display: flex
flex-direction: row
overflow: 0;
max-height: calc(100% - 26px)
max-height: 100%
&.placeholder
background-color: rgba(0, 0, 0, .2)
@@ -27,15 +27,20 @@
.swimlane-header-wrap
display: flex;
flex-direction: row;
flex: 1 0 100%;
background-color: #ccc;
flex: 0 0 50px;
padding-bottom: 30px;
border-bottom: 1px solid #CCC
.swimlane-header
-ms-writing-mode: tb-rl;
writing-mode: vertical-rl;
transform: rotate(180deg);
font-size: 14px;
padding: 5px 5px
line-height: 50px;
margin-top: 50px;
font-weight: bold;
min-height: 9px;
width: 100%;
width: 50px;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
@@ -44,95 +49,7 @@
.swimlane-header-menu
position: absolute
padding: 5px 5px
.swimlane-header-plus-icon
margin-left: 5px
margin-right: 10px
padding: 20px 20px
.list-group
height: 100%
.moving-swimlane
display: none
swimlane-color(background, color...)
background: background !important
if color
color: color !important //overwrite text for better visibility
.swimlane-white
swimlane-color(#ffffff, #4d4d4d) //Black text for better visibility
border: 1px solid #eee
.swimlane-green
swimlane-color(#3cb500, #ffffff) //White text for better visibility
.swimlane-yellow
swimlane-color(#fad900, #4d4d4d) //Black text for better visibility
.swimlane-orange
swimlane-color(#ff9f19, #4d4d4d) //Black text for better visibility
.swimlane-red
swimlane-color(#eb4646, #ffffff) //White text for better visibility
.swimlane-purple
swimlane-color(#a632db, #ffffff) //White text for better visibility
.swimlane-blue
swimlane-color(#0079bf, #ffffff) //White text for better visibility
.swimlane-pink
swimlane-color(#ff78cb, #4d4d4d) //Black text for better visibility
.swimlane-sky
swimlane-color(#00c2e0, #ffffff) //White text for better visibility
.swimlane-black
swimlane-color(#4d4d4d, #ffffff) //White text for better visibility
.swimlane-lime
swimlane-color(#51e898, #4d4d4d) //Black text for better visibility
.swimlane-silver
swimlane-color(unset, #4d4d4d) //Black text for better visibility
.swimlane-peachpuff
swimlane-color(#ffdab9, #4d4d4d) //Black text for better visibility
.swimlane-crimson
swimlane-color(#dc143c, #ffffff) //White text for better visibility
.swimlane-plum
swimlane-color(#dda0dd, #4d4d4d) //Black text for better visibility
.swimlane-darkgreen
swimlane-color(#006400, #ffffff) //White text for better visibility
.swimlane-slateblue
swimlane-color(#6a5acd, #ffffff) //White text for better visibility
.swimlane-magenta
swimlane-color(#ff00ff, #ffffff) //White text for better visibility
.swimlane-gold
swimlane-color(#ffd700, #4d4d4d) //Black text for better visibility
.swimlane-navy
swimlane-color(#000080, #ffffff) //White text for better visibility
.swimlane-gray
swimlane-color(#808080, #ffffff) //White text for better visibility
.swimlane-saddlebrown
swimlane-color(#8b4513, #ffffff) //White text for better visibility
.swimlane-paleturquoise
swimlane-color(#afeeee, #4d4d4d) //Black text for better visibility
.swimlane-mistyrose
swimlane-color(#ffe4e1, #4d4d4d) //Black text for better visibility
.swimlane-indigo
swimlane-color(#4b0082, #ffffff) //White text for better visibility
+8 -17
View File
@@ -4,11 +4,10 @@ template(name="headerUserBar")
.header-user-bar-avatar
+userAvatar(userId=currentUser._id)
unless isMiniScreen
unless isSandstorm
if currentUser.profile.fullname
= currentUser.profile.fullname
else
= currentUser.username
if currentUser.profile.fullname
= currentUser.profile.fullname
else
= currentUser.username
template(name="memberMenuPopup")
ul.pop-over-list
@@ -16,18 +15,13 @@ template(name="memberMenuPopup")
li: a.js-edit-profile {{_ 'edit-profile'}}
li: a.js-change-settings {{_ 'change-settings'}}
li: a.js-change-avatar {{_ 'edit-avatar'}}
unless isSandstorm
li: a.js-change-password {{_ 'changePasswordPopup-title'}}
li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
li: a.js-change-password {{_ 'changePasswordPopup-title'}}
li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
if currentUser.isAdmin
li: a.js-go-setting(href="{{pathFor 'setting'}}") {{_ 'admin-panel'}}
hr
ul.pop-over-list
li: a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") {{_ 'templates'}}
unless isSandstorm
hr
ul.pop-over-list
li: a.js-logout {{_ 'log-out'}}
li: a.js-logout {{_ 'log-out'}}
template(name="editProfilePopup")
form
@@ -53,10 +47,7 @@ template(name="editProfilePopup")
input.js-profile-email(type="email" value="{{emails.[0].address}}")
else
input.js-profile-email(type="email" value="{{emails.[0].address}}" readonly)
div.buttonsContainer
input.primary.wide(type="submit" value="{{_ 'save'}}")
div
input#deleteButton.primary.wide(type="button" value="{{_ 'delete'}}")
input.primary.wide(type="submit" value="{{_ 'save'}}")
template(name="changePasswordPopup")
+atForm(state='changePwd')
-16
View File
@@ -3,15 +3,6 @@ Template.headerUserBar.events({
'click .js-change-avatar': Popup.open('changeAvatar'),
});
Template.memberMenuPopup.helpers({
templatesBoardId() {
return Meteor.user().getTemplatesBoardId();
},
templatesBoardSlug() {
return Meteor.user().getTemplatesBoardSlug();
},
});
Template.memberMenuPopup.events({
'click .js-edit-profile': Popup.open('editProfile'),
'click .js-change-settings': Popup.open('changeSettings'),
@@ -95,11 +86,6 @@ Template.editProfilePopup.events({
});
} else Popup.back();
},
'click #deleteButton'() {
Users.remove(Meteor.userId());
Popup.close();
AccountsTemplates.logout();
},
});
// XXX For some reason the useraccounts autofocus isnt working in this case.
@@ -119,8 +105,6 @@ Template.changeLanguagePopup.helpers({
name = 'Brezhoneg';
} else if (lang.name === 'ig') {
name = 'Igbo';
} else if (lang.name === 'oc') {
name = 'Occitan';
}
return { tag, name };
}).sort(function (a, b) {
+1 -1
View File
@@ -135,6 +135,6 @@ $(document).on('click', (evt) => {
}
});
$(document).on('click', 'a[href=\\#]', (evt) => {
$(document).on('click', 'a[href=#]', (evt) => {
evt.preventDefault();
});
+7 -5
View File
@@ -4,9 +4,9 @@ window.Popup = new class {
this.template = Template.popup;
// We only want to display one popup at a time and we keep the view object
// in this `Popup.current` variable. If there is no popup currently opened
// in this `Popup._current` variable. If there is no popup currently opened
// the value is `null`.
this.current = null;
this._current = null;
// It's possible to open a sub-popup B from a popup A. In that case we keep
// the data of popup A so we can return back to it. Every time we open a new
@@ -27,9 +27,11 @@ window.Popup = new class {
open(name) {
const self = this;
const popupName = `${name}Popup`;
function clickFromPopup(evt) {
return $(evt.target).closest('.js-pop-over').length !== 0;
}
return function(evt) {
// If a popup is already opened, clicking again on the opener element
// should close it -- and interrupt the current `open` function.
@@ -55,6 +57,7 @@ window.Popup = new class {
self._stack = [];
openerElement = evt.currentTarget;
}
$(openerElement).addClass('is-active');
evt.preventDefault();
@@ -136,7 +139,6 @@ window.Popup = new class {
const openerElement = this._getTopStack().openerElement;
$(openerElement).removeClass('is-active');
this._stack = [];
}
}
@@ -184,7 +186,7 @@ window.Popup = new class {
// positives.
const title = TAPi18n.__(translationKey);
// when popup showed as full of small screen, we need a default header to clearly see [X] button
const defaultTitle = Utils.isMiniScreen() ? '' : false;
const defaultTitle = Utils.isMiniScreen() ? 'Wekan' : false;
return title !== translationKey ? title : defaultTitle;
};
}
@@ -198,7 +200,7 @@ escapeActions.forEach((actionName) => {
() => Popup[actionName](),
() => Popup.isOpen(),
{
noClickEscapeOn: '.js-pop-over,.js-open-card-title-popup',
noClickEscapeOn: '.js-pop-over',
enabledOnClick: actionName === 'close',
}
);
+1 -31
View File
@@ -145,26 +145,6 @@ Utils = {
});
},
manageCustomUI(){
Meteor.call('getCustomUI', (err, data) => {
if (err && err.error[0] === 'var-not-exist'){
Session.set('customUI', false); // siteId || address server not defined
}
if (!err){
Utils.setCustomUI(data);
}
});
},
setCustomUI(data){
const currentBoard = Boards.findOne(Session.get('currentBoard'));
if (currentBoard) {
DocHead.setTitle(`${currentBoard.title } - ${ data.productName}`);
} else {
DocHead.setTitle(`${data.productName}`);
}
},
setMatomo(data){
window._paq = window._paq || [];
window._paq.push(['setDoNotTrack', data.doNotTrack]);
@@ -218,20 +198,10 @@ Utils = {
const element = tempInstance.$(triggerEls[i]);
if (element.hasClass('trigger-text')) {
finalString += element.text().toLowerCase();
} else if (element.hasClass('user-details')) {
let username = element.find('input').val();
if(username === undefined || username === ''){
username = '*';
}
finalString += `${element.find('.trigger-text').text().toLowerCase() } ${ username}`;
} else if (element.find('select').length > 0) {
finalString += element.find('select option:selected').text().toLowerCase();
} else if (element.find('input').length > 0) {
let inputvalue = element.find('input').val();
if(inputvalue === undefined || inputvalue === ''){
inputvalue = '*';
}
finalString += inputvalue;
finalString += element.find('input').val();
}
// Add space
if (i !== length - 1) {
+11 -16
View File
@@ -14,7 +14,6 @@ FlowRouter.route('/', {
Filter.reset();
EscapeActions.executeAll();
Utils.manageCustomUI();
Utils.manageMatomo();
BlazeLayout.render('defaultLayout', {
@@ -41,7 +40,6 @@ FlowRouter.route('/b/:id/:slug', {
EscapeActions.executeUpTo('popup-close');
}
Utils.manageCustomUI();
Utils.manageMatomo();
BlazeLayout.render('defaultLayout', {
@@ -59,7 +57,6 @@ FlowRouter.route('/b/:boardId/:slug/:cardId', {
Session.set('currentBoard', params.boardId);
Session.set('currentCard', params.cardId);
Utils.manageCustomUI();
Utils.manageMatomo();
BlazeLayout.render('defaultLayout', {
@@ -125,7 +122,6 @@ FlowRouter.route('/setting', {
},
],
action() {
Utils.manageCustomUI();
BlazeLayout.render('defaultLayout', {
headerBar: 'settingHeaderBar',
content: 'setting',
@@ -203,21 +199,20 @@ _.each(redirections, (newPath, oldPath) => {
// using the `kadira:dochead` package. Currently we only use it to display the
// board title if we are in a board page (see #364) but we may want to support
// some <meta> tags in the future.
//const appTitle = Utils.manageCustomUI();
const appTitle = 'Wekan';
// XXX The `Meteor.startup` should not be necessary -- we don't need to wait for
// the complete DOM to be ready to call `DocHead.setTitle`. But the problem is
// that the global variable `Boards` is undefined when this file loads so we
// wait a bit until hopefully all files are loaded. This will be fixed in a
// clean way once Meteor will support ES6 modules -- hopefully in Meteor 1.3.
//Meteor.isClient && Meteor.startup(() => {
// Tracker.autorun(() => {
// const currentBoard = Boards.findOne(Session.get('currentBoard'));
// const titleStack = [appTitle];
// if (currentBoard) {
// titleStack.push(currentBoard.title);
// }
// DocHead.setTitle(titleStack.reverse().join(' - '));
// });
//});
Meteor.isClient && Meteor.startup(() => {
Tracker.autorun(() => {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const titleStack = [appTitle];
if (currentBoard) {
titleStack.push(currentBoard.title);
}
DocHead.setTitle(titleStack.reverse().join(' - '));
});
});
+221
View File
@@ -0,0 +1,221 @@
version: '2'
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
# 1) Edit settings
# 2) docker-compose up -d
services:
wekandb:
image: mongo:3.2.21
container_name: wekan-db
restart: always
command: mongod --smallfiles --oplogSize 128
networks:
- wekan-tier
expose:
- 27017
volumes:
- wekan-db:/data/db
- wekan-db-dump:/dump
wekan:
image: quay.io/wekan/wekan
container_name: wekan-app
restart: always
networks:
- wekan-tier
build:
context: .
dockerfile: Dockerfile
args:
- NODE_VERSION=${NODE_VERSION}
- METEOR_RELEASE=${METEOR_RELEASE}
- NPM_VERSION=${NPM_VERSION}
- ARCHITECTURE=${ARCHITECTURE}
- SRC_PATH=${SRC_PATH}
- METEOR_EDGE=${METEOR_EDGE}
- USE_EDGE=${USE_EDGE}
ports:
# Docker outsideport:insideport
- 80:8080
environment:
- MONGO_URL=mongodb://wekandb:27017/wekan
- ROOT_URL=http://localhost
# Wekan Export Board works when WITH_API=true.
# If you disable Wekan API with false, Export Board does not work.
- WITH_API=true
# Optional: Integration with Matomo https://matomo.org that is installed to your server
# The address of the server where Matomo is hosted.
# example: - MATOMO_ADDRESS=https://example.com/matomo
#- MATOMO_ADDRESS=
# The value of the site ID given in Matomo server for Wekan
# example: - MATOMO_SITE_ID=12345
#- MATOMO_SITE_ID=
# The option do not track which enables users to not be tracked by matomo
# example: - MATOMO_DO_NOT_TRACK=false
#- MATOMO_DO_NOT_TRACK=
# The option that allows matomo to retrieve the username:
# example: MATOMO_WITH_USERNAME=true
#- MATOMO_WITH_USERNAME=false
# Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside.
# Setting this to false is not recommended, it also disables all other browser policy protections
# and allows all iframing etc. See wekan/server/policy.js
- BROWSER_POLICY_ENABLED=true
# When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside.
#- TRUSTED_URL=
# What to send to Outgoing Webhook, or leave out. Example, that includes all that are default: cardId,listId,oldListId,boardId,comment,user,card,commentId .
# example: WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId
#- WEBHOOKS_ATTRIBUTES=
# Enable the OAuth2 connection
# example: OAUTH2_ENABLED=true
#- OAUTH2_ENABLED=false
# OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2
# OAuth2 Client ID, for example from Rocket.Chat. Example: abcde12345
# example: OAUTH2_CLIENT_ID=abcde12345
#- OAUTH2_CLIENT_ID=
# OAuth2 Secret, for example from Rocket.Chat: Example: 54321abcde
# example: OAUTH2_SECRET=54321abcde
#- OAUTH2_SECRET=
# OAuth2 Server URL, for example Rocket.Chat. Example: https://chat.example.com
# example: OAUTH2_SERVER_URL=https://chat.example.com
#- OAUTH2_SERVER_URL=
# OAuth2 Authorization Endpoint. Example: /oauth/authorize
# example: OAUTH2_AUTH_ENDPOINT=/oauth/authorize
#- OAUTH2_AUTH_ENDPOINT=
# OAuth2 Userinfo Endpoint. Example: /oauth/userinfo
# example: OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo
#- OAUTH2_USERINFO_ENDPOINT=
# OAuth2 Token Endpoint. Example: /oauth/token
# example: OAUTH2_TOKEN_ENDPOINT=/oauth/token
#- OAUTH2_TOKEN_ENDPOINT=
# LDAP_ENABLE : Enable or not the connection by the LDAP
# example : LDAP_ENABLE=true
#- LDAP_ENABLE=false
# LDAP_PORT : The port of the LDAP server
# example : LDAP_PORT=389
#- LDAP_PORT=389
# LDAP_HOST : The host server for the LDAP server
# example : LDAP_HOST=localhost
#- LDAP_HOST=
# LDAP_BASEDN : The base DN for the LDAP Tree
# example : LDAP_BASEDN=ou=user,dc=example,dc=org
#- LDAP_BASEDN=
# LDAP_LOGIN_FALLBACK : Fallback on the default authentication method
# example : LDAP_LOGIN_FALLBACK=true
#- LDAP_LOGIN_FALLBACK=false
# LDAP_RECONNECT : Reconnect to the server if the connection is lost
# example : LDAP_RECONNECT=false
#- LDAP_RECONNECT=true
# LDAP_TIMEOUT : Overall timeout, in milliseconds
# example : LDAP_TIMEOUT=12345
#- LDAP_TIMEOUT=10000
# LDAP_IDLE_TIMEOUT : Specifies the timeout for idle LDAP connections in milliseconds
# example : LDAP_IDLE_TIMEOUT=12345
#- LDAP_IDLE_TIMEOUT=10000
# LDAP_CONNECT_TIMEOUT : Connection timeout, in milliseconds
# example : LDAP_CONNECT_TIMEOUT=12345
#- LDAP_CONNECT_TIMEOUT=10000
# LDAP_AUTHENTIFICATION : If the LDAP needs a user account to search
# example : LDAP_AUTHENTIFICATION=true
#- LDAP_AUTHENTIFICATION=false
# LDAP_AUTHENTIFICATION_USERDN : The search user DN
# example : LDAP_AUTHENTIFICATION_USERDN=cn=admin,dc=example,dc=org
#- LDAP_AUTHENTIFICATION_USERDN=
# LDAP_AUTHENTIFICATION_PASSWORD : The password for the search user
# example : AUTHENTIFICATION_PASSWORD=admin
#- LDAP_AUTHENTIFICATION_PASSWORD=
# LDAP_LOG_ENABLED : Enable logs for the module
# example : LDAP_LOG_ENABLED=true
#- LDAP_LOG_ENABLED=false
# LDAP_BACKGROUND_SYNC : If the sync of the users should be done in the background
# example : LDAP_BACKGROUND_SYNC=true
#- LDAP_BACKGROUND_SYNC=false
# LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds
# example : LDAP_BACKGROUND_SYNC_INTERVAL=12345
#- LDAP_BACKGROUND_SYNC_INTERVAL=100
# LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED :
# example : LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=true
#- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
# LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS :
# example : LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=true
#- LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false
# LDAP_ENCRYPTION : If using LDAPS
# example : LDAP_ENCRYPTION=ssl
#- LDAP_ENCRYPTION=false
# LDAP_CA_CERT : The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file.
# example : LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+zCCA+OgAwIBAgIkAhwR/6TVLmdRY6hHxvUFWc0+Enmu/Hu6cj+G2FIdAgIC...-----END CERTIFICATE-----
#- LDAP_CA_CERT=
# LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
# example : LDAP_REJECT_UNAUTHORIZED=true
#- LDAP_REJECT_UNAUTHORIZED=false
# LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
# example : LDAP_USER_SEARCH_FILTER=
#- LDAP_USER_SEARCH_FILTER=
# LDAP_USER_SEARCH_SCOPE : base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree)
# example : LDAP_USER_SEARCH_SCOPE=one
#- LDAP_USER_SEARCH_SCOPE=
# LDAP_USER_SEARCH_FIELD : Which field is used to find the user
# example : LDAP_USER_SEARCH_FIELD=uid
#- LDAP_USER_SEARCH_FIELD=
# LDAP_SEARCH_PAGE_SIZE : Used for pagination (0=unlimited)
# example : LDAP_SEARCH_PAGE_SIZE=12345
#- LDAP_SEARCH_PAGE_SIZE=0
# LDAP_SEARCH_SIZE_LIMIT : The limit number of entries (0=unlimited)
# example : LDAP_SEARCH_SIZE_LIMIT=12345
#- LDAP_SEARCH_SIZE_LIMIT=0
# LDAP_GROUP_FILTER_ENABLE : Enable group filtering
# example : LDAP_GROUP_FILTER_ENABLE=true
#- LDAP_GROUP_FILTER_ENABLE=false
# LDAP_GROUP_FILTER_OBJECTCLASS : The object class for filtering
# example : LDAP_GROUP_FILTER_OBJECTCLASS=group
#- LDAP_GROUP_FILTER_OBJECTCLASS=
# LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE :
# example :
#- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=
# LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE :
# example :
#- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=
# LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT :
# example :
#- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=
# LDAP_GROUP_FILTER_GROUP_NAME :
# example :
#- LDAP_GROUP_FILTER_GROUP_NAME=
# LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier)
# example : LDAP_UNIQUE_IDENTIFIER_FIELD=guid
#- LDAP_UNIQUE_IDENTIFIER_FIELD=
# LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8
# example : LDAP_UTF8_NAMES_SLUGIFY=false
#- LDAP_UTF8_NAMES_SLUGIFY=true
# LDAP_USERNAME_FIELD : Which field contains the ldap username
# example : LDAP_USERNAME_FIELD=username
#- LDAP_USERNAME_FIELD=
# LDAP_MERGE_EXISTING_USERS :
# example : LDAP_MERGE_EXISTING_USERS=true
#- LDAP_MERGE_EXISTING_USERS=false
# LDAP_SYNC_USER_DATA :
# example : LDAP_SYNC_USER_DATA=true
#- LDAP_SYNC_USER_DATA=false
# LDAP_SYNC_USER_DATA_FIELDMAP :
# example : LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
#- LDAP_SYNC_USER_DATA_FIELDMAP=
# LDAP_SYNC_GROUP_ROLES :
# example :
#- LDAP_SYNC_GROUP_ROLES=
# LDAP_DEFAULT_DOMAIN : The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
# example :
#- LDAP_DEFAULT_DOMAIN=
depends_on:
- wekandb
volumes:
wekan-db:
driver: local
wekan-db-dump:
driver: local
networks:
wekan-tier:
driver: bridge
+244
View File
@@ -0,0 +1,244 @@
version: '2'
# Docker: Wekan <=> MongoDB <=> ToroDB => PostgreSQL read-only mirroring
# for reporting with SQL, and accessing with any programming language,
# reporting package and Office suite that can connect to PostgreSQL.
# https://github.com/wekan/wekan-postgresql
services:
torodb-stampede:
image: torodb/stampede:1.0.0-SNAPSHOT
networks:
- wekan-tier
links:
- postgres
- mongodb
environment:
- POSTGRES_PASSWORD
- TORODB_SETUP=true
- TORODB_SYNC_SOURCE=mongodb:27017
- TORODB_BACKEND_HOST=postgres
- TORODB_BACKEND_PORT=5432
- TORODB_BACKEND_DATABASE=wekan
- TORODB_BACKEND_USER=wekan
- TORODB_BACKEND_PASSWORD=wekan
- DEBUG
postgres:
image: postgres:9.6
networks:
- wekan-tier
environment:
- POSTGRES_PASSWORD
ports:
- "15432:5432"
mongodb:
image: mongo:3.2
networks:
- wekan-tier
ports:
- "28017:27017"
entrypoint:
- /bin/bash
- "-c"
- mongo --nodb --eval '
var db;
while (!db) {
try {
db = new Mongo("mongodb:27017").getDB("local");
} catch(ex) {}
sleep(3000);
};
rs.initiate({_id:"rs1",members:[{_id:0,host:"mongodb:27017"}]});
' 1>/dev/null 2>&1 &
mongod --replSet rs1
wekan:
image: quay.io/wekan/wekan
container_name: wekan-app
restart: always
networks:
- wekan-tier
ports:
- 80:8080
environment:
- MONGO_URL=mongodb://mongodb:27017/wekan
- ROOT_URL=http://localhost
#---------------------------------------------------------------
# == WEKAN API ==
# Wekan Export Board works when WITH_API='true'.
# If you disable Wekan API, Export Board does not work.
- WITH_API=true
# Optional: Integration with Matomo https://matomo.org that is installed to your server
# The address of the server where Matomo is hosted.
# example: - MATOMO_ADDRESS=https://example.com/matomo
#- MATOMO_ADDRESS=
# The value of the site ID given in Matomo server for Wekan
# example: - MATOMO_SITE_ID=12345
#- MATOMO_SITE_ID=
# The option do not track which enables users to not be tracked by matomo
# example: - MATOMO_DO_NOT_TRACK=false
#- MATOMO_DO_NOT_TRACK=
# The option that allows matomo to retrieve the username:
# example: MATOMO_WITH_USERNAME=true
#- MATOMO_WITH_USERNAME=false
# Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside.
# Setting this to false is not recommended, it also disables all other browser policy protections
# and allows all iframing etc. See wekan/server/policy.js
- BROWSER_POLICY_ENABLED=true
# When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside.
#- TRUSTED_URL=
# What to send to Outgoing Webhook, or leave out. Example, that includes all that are default: cardId,listId,oldListId,boardId,comment,user,card,commentId .
# example: WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId
#- WEBHOOKS_ATTRIBUTES=
# Enable the OAuth2 connection
# example: OAUTH2_ENABLED=true
#- OAUTH2_ENABLED=false
# OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2
# OAuth2 Client ID, for example from Rocket.Chat. Example: abcde12345
# example: OAUTH2_CLIENT_ID=abcde12345
#- OAUTH2_CLIENT_ID=
# OAuth2 Secret, for example from Rocket.Chat: Example: 54321abcde
# example: OAUTH2_SECRET=54321abcde
#- OAUTH2_SECRET=
# OAuth2 Server URL, for example Rocket.Chat. Example: https://chat.example.com
# example: OAUTH2_SERVER_URL=https://chat.example.com
#- OAUTH2_SERVER_URL=
# OAuth2 Authorization Endpoint. Example: /oauth/authorize
# example: OAUTH2_AUTH_ENDPOINT=/oauth/authorize
#- OAUTH2_AUTH_ENDPOINT=
# OAuth2 Userinfo Endpoint. Example: /oauth/userinfo
# example: OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo
#- OAUTH2_USERINFO_ENDPOINT=
# OAuth2 Token Endpoint. Example: /oauth/token
# example: OAUTH2_TOKEN_ENDPOINT=/oauth/token
#- OAUTH2_TOKEN_ENDPOINT=
# LDAP_ENABLE : Enable or not the connection by the LDAP
# example : LDAP_ENABLE=true
#- LDAP_ENABLE=false
# LDAP_PORT : The port of the LDAP server
# example : LDAP_PORT=389
#- LDAP_PORT=389
# LDAP_HOST : The host server for the LDAP server
# example : LDAP_HOST=localhost
#- LDAP_HOST=
# LDAP_BASEDN : The base DN for the LDAP Tree
# example : LDAP_BASEDN=ou=user,dc=example,dc=org
#- LDAP_BASEDN=
# LDAP_LOGIN_FALLBACK : Fallback on the default authentication method
# example : LDAP_LOGIN_FALLBACK=true
#- LDAP_LOGIN_FALLBACK=false
# LDAP_RECONNECT : Reconnect to the server if the connection is lost
# example : LDAP_RECONNECT=false
#- LDAP_RECONNECT=true
# LDAP_TIMEOUT : Overall timeout, in milliseconds
# example : LDAP_TIMEOUT=12345
#- LDAP_TIMEOUT=10000
# LDAP_IDLE_TIMEOUT : Specifies the timeout for idle LDAP connections in milliseconds
# example : LDAP_IDLE_TIMEOUT=12345
#- LDAP_IDLE_TIMEOUT=10000
# LDAP_CONNECT_TIMEOUT : Connection timeout, in milliseconds
# example : LDAP_CONNECT_TIMEOUT=12345
#- LDAP_CONNECT_TIMEOUT=10000
# LDAP_AUTHENTIFICATION : If the LDAP needs a user account to search
# example : LDAP_AUTHENTIFICATION=true
#- LDAP_AUTHENTIFICATION=false
# LDAP_AUTHENTIFICATION_USERDN : The search user DN
# example : LDAP_AUTHENTIFICATION_USERDN=cn=admin,dc=example,dc=org
#- LDAP_AUTHENTIFICATION_USERDN=
# LDAP_AUTHENTIFICATION_PASSWORD : The password for the search user
# example : AUTHENTIFICATION_PASSWORD=admin
#- LDAP_AUTHENTIFICATION_PASSWORD=
# LDAP_LOG_ENABLED : Enable logs for the module
# example : LDAP_LOG_ENABLED=true
#- LDAP_LOG_ENABLED=false
# LDAP_BACKGROUND_SYNC : If the sync of the users should be done in the background
# example : LDAP_BACKGROUND_SYNC=true
#- LDAP_BACKGROUND_SYNC=false
# LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds
# example : LDAP_BACKGROUND_SYNC_INTERVAL=12345
#- LDAP_BACKGROUND_SYNC_INTERVAL=100
# LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED :
# example : LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=true
#- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
# LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS :
# example : LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=true
#- LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false
# LDAP_ENCRYPTION : If using LDAPS
# example : LDAP_ENCRYPTION=ssl
#- LDAP_ENCRYPTION=false
# LDAP_CA_CERT : The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file.
# example : LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+zCCA+OgAwIBAgIkAhwR/6TVLmdRY6hHxvUFWc0+Enmu/Hu6cj+G2FIdAgIC...-----END CERTIFICATE-----
#- LDAP_CA_CERT=
# LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
# example : LDAP_REJECT_UNAUTHORIZED=true
#- LDAP_REJECT_UNAUTHORIZED=false
# LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
# example : LDAP_USER_SEARCH_FILTER=
#- LDAP_USER_SEARCH_FILTER=
# LDAP_USER_SEARCH_SCOPE : base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree)
# example : LDAP_USER_SEARCH_SCOPE=one
#- LDAP_USER_SEARCH_SCOPE=
# LDAP_USER_SEARCH_FIELD : Which field is used to find the user
# example : LDAP_USER_SEARCH_FIELD=uid
#- LDAP_USER_SEARCH_FIELD=
# LDAP_SEARCH_PAGE_SIZE : Used for pagination (0=unlimited)
# example : LDAP_SEARCH_PAGE_SIZE=12345
#- LDAP_SEARCH_PAGE_SIZE=0
# LDAP_SEARCH_SIZE_LIMIT : The limit number of entries (0=unlimited)
# example : LDAP_SEARCH_SIZE_LIMIT=12345
#- LDAP_SEARCH_SIZE_LIMIT=0
# LDAP_GROUP_FILTER_ENABLE : Enable group filtering
# example : LDAP_GROUP_FILTER_ENABLE=true
#- LDAP_GROUP_FILTER_ENABLE=false
# LDAP_GROUP_FILTER_OBJECTCLASS : The object class for filtering
# example : LDAP_GROUP_FILTER_OBJECTCLASS=group
#- LDAP_GROUP_FILTER_OBJECTCLASS=
# LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE :
# example :
#- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=
# LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE :
# example :
#- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=
# LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT :
# example :
#- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=
# LDAP_GROUP_FILTER_GROUP_NAME :
# example :
#- LDAP_GROUP_FILTER_GROUP_NAME=
# LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier)
# example : LDAP_UNIQUE_IDENTIFIER_FIELD=guid
#- LDAP_UNIQUE_IDENTIFIER_FIELD=
# LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8
# example : LDAP_UTF8_NAMES_SLUGIFY=false
#- LDAP_UTF8_NAMES_SLUGIFY=true
# LDAP_USERNAME_FIELD : Which field contains the ldap username
# example : LDAP_USERNAME_FIELD=username
#- LDAP_USERNAME_FIELD=
# LDAP_MERGE_EXISTING_USERS :
# example : LDAP_MERGE_EXISTING_USERS=true
#- LDAP_MERGE_EXISTING_USERS=false
# LDAP_SYNC_USER_DATA :
# example : LDAP_SYNC_USER_DATA=true
#- LDAP_SYNC_USER_DATA=false
# LDAP_SYNC_USER_DATA_FIELDMAP :
# example : LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
#- LDAP_SYNC_USER_DATA_FIELDMAP=
# LDAP_SYNC_GROUP_ROLES :
# example :
#- LDAP_SYNC_GROUP_ROLES=
# LDAP_DEFAULT_DOMAIN : The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
# example :
#- LDAP_DEFAULT_DOMAIN=
depends_on:
- mongodb
volumes:
mongodb:
driver: local
mongodb-dump:
driver: local
networks:
wekan-tier:
driver: bridge
+135 -495
View File
@@ -1,101 +1,13 @@
version: '2'
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
#---------------------------------------------------------------------------------------------------------
# ==== CREATING USERS AND LOGGING IN TO WEKAN ====
# https://github.com/wekan/wekan/wiki/Adding-users
#---------------------------------------------------------------------------------------------------------
# ==== FORGOT PASSWORD ====
# https://github.com/wekan/wekan/wiki/Forgot-Password
#---------------------------------------------------------------------------------------------------------
# ==== Upgrading Wekan to new version =====
# 1) Stop Wekan:
# docker-compose stop
# 2) Download new version:
# docker-compose pull wekan
# 3) If you have more networks for VPN etc as described at bottom of
# this config, download for them too:
# docker-compose pull wekan2
# 4) Start Wekan:
# docker-compose start
#----------------------------------------------------------------------------------
# ==== OPTIONAL: DEDICATED DOCKER USER ====
# 1) Optionally create a dedicated user for Wekan, for example:
# sudo useradd -d /home/wekan -m -s /bin/bash wekan
# 2) Add this user to the docker group, then logout+login or reboot:
# sudo usermod -aG docker wekan
# 3) Then login as user wekan.
# 4) Create this file /home/wekan/docker-compose.yml with your modifications.
#----------------------------------------------------------------------------------
# ==== RUN DOCKER AS SERVICE ====
# 1a) Running Docker as service, on Systemd like Debian 9, Ubuntu 16.04, CentOS 7:
# sudo systemctl enable docker
# sudo systemctl start docker
# 1b) Running Docker as service, on init.d like Debian 8, Ubuntu 14.04, CentOS 6:
# sudo update-rc.d docker defaults
# sudo service docker start
# ----------------------------------------------------------------------------------
# ==== USAGE OF THIS docker-compose.yml ====
# 1) For seeing does Wekan work, try this and check with your webbroser:
# docker-compose up
# 2) Stop Wekan and start Wekan in background:
# docker-compose stop
# docker-compose up -d
# 3) See running Docker containers:
# docker ps
# 4) Stop Docker containers:
# docker-compose stop
# ----------------------------------------------------------------------------------
# ===== INSIDE DOCKER CONTAINERS, AND BACKUP/RESTORE ====
# https://github.com/wekan/wekan/wiki/Backup
# If really necessary, repair MongoDB: https://github.com/wekan/wekan-mongodb/issues/6#issuecomment-424004116
# 1) Going inside containers:
# a) Wekan app, does not contain data
# docker exec -it wekan-app bash
# b) MongoDB, contains all data
# docker exec -it wekan-db bash
# 2) Copying database to outside of container:
# docker exec -it wekan-db bash
# cd /data
# mongodump
# exit
# docker cp wekan-db:/data/dump .
# 3) Restoring database
# # 1) Stop wekan
# docker stop wekan-app
# # 2) Go inside database container
# docker exec -it wekan-db bash
# # 3) and data directory
# cd /data
# # 4) Remove previos dump
# rm -rf dump
# # 5) Exit db container
# exit
# # 6) Copy dump to inside docker container
# docker cp dump wekan-db:/data/
# # 7) Go inside database container
# docker exec -it wekan-db bash
# # 8) and data directory
# cd /data
# # 9) Restore
# mongorestore --drop
# # 10) Exit db container
# exit
# # 11) Start wekan
# docker start wekan-app
#-------------------------------------------------------------------------
# 1) Edit settings
# 2) docker-compose up -d
services:
wekandb:
#-------------------------------------------------------------------------------------
# ==== MONGODB AND METEOR VERSION ====
# a) For Wekan Meteor 1.8.x version at meteor-1.8 branch, use mongo 4.x
#image: mongo:4.0.4
# b) For Wekan Meteor 1.6.x version at master/devel/edge branches.
# Only for Snap and Sandstorm while they are not upgraded yet to Meteor 1.8.x
image: mongo:3.2.21
#-------------------------------------------------------------------------------------
container_name: wekan-db
restart: always
command: mongod --smallfiles --oplogSize 128
@@ -108,457 +20,185 @@ services:
- wekan-db-dump:/dump
wekan:
#-------------------------------------------------------------------------------------
# ==== MONGODB AND METEOR VERSION ====
# a) For Wekan Meteor 1.8.x version at meteor-1.8 branch,
# using https://quay.io/wekan/wekan automatic builds
#image: quay.io/wekan/wekan:meteor-1.8
# b) For Wekan Meteor 1.6.x version at master/devel/edge branches.
# Only for Snap and Sandstorm while they are not upgraded yet to Meteor 1.8.x
image: quay.io/wekan/wekan:master
# c) Using specific Meteor 1.6.x version tag:
# image: quay.io/wekan/wekan:v1.95
# c) Using Docker Hub automatic builds https://hub.docker.com/r/wekanteam/wekan
# image: wekanteam/wekan:meteor-1.8
# image: wekanteam/wekan:v1.95
#-------------------------------------------------------------------------------------
image: quay.io/wekan/wekan
container_name: wekan-app
restart: always
networks:
- wekan-tier
#-------------------------------------------------------------------------------------
# ==== BUILD wekan-app DOCKER CONTAINER FROM SOURCE, if you uncomment these ====
# ==== and use commands: docker-compose up -d --build
#build:
# context: .
# dockerfile: Dockerfile
# args:
# - NODE_VERSION=${NODE_VERSION}
# - METEOR_RELEASE=${METEOR_RELEASE}
# - NPM_VERSION=${NPM_VERSION}
# - ARCHITECTURE=${ARCHITECTURE}
# - SRC_PATH=${SRC_PATH}
# - METEOR_EDGE=${METEOR_EDGE}
# - USE_EDGE=${USE_EDGE}
#-------------------------------------------------------------------------------------
ports:
# Docker outsideport:insideport. Do not add anything extra here.
# For example, if you want to have wekan on port 3001,
# use 3001:8080 . Do not add any extra address etc here, that way it does not work.
# remove port mapping if you use nginx reverse proxy, port 8080 is already exposed to wekan-tier network
# Docker outsideport:insideport
- 80:8080
environment:
- MONGO_URL=mongodb://wekandb:27017/wekan
#---------------------------------------------------------------
# ==== ROOT_URL SETTING ====
# Change ROOT_URL to your real Wekan URL, for example:
# If you have Caddy/Nginx/Apache providing SSL
# - https://example.com
# - https://boards.example.com
# This can be problematic with avatars https://github.com/wekan/wekan/issues/1776
# - https://example.com/wekan
# If without https, can be only wekan node, no need for Caddy/Nginx/Apache if you don't need them
# - http://example.com
# - http://boards.example.com
# - http://192.168.1.100 <=== using at local LAN
- ROOT_URL=http://localhost # <=== using only at same laptop/desktop where Wekan is installed
#---------------------------------------------------------------
# ==== EMAIL SETTINGS ====
# Email settings are required in both MAIL_URL and Admin Panel,
# see https://github.com/wekan/wekan/wiki/Troubleshooting-Mail
# For SSL in email, change smtp:// to smtps://
# NOTE: Special characters need to be url-encoded in MAIL_URL.
# You can encode those characters for example at: https://www.urlencoder.org
#- MAIL_URL=smtp://user:pass@mailserver.example.com:25/
- MAIL_URL=smtp://<mail_url>:25/?ignoreTLS=true&tls={rejectUnauthorized:false}
- MAIL_FROM=Wekan Notifications <noreply.wekan@mydomain.com>
#---------------------------------------------------------------
# ==== OPTIONAL: MONGO OPLOG SETTINGS =====
# https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-378343587
# We've fixed our CPU usage problem today with an environment
# change around Wekan. I wasn't aware during implementation
# that if you're using more than 1 instance of Wekan
# (or any MeteorJS based tool) you're supposed to set
# MONGO_OPLOG_URL as an environment variable.
# Without setting it, Meteor will perform a pull-and-diff
# update of it's dataset. With it, Meteor will update from
# the OPLOG. See here
# https://blog.meteor.com/tuning-meteor-mongo-livedata-for-scalability-13fe9deb8908
# After setting
# MONGO_OPLOG_URL=mongodb://<username>:<password>@<mongoDbURL>/local?authSource=admin&replicaSet=rsWekan
# the CPU usage for all Wekan instances dropped to an average
# of less than 10% with only occasional spikes to high usage
# (I guess when someone is doing a lot of work)
# - MONGO_OPLOG_URL=mongodb://<username>:<password>@<mongoDbURL>/local?authSource=admin&replicaSet=rsWekan
#---------------------------------------------------------------
# ==== OPTIONAL: KADIRA PERFORMANCE MONITORING FOR METEOR ====
# https://github.com/edemaine/kadira-compose
# https://github.com/meteor/meteor-apm-agent
# https://blog.meteor.com/kadira-apm-is-now-open-source-490469ffc85f
#- APM_OPTIONS_ENDPOINT=http://<kadira-ip>:11011
#- APM_APP_ID=
#- APM_APP_SECRET=
#---------------------------------------------------------------
# ==== OPTIONAL: LOGS AND STATS ====
# https://github.com/wekan/wekan/wiki/Logs
#
# Daily export of Wekan changes as JSON to Logstash and ElasticSearch / Kibana (ELK)
# https://github.com/wekan/wekan-logstash
#
# Statistics Python script for Wekan Dashboard
# https://github.com/wekan/wekan-stats
#
# Console, file, and zulip logger on database changes https://github.com/wekan/wekan/pull/1010
# with fix to replace console.log by winston logger https://github.com/wekan/wekan/pull/1033
# but there could be bug https://github.com/wekan/wekan/issues/1094
#
# 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
#---------------------------------------------------------------
# ==== WEKAN API AND EXPORT BOARD ====
- ROOT_URL=http://localhost
# Wekan Export Board works when WITH_API=true.
# https://github.com/wekan/wekan/wiki/REST-API
# https://github.com/wekan/wekan-gogs
# If you disable Wekan API with false, Export Board does not work.
- WITH_API=true
#---------------------------------------------------------------
# ==== PASSWORD BRUTE FORCE PROTECTION ====
#https://atmospherejs.com/lucasantoniassi/accounts-lockout
#Defaults below. Uncomment to change. wekan/server/accounts-lockout.js
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60
#- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60
#- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15
#---------------------------------------------------------------
# ==== EMAIL NOTIFICATION TIMEOUT, ms =====
# Defaut: 30000 ms = 30s
#- EMAIL_NOTIFICATION_TIMEOUT=30000
#-----------------------------------------------------------------
# ==== CORS =====
# CORS: Set Access-Control-Allow-Origin header.
#- CORS=*
# CORS_ALLOW_HEADERS: Set Access-Control-Allow-Headers header. "Authorization,Content-Type" is required for cross-origin use of the API.
#- CORS_ALLOW_HEADERS=Authorization,Content-Type
# CORS_EXPOSE_HEADERS: Set Access-Control-Expose-Headers header. This is not needed for typical CORS situations
#- CORS_EXPOSE_HEADERS=*
#-----------------------------------------------------------------
# ==== MATOMO INTEGRATION ====
# Optional: Integration with Matomo https://matomo.org that is installed to your server
# The address of the server where Matomo is hosted.
#- MATOMO_ADDRESS=https://example.com/matomo
# example: - MATOMO_ADDRESS=https://example.com/matomo
#- MATOMO_ADDRESS=
# The value of the site ID given in Matomo server for Wekan
#- MATOMO_SITE_ID=1
# example: - MATOMO_SITE_ID=12345
#- MATOMO_SITE_ID=
# The option do not track which enables users to not be tracked by matomo
#- MATOMO_DO_NOT_TRACK=true
# example: - MATOMO_DO_NOT_TRACK=false
#- MATOMO_DO_NOT_TRACK=
# The option that allows matomo to retrieve the username:
#- MATOMO_WITH_USERNAME=true
#-----------------------------------------------------------------
# ==== BROWSER POLICY AND TRUSTED IFRAME URL ====
# example: MATOMO_WITH_USERNAME=true
#- MATOMO_WITH_USERNAME=false
# Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside.
# Setting this to false is not recommended, it also disables all other browser policy protections
# and allows all iframing etc. See wekan/server/policy.js
- BROWSER_POLICY_ENABLED=true
# When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside.
#- TRUSTED_URL=https://intra.example.com
#-----------------------------------------------------------------
# ==== OUTGOING WEBHOOKS ====
#- TRUSTED_URL=
# What to send to Outgoing Webhook, or leave out. Example, that includes all that are default: cardId,listId,oldListId,boardId,comment,user,card,commentId .
#- WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId
#-----------------------------------------------------------------
# ==== Debug OIDC OAuth2 etc ====
#- DEBUG=true
#-----------------------------------------------------------------
# ==== OAUTH2 AZURE ====
# https://github.com/wekan/wekan/wiki/Azure
# 1) Register the application with Azure. Make sure you capture
# the application ID as well as generate a secret key.
# 2) Configure the environment variables. This differs slightly
# by installation type, but make sure you have the following:
#- OAUTH2_ENABLED=true
# OAuth2 login style: popup or redirect.
#- OAUTH2_LOGIN_STYLE=redirect
# Application GUID captured during app registration:
#- OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx
# Secret key generated during app registration:
#- OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#- OAUTH2_SERVER_URL=https://login.microsoftonline.com/
#- OAUTH2_AUTH_ENDPOINT=/oauth2/v2.0/authorize
#- OAUTH2_USERINFO_ENDPOINT=https://graph.microsoft.com/oidc/userinfo
#- OAUTH2_TOKEN_ENDPOINT=/oauth2/v2.0/token
# The claim name you want to map to the unique ID field:
#- OAUTH2_ID_MAP=email
# The claim name you want to map to the username field:
#- OAUTH2_USERNAME_MAP=email
# The claim name you want to map to the full name field:
#- OAUTH2_FULLNAME_MAP=name
# Tthe claim name you want to map to the email field:
#- OAUTH2_EMAIL_MAP=email
#-----------------------------------------------------------------
# ==== OAUTH2 KEYCLOAK ====
# https://github.com/wekan/wekan/wiki/Keycloak <== MAPPING INFO, REQUIRED
#- OAUTH2_ENABLED=true
# OAuth2 login style: popup or redirect.
#- OAUTH2_LOGIN_STYLE=redirect
#- OAUTH2_CLIENT_ID=<Keycloak create Client ID>
#- OAUTH2_SERVER_URL=<Keycloak server name>/auth
#- OAUTH2_AUTH_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/auth
#- OAUTH2_USERINFO_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/userinfo
#- OAUTH2_TOKEN_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/token
#- OAUTH2_SECRET=<keycloak client secret>
#-----------------------------------------------------------------
# ==== OAUTH2 DOORKEEPER ====
# https://github.com/wekan/wekan/issues/1874
# https://github.com/wekan/wekan/wiki/OAuth2
# example: WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId
#- WEBHOOKS_ATTRIBUTES=
# Enable the OAuth2 connection
#- OAUTH2_ENABLED=true
# example: OAUTH2_ENABLED=true
#- OAUTH2_ENABLED=false
# OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2
# OAuth2 login style: popup or redirect.
#- OAUTH2_LOGIN_STYLE=redirect
# OAuth2 Client ID.
#- OAUTH2_CLIENT_ID=abcde12345
# OAuth2 Secret.
#- OAUTH2_SECRET=54321abcde
# OAuth2 Server URL.
#- OAUTH2_SERVER_URL=https://chat.example.com
# OAuth2 Authorization Endpoint.
#- OAUTH2_AUTH_ENDPOINT=/oauth/authorize
# OAuth2 Userinfo Endpoint.
#- OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo
# OAuth2 Token Endpoint.
#- OAUTH2_TOKEN_ENDPOINT=/oauth/token
# OAUTH2 ID Token Whitelist Fields.
#- OAUTH2_ID_TOKEN_WHITELIST_FIELDS=""
# OAUTH2 Request Permissions.
#- OAUTH2_REQUEST_PERMISSIONS=openid profile email
# OAuth2 ID Mapping
#- OAUTH2_ID_MAP=
# OAuth2 Username Mapping
#- OAUTH2_USERNAME_MAP=
# OAuth2 Fullname Mapping
#- OAUTH2_FULLNAME_MAP=
# OAuth2 Email Mapping
#- OAUTH2_EMAIL_MAP=
#-----------------------------------------------------------------
# ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ====
# https://github.com/wekan/wekan/wiki/LDAP
# For Snap settings see https://github.com/wekan/wekan-snap/wiki/Supported-settings-keys
# Most settings work both on Snap and Docker below.
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
#
# The default authentication method used if a user does not exist to create and authenticate. Can be set as ldap.
#- DEFAULT_AUTHENTICATION_METHOD=ldap
#
# Enable or not the connection by the LDAP
#- LDAP_ENABLE=true
#
# The port of the LDAP server
# OAuth2 Client ID, for example from Rocket.Chat. Example: abcde12345
# example: OAUTH2_CLIENT_ID=abcde12345
#- OAUTH2_CLIENT_ID=
# OAuth2 Secret, for example from Rocket.Chat: Example: 54321abcde
# example: OAUTH2_SECRET=54321abcde
#- OAUTH2_SECRET=
# OAuth2 Server URL, for example Rocket.Chat. Example: https://chat.example.com
# example: OAUTH2_SERVER_URL=https://chat.example.com
#- OAUTH2_SERVER_URL=
# OAuth2 Authorization Endpoint. Example: /oauth/authorize
# example: OAUTH2_AUTH_ENDPOINT=/oauth/authorize
#- OAUTH2_AUTH_ENDPOINT=
# OAuth2 Userinfo Endpoint. Example: /oauth/userinfo
# example: OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo
#- OAUTH2_USERINFO_ENDPOINT=
# OAuth2 Token Endpoint. Example: /oauth/token
# example: OAUTH2_TOKEN_ENDPOINT=/oauth/token
#- OAUTH2_TOKEN_ENDPOINT=
# LDAP_ENABLE : Enable or not the connection by the LDAP
# example : LDAP_ENABLE=true
#- LDAP_ENABLE=false
# LDAP_PORT : The port of the LDAP server
# example : LDAP_PORT=389
#- LDAP_PORT=389
#
# The host server for the LDAP server
#- LDAP_HOST=localhost
#
# The base DN for the LDAP Tree
#- LDAP_BASEDN=ou=user,dc=example,dc=org
#
# Fallback on the default authentication method
# LDAP_HOST : The host server for the LDAP server
# example : LDAP_HOST=localhost
#- LDAP_HOST=
# LDAP_BASEDN : The base DN for the LDAP Tree
# example : LDAP_BASEDN=ou=user,dc=example,dc=org
#- LDAP_BASEDN=
# LDAP_LOGIN_FALLBACK : Fallback on the default authentication method
# example : LDAP_LOGIN_FALLBACK=true
#- LDAP_LOGIN_FALLBACK=false
#
# Reconnect to the server if the connection is lost
# LDAP_RECONNECT : Reconnect to the server if the connection is lost
# example : LDAP_RECONNECT=false
#- LDAP_RECONNECT=true
#
# Overall timeout, in milliseconds
# LDAP_TIMEOUT : Overall timeout, in milliseconds
# example : LDAP_TIMEOUT=12345
#- LDAP_TIMEOUT=10000
#
# Specifies the timeout for idle LDAP connections in milliseconds
# LDAP_IDLE_TIMEOUT : Specifies the timeout for idle LDAP connections in milliseconds
# example : LDAP_IDLE_TIMEOUT=12345
#- LDAP_IDLE_TIMEOUT=10000
#
# Connection timeout, in milliseconds
# LDAP_CONNECT_TIMEOUT : Connection timeout, in milliseconds
# example : LDAP_CONNECT_TIMEOUT=12345
#- LDAP_CONNECT_TIMEOUT=10000
#
# If the LDAP needs a user account to search
#- LDAP_AUTHENTIFICATION=true
#
# The search user DN
#- LDAP_AUTHENTIFICATION_USERDN=cn=wekan_adm,ou=serviceaccounts,ou=admin,ou=prod,dc=mydomain,dc=com
#
# The password for the search user
#- LDAP_AUTHENTIFICATION_PASSWORD=pwd
#
# Enable logs for the module
#- LDAP_LOG_ENABLED=true
#
# If the sync of the users should be done in the background
# LDAP_AUTHENTIFICATION : If the LDAP needs a user account to search
# example : LDAP_AUTHENTIFICATION=true
#- LDAP_AUTHENTIFICATION=false
# LDAP_AUTHENTIFICATION_USERDN : The search user DN
# example : LDAP_AUTHENTIFICATION_USERDN=cn=admin,dc=example,dc=org
#- LDAP_AUTHENTIFICATION_USERDN=
# LDAP_AUTHENTIFICATION_PASSWORD : The password for the search user
# example : AUTHENTIFICATION_PASSWORD=admin
#- LDAP_AUTHENTIFICATION_PASSWORD=
# LDAP_LOG_ENABLED : Enable logs for the module
# example : LDAP_LOG_ENABLED=true
#- LDAP_LOG_ENABLED=false
# LDAP_BACKGROUND_SYNC : If the sync of the users should be done in the background
# example : LDAP_BACKGROUND_SYNC=true
#- LDAP_BACKGROUND_SYNC=false
#
# At which interval does the background task sync in milliseconds
# LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds
# example : LDAP_BACKGROUND_SYNC_INTERVAL=12345
#- LDAP_BACKGROUND_SYNC_INTERVAL=100
#
# LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED :
# example : LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=true
#- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
#
# LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS :
# example : LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=true
#- LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false
#
# If using LDAPS: LDAP_ENCRYPTION=ssl
# LDAP_ENCRYPTION : If using LDAPS
# example : LDAP_ENCRYPTION=ssl
#- LDAP_ENCRYPTION=false
#
# The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file.
#- LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+G2FIdAgIC...-----END CERTIFICATE-----
#
# Reject Unauthorized Certificate
# LDAP_CA_CERT : The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file.
# example : LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+zCCA+OgAwIBAgIkAhwR/6TVLmdRY6hHxvUFWc0+Enmu/Hu6cj+G2FIdAgIC...-----END CERTIFICATE-----
#- LDAP_CA_CERT=
# LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
# example : LDAP_REJECT_UNAUTHORIZED=true
#- LDAP_REJECT_UNAUTHORIZED=false
#
# Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key).
#- LDAP_USER_AUTHENTICATION="true"
#
# Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
# LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
# example : LDAP_USER_SEARCH_FILTER=
#- LDAP_USER_SEARCH_FILTER=
#
# base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree)
#- LDAP_USER_SEARCH_SCOPE=one
#
# Which field is used to find the user, like uid / sAMAccountName
#- LDAP_USER_SEARCH_FIELD=sAMAccountName
#
# Used for pagination (0=unlimited)
# LDAP_USER_SEARCH_SCOPE : base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree)
# example : LDAP_USER_SEARCH_SCOPE=one
#- LDAP_USER_SEARCH_SCOPE=
# LDAP_USER_SEARCH_FIELD : Which field is used to find the user
# example : LDAP_USER_SEARCH_FIELD=uid
#- LDAP_USER_SEARCH_FIELD=
# LDAP_SEARCH_PAGE_SIZE : Used for pagination (0=unlimited)
# example : LDAP_SEARCH_PAGE_SIZE=12345
#- LDAP_SEARCH_PAGE_SIZE=0
#
# The limit number of entries (0=unlimited)
# LDAP_SEARCH_SIZE_LIMIT : The limit number of entries (0=unlimited)
# example : LDAP_SEARCH_SIZE_LIMIT=12345
#- LDAP_SEARCH_SIZE_LIMIT=0
#
# Enable group filtering
# LDAP_GROUP_FILTER_ENABLE : Enable group filtering
# example : LDAP_GROUP_FILTER_ENABLE=true
#- LDAP_GROUP_FILTER_ENABLE=false
#
# The object class for filtering. Example: group
# LDAP_GROUP_FILTER_OBJECTCLASS : The object class for filtering
# example : LDAP_GROUP_FILTER_OBJECTCLASS=group
#- LDAP_GROUP_FILTER_OBJECTCLASS=
#
#- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=
#
#- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=
#
#- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=
#
#- LDAP_GROUP_FILTER_GROUP_NAME=
#
# LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier). Example: guid
#- LDAP_UNIQUE_IDENTIFIER_FIELD=
#
# LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8
#- LDAP_UTF8_NAMES_SLUGIFY=true
#
# LDAP_USERNAME_FIELD : Which field contains the ldap username. username / sAMAccountName
#- LDAP_USERNAME_FIELD=sAMAccountName
#
# LDAP_FULLNAME_FIELD : Which field contains the ldap fullname. fullname / sAMAccountName
#- LDAP_FULLNAME_FIELD=fullname
#
#- LDAP_MERGE_EXISTING_USERS=false
#
# Allow existing account matching by e-mail address when username does not match
#- LDAP_EMAIL_MATCH_ENABLE=true
#
# LDAP_EMAIL_MATCH_REQUIRE : require existing account matching by e-mail address when username does match
#- LDAP_EMAIL_MATCH_REQUIRE=true
#
# LDAP_EMAIL_MATCH_VERIFIED : require existing account email address to be verified for matching
#- LDAP_EMAIL_MATCH_VERIFIED=true
#
# LDAP_EMAIL_FIELD : which field contains the LDAP e-mail address
#- LDAP_EMAIL_FIELD=mail
#-----------------------------------------------------------------
#- LDAP_SYNC_USER_DATA=false
#
#- LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
#
#- LDAP_SYNC_GROUP_ROLES=''
#
# The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
# LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE :
# example :
#- LDAP_DEFAULT_DOMAIN=mydomain.com
#
# Enable/Disable syncing of admin status based on ldap groups:
#- LDAP_SYNC_ADMIN_STATUS=true
#
# Comma separated list of admin group names to sync.
#- LDAP_SYNC_ADMIN_GROUPS=group1,group2
#---------------------------------------------------------------------
# Login to LDAP automatically with HTTP header.
# In below example for siteminder, at right side of = is header name.
#- HEADER_LOGIN_ID=HEADERUID
#- HEADER_LOGIN_FIRSTNAME=HEADERFIRSTNAME
#- HEADER_LOGIN_LASTNAME=HEADERLASTNAME
#- HEADER_LOGIN_EMAIL=HEADEREMAILADDRESS
#---------------------------------------------------------------------
# ==== LOGOUT TIMER, probably does not work yet ====
# LOGOUT_WITH_TIMER : Enables or not the option logout with timer
# example : LOGOUT_WITH_TIMER=true
#- LOGOUT_WITH_TIMER=
#
# LOGOUT_IN : The number of days
# example : LOGOUT_IN=1
#- LOGOUT_IN=
#
# LOGOUT_ON_HOURS : The number of hours
# example : LOGOUT_ON_HOURS=9
#- LOGOUT_ON_HOURS=
#
# LOGOUT_ON_MINUTES : The number of minutes
# example : LOGOUT_ON_MINUTES=55
#- LOGOUT_ON_MINUTES=
#-------------------------------------------------------------------
#- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=
# LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE :
# example :
#- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=
# LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT :
# example :
#- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=
# LDAP_GROUP_FILTER_GROUP_NAME :
# example :
#- LDAP_GROUP_FILTER_GROUP_NAME=
# LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier)
# example : LDAP_UNIQUE_IDENTIFIER_FIELD=guid
#- LDAP_UNIQUE_IDENTIFIER_FIELD=
# LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8
# example : LDAP_UTF8_NAMES_SLUGIFY=false
#- LDAP_UTF8_NAMES_SLUGIFY=true
# LDAP_USERNAME_FIELD : Which field contains the ldap username
# example : LDAP_USERNAME_FIELD=username
#- LDAP_USERNAME_FIELD=
# LDAP_MERGE_EXISTING_USERS :
# example : LDAP_MERGE_EXISTING_USERS=true
#- LDAP_MERGE_EXISTING_USERS=false
# LDAP_SYNC_USER_DATA :
# example : LDAP_SYNC_USER_DATA=true
#- LDAP_SYNC_USER_DATA=false
# LDAP_SYNC_USER_DATA_FIELDMAP :
# example : LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"}
#- LDAP_SYNC_USER_DATA_FIELDMAP=
# LDAP_SYNC_GROUP_ROLES :
# example :
#- LDAP_SYNC_GROUP_ROLES=
# LDAP_DEFAULT_DOMAIN : The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP
# example :
#- LDAP_DEFAULT_DOMAIN=
depends_on:
- wekandb
#---------------------------------------------------------------------------------
# ==== OPTIONAL: SHARE DATABASE TO OFFICE LAN AND REMOTE VPN ====
# When using Wekan both at office LAN and remote VPN:
# 1) Have above Wekan docker container config with LAN IP address
# 2) Copy all of above wekan container config below, look above of this part above and all config below it,
# before above depends_on: part:
#
# wekan:
# #-------------------------------------------------------------------------------------
# # ==== MONGODB AND METEOR VERSION ====
# # a) For Wekan Meteor 1.8.x version at meteor-1.8 branch, .....
#
#
# and change name to different name like wekan2 or wekanvpn, and change ROOT_URL to server VPN IP
# address.
# 3) This way both Wekan containers can use same MongoDB database
# and see the same Wekan boards.
# 4) You could also add 3rd Wekan container for 3rd network etc.
# EXAMPLE:
# wekan2:
# ....COPY CONFIG FROM ABOVE TO HERE...
# environment:
# - ROOT_URL='http://10.10.10.10'
# ...COPY CONFIG FROM ABOVE TO HERE...
#---------------------------------------------------------------------------------
# OPTIONAL NGINX CONFIG FOR REVERSE PROXY
# nginx:
# image: nginx
# container_name: nginx
# restart: always
# networks:
# - wekan-tier
# depends_on:
# - wekan
# ports:
# - 80:80
# - 443:443
# volumes:
# - ./nginx/ssl:/etc/nginx/ssl/:ro
# - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
## Alternative volume config:
## volumes:
## - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
## - ./nginx/ssl/ssl.conf:/etc/nginx/conf.d/ssl/ssl.conf:ro
## - ./nginx/ssl/testvm-ehu.crt:/etc/nginx/conf.d/ssl/certs/mycert.crt:ro
## - ./nginx/ssl/testvm-ehu.key:/etc/nginx/conf.d/ssl/certs/mykey.key:ro
## - ./nginx/ssl/pphrase:/etc/nginx/conf.d/ssl/pphrase:ro
volumes:
wekan-db:
driver: local
-16
View File
@@ -1,16 +0,0 @@
#!/bin/bash
# Find text from all subdirectories
# and ignore all temporary directories:
# - node-modules = installed node modules
# - .build = Wekan bundle that is combined from source. Do not edit these, these are deleted and recreated.
# - .meteor = Meteor version, packages etc at .meteor/local
# - .git = git history
# If less or more that 1 parameter, show usage.
if (( $# != 1 )); then
echo 'Usage: ./find.sh text-to-find'
exit 0
fi
find . | grep -v node_modules | grep -v .build | grep -v .meteor | grep -v .git | xargs grep --no-messages $1 | less
-22
View File
@@ -1,22 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
OWNERS
-13
View File
@@ -1,13 +0,0 @@
name: wekan
version: 1.0.0
appVersion: 2.x.x
kubeVersion: "^1.8.0-0"
description: Open Source kanban
home: https://wekan.github.io/
icon: https://wekan.github.io/wekan-logo.svg
sources:
- https://github.com/wekan/wekan
maintainers:
- name: technotaff
email: github@randall.cc
engine: gotpl
-4
View File
@@ -1,4 +0,0 @@
approvers:
- technotaff
reviewers:
- technotaff
-58
View File
@@ -1,58 +0,0 @@
# Helm Chart for Wekan
## Features
o Uses a MongoDB replica set by default - this allows fault-tolerant
and scalable MongoDB deployment (or just set the replicas to 1 for
a single server install)
o Optional Horizontal Pod Autoscaler (HPA), so that your Wekan pods
will scale automatically with increased CPU load.
## The configurable values (values.yaml)
Scaling Wekan:
```yaml
## Configuration for wekan component
##
replicaCount: 1
```
**replicaCount** Will set the initial number of replicas for the Wekan pod (and container)
```yaml
## Configure an horizontal pod autoscaler
##
autoscaling:
enabled: true
config:
minReplicas: 1
maxReplicas: 16
## Note: when setting this, a `resources.request.cpu` is required. You
## likely want to set it to `1` or some lower value.
##
targetCPUUtilizationPercentage: 80
```
This section (if *enabled* is set to **true**) will enable the Kubernetes Horizontal Pod Autoscaler (HPA).
**minReplicas:** this is the minimum number of pods to scale down to (We recommend setting this to the same value as **replicaCount**).
**maxReplicas:** this is the maximum number of pods to scale up to.
**targetCPUUtilizationPercentage:** This is the CPU at which the HPA will scale-out the number of Wekan pods.
```yaml
mongodb-replicaset:
enabled: true
replicas: 3
replicaSetName: rs0
securityContext:
runAsUser: 1000
fsGroup: 1000
runAsNonRoot: true
```
This section controls the scale of the MongoDB redundant Replica Set.
**replicas:** This is the number of MongoDB instances to include in the set. You can set this to 1 for a single server - this will still allow you to scale-up later with a helm upgrade.
Loaded 100 of 582 files, more files were not shown because too many files have changed in this diff. Show more