mirror of
https://github.com/wekan/wekan.git
synced 2025-04-23 13:37:09 -04:00
Merge branch 'master' of https://github.com/wekan/wekan into lib-change
This commit is contained in:
commit
4b196d5378
175 changed files with 13493 additions and 1971 deletions
|
@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND=noninteractive
|
|||
|
||||
ENV \
|
||||
DEBUG=false \
|
||||
NODE_VERSION=8.16.1 \
|
||||
NODE_VERSION=8.17.0 \
|
||||
METEOR_RELEASE=1.8.1 \
|
||||
USE_EDGE=false \
|
||||
METEOR_EDGE=1.5-beta.17 \
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
packages/*
|
||||
.snap-meteor-1.8/*
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
"allowIsBoardMemberByCard": true,
|
||||
"allowIsBoardMemberCommentOnly": true,
|
||||
"allowIsBoardMemberNoComments": true,
|
||||
"allowIsBoardMemberWorker": true,
|
||||
"Emoji": true,
|
||||
"Checklists": true,
|
||||
"Settings": true,
|
||||
|
|
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
|
@ -2,6 +2,8 @@
|
|||
|
||||
Add these issues to elsewhere:
|
||||
- Snap: https://github.com/wekan/wekan-snap/issues
|
||||
- LDAP: https://github.com/wekan/wekan-ldap/issues
|
||||
- UCS: https://github.com/wekan/univention/issues
|
||||
|
||||
Other Wekan issues can be added here.
|
||||
|
||||
|
|
17
.github/workflows/dockerimage.yml
vendored
Normal file
17
.github/workflows/dockerimage.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
name: Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file Dockerfile --tag wekan:$(date +%s)
|
|
@ -17,3 +17,4 @@ notices-for-facebook-graph-api-2
|
|||
1.4.3-split-account-service-packages
|
||||
1.5-add-dynamic-import-package
|
||||
1.7-split-underscore-from-meteor-base
|
||||
1.8.3-split-jquery-from-blaze
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
meteor-base@1.4.0
|
||||
|
||||
# Build system
|
||||
ecmascript@0.12.4
|
||||
standard-minifier-css@1.5.3
|
||||
standard-minifier-js@2.4.1
|
||||
ecmascript@0.14.0
|
||||
standard-minifier-css@1.6.0
|
||||
standard-minifier-js@2.6.0
|
||||
mquandalle:jade
|
||||
|
||||
# Polyfills
|
||||
|
@ -22,7 +22,7 @@ dburles:collection-helpers
|
|||
idmontie:migrations
|
||||
matb33:collection-hooks
|
||||
matteodem:easy-search
|
||||
mongo@1.6.2
|
||||
mongo@1.8.0
|
||||
mquandalle:collection-mutations
|
||||
|
||||
# Account system
|
||||
|
@ -75,7 +75,7 @@ horka:swipebox
|
|||
dynamic-import@0.5.1
|
||||
staringatlights:fast-render
|
||||
|
||||
accounts-password@1.5.1
|
||||
accounts-password@1.5.2
|
||||
cfs:gridfs
|
||||
rzymek:fullcalendar
|
||||
momentjs:moment@2.22.2
|
||||
|
@ -97,3 +97,4 @@ percolate:synced-cron
|
|||
easylogic:summernote
|
||||
cfs:filesystem
|
||||
ostrio:files
|
||||
ostrio:cookies
|
||||
|
|
|
@ -1 +1 @@
|
|||
METEOR@1.8.1
|
||||
METEOR@1.9
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
3stack:presence@1.1.2
|
||||
accounts-base@1.4.4
|
||||
accounts-base@1.5.0
|
||||
accounts-oauth@1.1.16
|
||||
accounts-password@1.5.1
|
||||
accounts-password@1.5.3
|
||||
aldeed:collection2@2.10.0
|
||||
aldeed:collection2-core@1.2.0
|
||||
aldeed:schema-deny@1.1.0
|
||||
|
@ -12,18 +12,18 @@ allow-deny@1.1.0
|
|||
arillo:flow-router-helpers@0.5.2
|
||||
audit-argument-checks@1.0.7
|
||||
autoupdate@1.6.0
|
||||
babel-compiler@7.3.4
|
||||
babel-runtime@1.3.0
|
||||
babel-compiler@7.5.1
|
||||
babel-runtime@1.5.0
|
||||
base64@1.0.12
|
||||
binary-heap@1.0.11
|
||||
blaze@2.3.3
|
||||
blaze@2.3.4
|
||||
blaze-tools@1.0.10
|
||||
boilerplate-generator@1.6.0
|
||||
browser-policy-common@1.0.11
|
||||
browser-policy-framing@1.1.0
|
||||
caching-compiler@1.2.1
|
||||
caching-html-compiler@1.1.3
|
||||
callback-hook@1.1.0
|
||||
callback-hook@1.3.0
|
||||
cfs:access-point@0.1.49
|
||||
cfs:base-package@0.0.30
|
||||
cfs:collection@0.5.5
|
||||
|
@ -52,16 +52,16 @@ ddp@1.4.0
|
|||
ddp-client@2.3.3
|
||||
ddp-common@1.4.0
|
||||
ddp-rate-limiter@1.0.7
|
||||
ddp-server@2.3.0
|
||||
ddp-server@2.3.1
|
||||
deps@1.0.12
|
||||
diff-sequence@1.1.1
|
||||
dynamic-import@0.5.1
|
||||
easylogic:summernote@0.8.8
|
||||
ecmascript@0.12.7
|
||||
ecmascript@0.14.1
|
||||
ecmascript-runtime@0.7.0
|
||||
ecmascript-runtime-client@0.8.0
|
||||
ecmascript-runtime-server@0.7.1
|
||||
ejson@1.1.0
|
||||
ecmascript-runtime-client@0.10.0
|
||||
ecmascript-runtime-server@0.9.0
|
||||
ejson@1.1.1
|
||||
email@1.2.3
|
||||
es5-shim@4.8.0
|
||||
fastclick@1.0.13
|
||||
|
@ -82,7 +82,7 @@ kadira:dochead@1.5.0
|
|||
kadira:flow-router@2.12.1
|
||||
kenton:accounts-sandstorm@0.7.0
|
||||
konecty:mongo-counter@0.0.5_3
|
||||
lamhieu:meteorx@2.0.1
|
||||
lamhieu:meteorx@2.1.1
|
||||
lamhieu:unblock@1.0.0
|
||||
launch-screen@1.1.1
|
||||
livedata@1.0.18
|
||||
|
@ -91,7 +91,7 @@ logging@1.1.20
|
|||
lucasantoniassi:accounts-lockout@1.0.0
|
||||
matb33:collection-hooks@0.9.1
|
||||
matteodem:easy-search@1.6.4
|
||||
mdg:meteor-apm-agent@3.2.3
|
||||
mdg:meteor-apm-agent@3.2.5
|
||||
mdg:validation-error@0.5.1
|
||||
meteor@1.9.3
|
||||
meteor-base@1.4.0
|
||||
|
@ -101,16 +101,16 @@ meteorhacks:collection-utils@1.2.0
|
|||
meteorhacks:picker@1.0.3
|
||||
meteorhacks:subs-manager@1.6.4
|
||||
meteorspark:util@0.2.0
|
||||
minifier-css@1.4.2
|
||||
minifier-js@2.4.1
|
||||
minifier-css@1.5.0
|
||||
minifier-js@2.6.0
|
||||
minifiers@1.1.8-faster-rebuild.0
|
||||
minimongo@1.4.5
|
||||
mobile-status-bar@1.0.14
|
||||
modern-browsers@0.1.4
|
||||
modules@0.13.0
|
||||
modules-runtime@0.10.3
|
||||
modern-browsers@0.1.5
|
||||
modules@0.15.0
|
||||
modules-runtime@0.12.0
|
||||
momentjs:moment@2.24.0
|
||||
mongo@1.6.3
|
||||
mongo@1.8.1
|
||||
mongo-decimal@0.1.1
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.7
|
||||
|
@ -127,7 +127,7 @@ mquandalle:mousetrap-bindglobal@0.0.1
|
|||
mquandalle:perfect-scrollbar@0.6.5_2
|
||||
msavin:usercache@1.8.0
|
||||
npm-bcrypt@0.9.3
|
||||
npm-mongo@3.1.2
|
||||
npm-mongo@3.3.0
|
||||
oauth@1.2.8
|
||||
oauth2@1.2.1
|
||||
observe-sequence@1.0.16
|
||||
|
@ -135,11 +135,11 @@ ongoworks:speakingurl@1.1.0
|
|||
ordered-dict@1.1.0
|
||||
ostrio:cookies@2.5.0
|
||||
ostrio:files@1.13.0
|
||||
peerlibrary:assert@0.2.5
|
||||
peerlibrary:assert@0.3.0
|
||||
peerlibrary:base-component@0.16.0
|
||||
peerlibrary:blaze-components@0.15.1
|
||||
peerlibrary:computed-field@0.9.0
|
||||
peerlibrary:reactive-field@0.5.0
|
||||
peerlibrary:computed-field@0.10.0
|
||||
peerlibrary:reactive-field@0.6.0
|
||||
percolate:synced-cron@1.3.2
|
||||
promise@0.11.2
|
||||
raix:eventemitter@0.1.3
|
||||
|
@ -163,13 +163,13 @@ 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
|
||||
socket-stream-client@0.2.3
|
||||
softwarerero:accounts-t9n@1.3.11
|
||||
spacebars@1.0.15
|
||||
spacebars-compiler@1.1.3
|
||||
srp@1.0.12
|
||||
standard-minifier-css@1.5.3
|
||||
standard-minifier-js@2.4.1
|
||||
standard-minifier-css@1.6.0
|
||||
standard-minifier-js@2.6.0
|
||||
staringatlights:fast-render@3.2.0
|
||||
staringatlights:inject-data@2.3.0
|
||||
tap:i18n@1.8.2
|
||||
|
@ -187,7 +187,7 @@ useraccounts:core@1.14.2
|
|||
useraccounts:flow-routing@1.14.2
|
||||
useraccounts:unstyled@1.14.2
|
||||
verron:autosize@3.0.8
|
||||
webapp@1.7.4
|
||||
webapp@1.8.2
|
||||
webapp-hashing@1.0.9
|
||||
wekan-accounts-cas@0.1.0
|
||||
wekan-accounts-oidc@1.0.10
|
||||
|
|
|
@ -5,3 +5,4 @@ node_modules/
|
|||
.vscode/
|
||||
.tx/
|
||||
.github/
|
||||
.snap-meteor-1.8/
|
||||
|
|
20
.snap-meteor-1.8/.meteor/.finished-upgraders
Normal file
20
.snap-meteor-1.8/.meteor/.finished-upgraders
Normal file
|
@ -0,0 +1,20 @@
|
|||
# This file contains information which helps Meteor properly upgrade your
|
||||
# app when you run 'meteor update'. You should check it into version control
|
||||
# with your project.
|
||||
|
||||
notices-for-0.9.0
|
||||
notices-for-0.9.1
|
||||
0.9.4-platform-file
|
||||
notices-for-facebook-graph-api-2
|
||||
1.2.0-standard-minifiers-package
|
||||
1.2.0-meteor-platform-split
|
||||
1.2.0-cordova-changes
|
||||
1.2.0-breaking-changes
|
||||
1.3.0-split-minifiers-package
|
||||
1.3.5-remove-old-dev-bundle-link
|
||||
1.4.0-remove-old-dev-bundle-link
|
||||
1.4.1-add-shell-server-package
|
||||
1.4.3-split-account-service-packages
|
||||
1.5-add-dynamic-import-package
|
||||
1.7-split-underscore-from-meteor-base
|
||||
1.8.3-split-jquery-from-blaze
|
2
.snap-meteor-1.8/.meteor/.gitignore
vendored
Normal file
2
.snap-meteor-1.8/.meteor/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
dev_bundle
|
||||
local
|
7
.snap-meteor-1.8/.meteor/.id
Normal file
7
.snap-meteor-1.8/.meteor/.id
Normal file
|
@ -0,0 +1,7 @@
|
|||
# This file contains a token that is unique to your project.
|
||||
# Check it into your repository along with the rest of this directory.
|
||||
# It can be used for purposes such as:
|
||||
# - ensuring you don't accidentally deploy one app on top of another
|
||||
# - providing package authors with aggregated statistics
|
||||
|
||||
dvyihgykyzec6y1dpg
|
0
.snap-meteor-1.8/.meteor/cordova-plugins
Normal file
0
.snap-meteor-1.8/.meteor/cordova-plugins
Normal file
99
.snap-meteor-1.8/.meteor/packages
Normal file
99
.snap-meteor-1.8/.meteor/packages
Normal file
|
@ -0,0 +1,99 @@
|
|||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
meteor-base@1.4.0
|
||||
|
||||
# Build system
|
||||
ecmascript@0.13.2
|
||||
standard-minifier-css@1.5.4
|
||||
standard-minifier-js@2.5.2
|
||||
mquandalle:jade
|
||||
|
||||
# Polyfills
|
||||
es5-shim@4.8.0
|
||||
|
||||
# Collections
|
||||
aldeed:collection2
|
||||
cfs:standard-packages
|
||||
cottz:publish-relations
|
||||
dburles:collection-helpers
|
||||
idmontie:migrations
|
||||
matb33:collection-hooks
|
||||
matteodem:easy-search
|
||||
mongo@1.7.0
|
||||
mquandalle:collection-mutations
|
||||
|
||||
# Account system
|
||||
kenton:accounts-sandstorm
|
||||
service-configuration@1.0.11
|
||||
useraccounts:unstyled
|
||||
useraccounts:flow-routing
|
||||
wekan-ldap
|
||||
wekan-accounts-cas
|
||||
wekan-accounts-oidc
|
||||
|
||||
# Utilities
|
||||
check@1.3.1
|
||||
jquery@1.11.10
|
||||
random@1.1.0
|
||||
reactive-dict@1.3.0
|
||||
session@1.2.0
|
||||
tracker@1.2.0
|
||||
underscore@1.0.10
|
||||
3stack:presence
|
||||
alethes:pages
|
||||
arillo:flow-router-helpers
|
||||
audit-argument-checks@1.0.7
|
||||
kadira:blaze-layout
|
||||
kadira:dochead
|
||||
mquandalle:autofocus
|
||||
ongoworks:speakingurl
|
||||
raix:handlebar-helpers
|
||||
tap:i18n
|
||||
http@1.4.2
|
||||
|
||||
# UI components
|
||||
blaze
|
||||
reactive-var@1.0.11
|
||||
fortawesome:fontawesome
|
||||
mousetrap:mousetrap
|
||||
mquandalle:jquery-textcomplete
|
||||
mquandalle:jquery-ui-drag-drop-sort
|
||||
mquandalle:mousetrap-bindglobal
|
||||
peerlibrary:blaze-components@=0.15.1
|
||||
templates:tabs
|
||||
verron:autosize
|
||||
simple:json-routes
|
||||
rajit:bootstrap3-datepicker
|
||||
shell-server@0.4.0
|
||||
simple:rest-accounts-password
|
||||
useraccounts:core
|
||||
email@1.2.3
|
||||
horka:swipebox
|
||||
dynamic-import@0.5.1
|
||||
staringatlights:fast-render
|
||||
|
||||
accounts-password@1.5.2
|
||||
cfs:gridfs
|
||||
rzymek:fullcalendar
|
||||
momentjs:moment@2.22.2
|
||||
browser-policy-framing@1.1.0
|
||||
mquandalle:moment
|
||||
msavin:usercache
|
||||
wekan-scrollbar
|
||||
mquandalle:perfect-scrollbar
|
||||
mdg:meteor-apm-agent@3.2.0-rc.0!
|
||||
coagmano:stylus
|
||||
lucasantoniassi:accounts-lockout
|
||||
meteorhacks:subs-manager
|
||||
meteorhacks:picker
|
||||
lamhieu:unblock
|
||||
meteorhacks:aggregate@1.3.0
|
||||
wekan-markdown
|
||||
konecty:mongo-counter
|
||||
percolate:synced-cron
|
||||
easylogic:summernote
|
||||
cfs:filesystem
|
||||
ostrio:cookies
|
2
.snap-meteor-1.8/.meteor/platforms
Normal file
2
.snap-meteor-1.8/.meteor/platforms
Normal file
|
@ -0,0 +1,2 @@
|
|||
server
|
||||
browser
|
1
.snap-meteor-1.8/.meteor/release
Normal file
1
.snap-meteor-1.8/.meteor/release
Normal file
|
@ -0,0 +1 @@
|
|||
METEOR@1.8.3
|
198
.snap-meteor-1.8/.meteor/versions
Normal file
198
.snap-meteor-1.8/.meteor/versions
Normal file
|
@ -0,0 +1,198 @@
|
|||
3stack:presence@1.1.2
|
||||
accounts-base@1.4.5
|
||||
accounts-oauth@1.1.16
|
||||
accounts-password@1.5.2
|
||||
aldeed:collection2@2.10.0
|
||||
aldeed:collection2-core@1.2.0
|
||||
aldeed:schema-deny@1.1.0
|
||||
aldeed:schema-index@1.1.1
|
||||
aldeed:simple-schema@1.5.4
|
||||
alethes:pages@1.8.6
|
||||
allow-deny@1.1.0
|
||||
arillo:flow-router-helpers@0.5.2
|
||||
audit-argument-checks@1.0.7
|
||||
autoupdate@1.6.0
|
||||
babel-compiler@7.4.2
|
||||
babel-runtime@1.4.0
|
||||
base64@1.0.12
|
||||
binary-heap@1.0.11
|
||||
blaze@2.3.4
|
||||
blaze-tools@1.0.10
|
||||
boilerplate-generator@1.6.0
|
||||
browser-policy-common@1.0.11
|
||||
browser-policy-framing@1.1.0
|
||||
caching-compiler@1.2.1
|
||||
caching-html-compiler@1.1.3
|
||||
callback-hook@1.2.0
|
||||
cfs:access-point@0.1.49
|
||||
cfs:base-package@0.0.30
|
||||
cfs:collection@0.5.5
|
||||
cfs:collection-filters@0.2.4
|
||||
cfs:data-man@0.0.6
|
||||
cfs:file@0.1.17
|
||||
cfs:filesystem@0.1.2
|
||||
cfs:gridfs@0.0.34
|
||||
cfs:http-methods@0.0.32
|
||||
cfs:http-publish@0.0.13
|
||||
cfs:power-queue@0.9.11
|
||||
cfs:reactive-list@0.0.9
|
||||
cfs:reactive-property@0.0.4
|
||||
cfs:standard-packages@0.5.10
|
||||
cfs:storage-adapter@0.2.4
|
||||
cfs:tempstore@0.1.6
|
||||
cfs:upload-http@0.0.20
|
||||
cfs:worker@0.1.5
|
||||
check@1.3.1
|
||||
chuangbo:cookie@1.1.0
|
||||
coagmano:stylus@2.0.0
|
||||
coffeescript@1.0.17
|
||||
cottz:publish-relations@2.0.8
|
||||
dburles:collection-helpers@1.1.0
|
||||
ddp@1.4.0
|
||||
ddp-client@2.3.3
|
||||
ddp-common@1.4.0
|
||||
ddp-rate-limiter@1.0.7
|
||||
ddp-server@2.3.0
|
||||
deps@1.0.12
|
||||
diff-sequence@1.1.1
|
||||
dynamic-import@0.5.1
|
||||
easylogic:summernote@0.8.8
|
||||
ecmascript@0.13.2
|
||||
ecmascript-runtime@0.7.0
|
||||
ecmascript-runtime-client@0.9.0
|
||||
ecmascript-runtime-server@0.8.0
|
||||
ejson@1.1.1
|
||||
email@1.2.3
|
||||
es5-shim@4.8.0
|
||||
fastclick@1.0.13
|
||||
fetch@0.1.1
|
||||
fortawesome:fontawesome@4.7.0
|
||||
geojson-utils@1.0.10
|
||||
horka:swipebox@1.0.2
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.0.11
|
||||
htmljs@1.0.11
|
||||
http@1.4.2
|
||||
id-map@1.1.0
|
||||
idmontie:migrations@1.0.3
|
||||
inter-process-messaging@0.1.0
|
||||
jquery@1.11.11
|
||||
kadira:blaze-layout@2.3.0
|
||||
kadira:dochead@1.5.0
|
||||
kadira:flow-router@2.12.1
|
||||
kenton:accounts-sandstorm@0.7.0
|
||||
konecty:mongo-counter@0.0.5_3
|
||||
lamhieu:meteorx@2.1.1
|
||||
lamhieu:unblock@1.0.0
|
||||
launch-screen@1.1.1
|
||||
livedata@1.0.18
|
||||
localstorage@1.2.0
|
||||
logging@1.1.20
|
||||
lucasantoniassi:accounts-lockout@1.0.0
|
||||
matb33:collection-hooks@0.9.1
|
||||
matteodem:easy-search@1.6.4
|
||||
mdg:meteor-apm-agent@3.2.5
|
||||
mdg:validation-error@0.5.1
|
||||
meteor@1.9.3
|
||||
meteor-base@1.4.0
|
||||
meteor-platform@1.2.6
|
||||
meteorhacks:aggregate@1.3.0
|
||||
meteorhacks:collection-utils@1.2.0
|
||||
meteorhacks:picker@1.0.3
|
||||
meteorhacks:subs-manager@1.6.4
|
||||
meteorspark:util@0.2.0
|
||||
minifier-css@1.4.3
|
||||
minifier-js@2.5.1
|
||||
minifiers@1.1.8-faster-rebuild.0
|
||||
minimongo@1.4.5
|
||||
mobile-status-bar@1.0.14
|
||||
modern-browsers@0.1.4
|
||||
modules@0.14.0
|
||||
modules-runtime@0.11.0
|
||||
momentjs:moment@2.24.0
|
||||
mongo@1.7.0
|
||||
mongo-decimal@0.1.1
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.7
|
||||
mongo-livedata@1.0.12
|
||||
mousetrap:mousetrap@1.4.6_1
|
||||
mquandalle:autofocus@1.0.0
|
||||
mquandalle:collection-mutations@0.1.0
|
||||
mquandalle:jade@0.4.9
|
||||
mquandalle:jade-compiler@0.4.5
|
||||
mquandalle:jquery-textcomplete@0.8.0_1
|
||||
mquandalle:jquery-ui-drag-drop-sort@0.2.0
|
||||
mquandalle:moment@1.0.1
|
||||
mquandalle:mousetrap-bindglobal@0.0.1
|
||||
mquandalle:perfect-scrollbar@0.6.5_2
|
||||
msavin:usercache@1.8.0
|
||||
npm-bcrypt@0.9.3
|
||||
npm-mongo@3.2.0
|
||||
oauth@1.2.8
|
||||
oauth2@1.2.1
|
||||
observe-sequence@1.0.16
|
||||
ongoworks:speakingurl@1.1.0
|
||||
ordered-dict@1.1.0
|
||||
ostrio:cookies@2.5.0
|
||||
peerlibrary:assert@0.3.0
|
||||
peerlibrary:base-component@0.16.0
|
||||
peerlibrary:blaze-components@0.15.1
|
||||
peerlibrary:computed-field@0.10.0
|
||||
peerlibrary:reactive-field@0.6.0
|
||||
percolate:synced-cron@1.3.2
|
||||
promise@0.11.2
|
||||
raix:eventemitter@0.1.3
|
||||
raix:handlebar-helpers@0.2.5
|
||||
rajit:bootstrap3-datepicker@1.7.1_1
|
||||
random@1.1.0
|
||||
rate-limit@1.0.9
|
||||
reactive-dict@1.3.0
|
||||
reactive-var@1.0.11
|
||||
reload@1.3.0
|
||||
retry@1.1.0
|
||||
routepolicy@1.1.0
|
||||
rzymek:fullcalendar@3.8.0
|
||||
server-render@0.3.1
|
||||
service-configuration@1.0.11
|
||||
session@1.2.0
|
||||
sha@1.0.9
|
||||
shell-server@0.4.0
|
||||
simple:authenticate-user-by-token@1.0.1
|
||||
simple:json-routes@2.1.0
|
||||
simple:rest-accounts-password@1.1.2
|
||||
simple:rest-bearer-token-parser@1.0.1
|
||||
simple:rest-json-error-handler@1.0.1
|
||||
socket-stream-client@0.2.2
|
||||
softwarerero:accounts-t9n@1.3.11
|
||||
spacebars@1.0.15
|
||||
spacebars-compiler@1.1.3
|
||||
srp@1.0.12
|
||||
standard-minifier-css@1.5.4
|
||||
standard-minifier-js@2.5.2
|
||||
staringatlights:fast-render@3.2.0
|
||||
staringatlights:inject-data@2.3.0
|
||||
tap:i18n@1.8.2
|
||||
templates:tabs@2.3.0
|
||||
templating@1.3.2
|
||||
templating-compiler@1.3.3
|
||||
templating-runtime@1.3.2
|
||||
templating-tools@1.1.2
|
||||
tracker@1.2.0
|
||||
twbs:bootstrap@3.3.6
|
||||
ui@1.0.13
|
||||
underscore@1.0.10
|
||||
url@1.2.0
|
||||
useraccounts:core@1.14.2
|
||||
useraccounts:flow-routing@1.14.2
|
||||
useraccounts:unstyled@1.14.2
|
||||
verron:autosize@3.0.8
|
||||
webapp@1.7.5
|
||||
webapp-hashing@1.0.9
|
||||
wekan-accounts-cas@0.1.0
|
||||
wekan-accounts-oidc@1.0.10
|
||||
wekan-ldap@0.0.2
|
||||
wekan-markdown@1.0.7
|
||||
wekan-oidc@1.0.12
|
||||
wekan-scrollbar@3.1.3
|
||||
yasaricli:slugify@0.0.7
|
||||
zimme:active-route@2.3.2
|
914
.snap-meteor-1.8/cfs_access-point.txt
Normal file
914
.snap-meteor-1.8/cfs_access-point.txt
Normal file
|
@ -0,0 +1,914 @@
|
|||
(function () {
|
||||
|
||||
/* Imports */
|
||||
var Meteor = Package.meteor.Meteor;
|
||||
var global = Package.meteor.global;
|
||||
var meteorEnv = Package.meteor.meteorEnv;
|
||||
var FS = Package['cfs:base-package'].FS;
|
||||
var check = Package.check.check;
|
||||
var Match = Package.check.Match;
|
||||
var EJSON = Package.ejson.EJSON;
|
||||
var HTTP = Package['cfs:http-methods'].HTTP;
|
||||
|
||||
/* Package-scope variables */
|
||||
var rootUrlPathPrefix, baseUrl, getHeaders, getHeadersByCollection, _existingMountPoints, mountUrls;
|
||||
|
||||
(function(){
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// packages/cfs_access-point/packages/cfs_access-point.js //
|
||||
// //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
(function () {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// packages/cfs:access-point/access-point-common.js //
|
||||
// //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ""; // 1
|
||||
// Adjust the rootUrlPathPrefix if necessary // 2
|
||||
if (rootUrlPathPrefix.length > 0) { // 3
|
||||
if (rootUrlPathPrefix.slice(0, 1) !== '/') { // 4
|
||||
rootUrlPathPrefix = '/' + rootUrlPathPrefix; // 5
|
||||
} // 6
|
||||
if (rootUrlPathPrefix.slice(-1) === '/') { // 7
|
||||
rootUrlPathPrefix = rootUrlPathPrefix.slice(0, -1); // 8
|
||||
} // 9
|
||||
} // 10
|
||||
// 11
|
||||
// prepend ROOT_URL when isCordova // 12
|
||||
if (Meteor.isCordova) { // 13
|
||||
rootUrlPathPrefix = Meteor.absoluteUrl(rootUrlPathPrefix.replace(/^\/+/, '')).replace(/\/+$/, ''); // 14
|
||||
} // 15
|
||||
// 16
|
||||
baseUrl = '/cfs'; // 17
|
||||
FS.HTTP = FS.HTTP || {}; // 18
|
||||
// 19
|
||||
// Note the upload URL so that client uploader packages know what it is // 20
|
||||
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files'; // 21
|
||||
// 22
|
||||
/** // 23
|
||||
* @method FS.HTTP.setBaseUrl // 24
|
||||
* @public // 25
|
||||
* @param {String} newBaseUrl - Change the base URL for the HTTP GET and DELETE endpoints. // 26
|
||||
* @returns {undefined} // 27
|
||||
*/ // 28
|
||||
FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { // 29
|
||||
// 30
|
||||
// Adjust the baseUrl if necessary // 31
|
||||
if (newBaseUrl.slice(0, 1) !== '/') { // 32
|
||||
newBaseUrl = '/' + newBaseUrl; // 33
|
||||
} // 34
|
||||
if (newBaseUrl.slice(-1) === '/') { // 35
|
||||
newBaseUrl = newBaseUrl.slice(0, -1); // 36
|
||||
} // 37
|
||||
// 38
|
||||
// Update the base URL // 39
|
||||
baseUrl = newBaseUrl; // 40
|
||||
// 41
|
||||
// Change the upload URL so that client uploader packages know what it is // 42
|
||||
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files'; // 43
|
||||
// 44
|
||||
// Remount URLs with the new baseUrl, unmounting the old, on the server only. // 45
|
||||
// If existingMountPoints is empty, then we haven't run the server startup // 46
|
||||
// code yet, so this new URL will be used at that point for the initial mount. // 47
|
||||
if (Meteor.isServer && !FS.Utility.isEmpty(_existingMountPoints)) { // 48
|
||||
mountUrls(); // 49
|
||||
} // 50
|
||||
}; // 51
|
||||
// 52
|
||||
/* // 53
|
||||
* FS.File extensions // 54
|
||||
*/ // 55
|
||||
// 56
|
||||
/** // 57
|
||||
* @method FS.File.prototype.url Construct the file url // 58
|
||||
* @public // 59
|
||||
* @param {Object} [options] // 60
|
||||
* @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
* @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
* @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
* @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
* @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself.
|
||||
* @param {String} [options.uploading=null] A URL to return while the file is being uploaded. // 66
|
||||
* @param {String} [options.storing=null] A URL to return while the file is being stored. // 67
|
||||
* @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
* // 69
|
||||
* Returns the HTTP URL for getting the file or its metadata. // 70
|
||||
*/ // 71
|
||||
FS.File.prototype.url = function(options) { // 72
|
||||
var self = this; // 73
|
||||
options = options || {}; // 74
|
||||
options = FS.Utility.extend({ // 75
|
||||
store: null, // 76
|
||||
auth: null, // 77
|
||||
download: false, // 78
|
||||
metadata: false, // 79
|
||||
brokenIsFine: false, // 80
|
||||
uploading: null, // return this URL while uploading // 81
|
||||
storing: null, // return this URL while storing // 82
|
||||
filename: null // override the filename that is shown to the user // 83
|
||||
}, options.hash || options); // check for "hash" prop if called as helper // 84
|
||||
// 85
|
||||
// Primarily useful for displaying a temporary image while uploading an image // 86
|
||||
if (options.uploading && !self.isUploaded()) { // 87
|
||||
return options.uploading; // 88
|
||||
} // 89
|
||||
// 90
|
||||
if (self.isMounted()) { // 91
|
||||
// See if we've stored in the requested store yet // 92
|
||||
var storeName = options.store || self.collection.primaryStore.name; // 93
|
||||
if (!self.hasStored(storeName)) { // 94
|
||||
if (options.storing) { // 95
|
||||
return options.storing; // 96
|
||||
} else if (!options.brokenIsFine) { // 97
|
||||
// We want to return null if we know the URL will be a broken // 98
|
||||
// link because then we can avoid rendering broken links, broken // 99
|
||||
// images, etc. // 100
|
||||
return null; // 101
|
||||
} // 102
|
||||
} // 103
|
||||
// 104
|
||||
// Add filename to end of URL if we can determine one // 105
|
||||
var filename = options.filename || self.name({store: storeName}); // 106
|
||||
if (typeof filename === "string" && filename.length) { // 107
|
||||
filename = '/' + filename; // 108
|
||||
} else { // 109
|
||||
filename = ''; // 110
|
||||
} // 111
|
||||
// 112
|
||||
// TODO: Could we somehow figure out if the collection requires login? // 113
|
||||
var authToken = ''; // 114
|
||||
if (Meteor.isClient && typeof Accounts !== "undefined" && typeof Accounts._storedLoginToken === "function") { // 115
|
||||
if (options.auth !== false) { // 116
|
||||
// Add reactive deps on the user // 117
|
||||
Meteor.userId(); // 118
|
||||
// 119
|
||||
var authObject = { // 120
|
||||
authToken: Accounts._storedLoginToken() || '' // 121
|
||||
}; // 122
|
||||
// 123
|
||||
// If it's a number, we use that as the expiration time (in seconds) // 124
|
||||
if (options.auth === +options.auth) { // 125
|
||||
authObject.expiration = FS.HTTP.now() + options.auth * 1000; // 126
|
||||
} // 127
|
||||
// 128
|
||||
// Set the authToken // 129
|
||||
var authString = JSON.stringify(authObject); // 130
|
||||
authToken = FS.Utility.btoa(authString); // 131
|
||||
} // 132
|
||||
} else if (typeof options.auth === "string") { // 133
|
||||
// If the user supplies auth token the user will be responsible for // 134
|
||||
// updating // 135
|
||||
authToken = options.auth; // 136
|
||||
} // 137
|
||||
// 138
|
||||
// Construct query string // 139
|
||||
var params = {}; // 140
|
||||
if (authToken !== '') { // 141
|
||||
params.token = authToken; // 142
|
||||
} // 143
|
||||
if (options.download) { // 144
|
||||
params.download = true; // 145
|
||||
} // 146
|
||||
if (options.store) { // 147
|
||||
// We use options.store here instead of storeName because we want to omit the queryString // 148
|
||||
// whenever possible, allowing users to have "clean" URLs if they want. The server will // 149
|
||||
// assume the first store defined on the server, which means that we are assuming that // 150
|
||||
// the first on the client is also the first on the server. If that's not the case, the // 151
|
||||
// store option should be supplied. // 152
|
||||
params.store = options.store; // 153
|
||||
} // 154
|
||||
var queryString = FS.Utility.encodeParams(params); // 155
|
||||
if (queryString.length) { // 156
|
||||
queryString = '?' + queryString; // 157
|
||||
} // 158
|
||||
// 159
|
||||
// Determine which URL to use // 160
|
||||
var area; // 161
|
||||
if (options.metadata) { // 162
|
||||
area = '/record'; // 163
|
||||
} else { // 164
|
||||
area = '/files'; // 165
|
||||
} // 166
|
||||
// 167
|
||||
// Construct and return the http method url // 168
|
||||
return rootUrlPathPrefix + baseUrl + area + '/' + self.collection.name + '/' + self._id + filename + queryString; // 169
|
||||
} // 170
|
||||
// 171
|
||||
}; // 172
|
||||
// 173
|
||||
// 174
|
||||
// 175
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
}).call(this);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(function () {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// packages/cfs:access-point/access-point-handlers.js //
|
||||
// //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
getHeaders = []; // 1
|
||||
getHeadersByCollection = {}; // 2
|
||||
// 3
|
||||
FS.HTTP.Handlers = {}; // 4
|
||||
// 5
|
||||
/** // 6
|
||||
* @method FS.HTTP.Handlers.Del // 7
|
||||
* @public // 8
|
||||
* @returns {any} response // 9
|
||||
* // 10
|
||||
* HTTP DEL request handler // 11
|
||||
*/ // 12
|
||||
FS.HTTP.Handlers.Del = function httpDelHandler(ref) { // 13
|
||||
var self = this; // 14
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 15
|
||||
// 16
|
||||
// If DELETE request, validate with 'remove' allow/deny, delete the file, and return // 17
|
||||
FS.Utility.validateAction(ref.collection.files._validators['remove'], ref.file, self.userId); // 18
|
||||
// 19
|
||||
/* // 20
|
||||
* From the DELETE spec: // 21
|
||||
* A successful response SHOULD be 200 (OK) if the response includes an // 22
|
||||
* entity describing the status, 202 (Accepted) if the action has not // 23
|
||||
* yet been enacted, or 204 (No Content) if the action has been enacted // 24
|
||||
* but the response does not include an entity. // 25
|
||||
*/ // 26
|
||||
self.setStatusCode(200); // 27
|
||||
// 28
|
||||
return { // 29
|
||||
deleted: !!ref.file.remove() // 30
|
||||
}; // 31
|
||||
}; // 32
|
||||
// 33
|
||||
/** // 34
|
||||
* @method FS.HTTP.Handlers.GetList // 35
|
||||
* @public // 36
|
||||
* @returns {Object} response // 37
|
||||
* // 38
|
||||
* HTTP GET file list request handler // 39
|
||||
*/ // 40
|
||||
FS.HTTP.Handlers.GetList = function httpGetListHandler() { // 41
|
||||
// Not Yet Implemented // 42
|
||||
// Need to check publications and return file list based on // 43
|
||||
// what user is allowed to see // 44
|
||||
}; // 45
|
||||
// 46
|
||||
/* // 47
|
||||
requestRange will parse the range set in request header - if not possible it // 48
|
||||
will throw fitting errors and autofill range for both partial and full ranges // 49
|
||||
// 50
|
||||
throws error or returns the object: // 51
|
||||
{ // 52
|
||||
start // 53
|
||||
end // 54
|
||||
length // 55
|
||||
unit // 56
|
||||
partial // 57
|
||||
} // 58
|
||||
*/ // 59
|
||||
var requestRange = function(req, fileSize) { // 60
|
||||
if (req) { // 61
|
||||
if (req.headers) { // 62
|
||||
var rangeString = req.headers.range; // 63
|
||||
// 64
|
||||
// Make sure range is a string // 65
|
||||
if (rangeString === ''+rangeString) { // 66
|
||||
// 67
|
||||
// range will be in the format "bytes=0-32767" // 68
|
||||
var parts = rangeString.split('='); // 69
|
||||
var unit = parts[0]; // 70
|
||||
// 71
|
||||
// Make sure parts consists of two strings and range is of type "byte" // 72
|
||||
if (parts.length == 2 && unit == 'bytes') { // 73
|
||||
// Parse the range // 74
|
||||
var range = parts[1].split('-'); // 75
|
||||
var start = Number(range[0]); // 76
|
||||
var end = Number(range[1]); // 77
|
||||
// 78
|
||||
// Fix invalid ranges? // 79
|
||||
if (range[0] != start) start = 0; // 80
|
||||
if (range[1] != end || !end) end = fileSize - 1; // 81
|
||||
// 82
|
||||
// Make sure range consists of a start and end point of numbers and start is less than end // 83
|
||||
if (start < end) { // 84
|
||||
// 85
|
||||
var partSize = 0 - start + end + 1; // 86
|
||||
// 87
|
||||
// Return the parsed range // 88
|
||||
return { // 89
|
||||
start: start, // 90
|
||||
end: end, // 91
|
||||
length: partSize, // 92
|
||||
size: fileSize, // 93
|
||||
unit: unit, // 94
|
||||
partial: (partSize < fileSize) // 95
|
||||
}; // 96
|
||||
// 97
|
||||
} else { // 98
|
||||
throw new Meteor.Error(416, "Requested Range Not Satisfiable"); // 99
|
||||
} // 100
|
||||
// 101
|
||||
} else { // 102
|
||||
// The first part should be bytes // 103
|
||||
throw new Meteor.Error(416, "Requested Range Unit Not Satisfiable"); // 104
|
||||
} // 105
|
||||
// 106
|
||||
} else { // 107
|
||||
// No range found // 108
|
||||
} // 109
|
||||
// 110
|
||||
} else { // 111
|
||||
// throw new Error('No request headers set for _parseRange function'); // 112
|
||||
} // 113
|
||||
} else { // 114
|
||||
throw new Error('No request object passed to _parseRange function'); // 115
|
||||
} // 116
|
||||
// 117
|
||||
return { // 118
|
||||
start: 0, // 119
|
||||
end: fileSize - 1, // 120
|
||||
length: fileSize, // 121
|
||||
size: fileSize, // 122
|
||||
unit: 'bytes', // 123
|
||||
partial: false // 124
|
||||
}; // 125
|
||||
}; // 126
|
||||
// 127
|
||||
/** // 128
|
||||
* @method FS.HTTP.Handlers.Get // 129
|
||||
* @public // 130
|
||||
* @returns {any} response // 131
|
||||
* // 132
|
||||
* HTTP GET request handler // 133
|
||||
*/ // 134
|
||||
FS.HTTP.Handlers.Get = function httpGetHandler(ref) { // 135
|
||||
var self = this; // 136
|
||||
// Once we have the file, we can test allow/deny validators // 137
|
||||
// XXX: pass on the "share" query eg. ?share=342hkjh23ggj for shared url access? // 138
|
||||
FS.Utility.validateAction(ref.collection._validators['download'], ref.file, self.userId /*, self.query.shareId*/); // 139
|
||||
// 140
|
||||
var storeName = ref.storeName; // 141
|
||||
// 142
|
||||
// If no storeName was specified, use the first defined storeName // 143
|
||||
if (typeof storeName !== "string") { // 144
|
||||
// No store handed, we default to primary store // 145
|
||||
storeName = ref.collection.primaryStore.name; // 146
|
||||
} // 147
|
||||
// 148
|
||||
// Get the storage reference // 149
|
||||
var storage = ref.collection.storesLookup[storeName]; // 150
|
||||
// 151
|
||||
if (!storage) { // 152
|
||||
throw new Meteor.Error(404, "Not Found", 'There is no store "' + storeName + '"'); // 153
|
||||
} // 154
|
||||
// 155
|
||||
// Get the file // 156
|
||||
var copyInfo = ref.file.copies[storeName]; // 157
|
||||
// 158
|
||||
if (!copyInfo) { // 159
|
||||
throw new Meteor.Error(404, "Not Found", 'This file was not stored in the ' + storeName + ' store'); // 160
|
||||
} // 161
|
||||
// 162
|
||||
// Set the content type for file // 163
|
||||
if (typeof copyInfo.type === "string") { // 164
|
||||
self.setContentType(copyInfo.type); // 165
|
||||
} else { // 166
|
||||
self.setContentType('application/octet-stream'); // 167
|
||||
} // 168
|
||||
// 169
|
||||
// Add 'Content-Disposition' header if requested a download/attachment URL // 170
|
||||
if (typeof ref.download !== "undefined") { // 171
|
||||
var filename = ref.filename || copyInfo.name; // 172
|
||||
self.addHeader('Content-Disposition', 'attachment; filename="' + filename + '"'); // 173
|
||||
} else { // 174
|
||||
self.addHeader('Content-Disposition', 'inline'); // 175
|
||||
} // 176
|
||||
// 177
|
||||
// Get the contents range from request // 178
|
||||
var range = requestRange(self.request, copyInfo.size); // 179
|
||||
// 180
|
||||
// Some browsers cope better if the content-range header is // 181
|
||||
// still included even for the full file being returned. // 182
|
||||
self.addHeader('Content-Range', range.unit + ' ' + range.start + '-' + range.end + '/' + range.size); // 183
|
||||
// 184
|
||||
// If a chunk/range was requested instead of the whole file, serve that' // 185
|
||||
if (range.partial) { // 186
|
||||
self.setStatusCode(206, 'Partial Content'); // 187
|
||||
} else { // 188
|
||||
self.setStatusCode(200, 'OK'); // 189
|
||||
} // 190
|
||||
// 191
|
||||
// Add any other global custom headers and collection-specific custom headers // 192
|
||||
FS.Utility.each(getHeaders.concat(getHeadersByCollection[ref.collection.name] || []), function(header) { // 193
|
||||
self.addHeader(header[0], header[1]); // 194
|
||||
}); // 195
|
||||
// 196
|
||||
// Inform clients about length (or chunk length in case of ranges) // 197
|
||||
self.addHeader('Content-Length', range.length); // 198
|
||||
// 199
|
||||
// Last modified header (updatedAt from file info) // 200
|
||||
self.addHeader('Last-Modified', copyInfo.updatedAt.toUTCString()); // 201
|
||||
// 202
|
||||
// Inform clients that we accept ranges for resumable chunked downloads // 203
|
||||
self.addHeader('Accept-Ranges', range.unit); // 204
|
||||
// 205
|
||||
if (FS.debug) console.log('Read file "' + (ref.filename || copyInfo.name) + '" ' + range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);
|
||||
// 207
|
||||
var readStream = storage.adapter.createReadStream(ref.file, {start: range.start, end: range.end}); // 208
|
||||
// 209
|
||||
readStream.on('error', function(err) { // 210
|
||||
// Send proper error message on get error // 211
|
||||
if (err.message && err.statusCode) { // 212
|
||||
self.Error(new Meteor.Error(err.statusCode, err.message)); // 213
|
||||
} else { // 214
|
||||
self.Error(new Meteor.Error(503, 'Service unavailable')); // 215
|
||||
} // 216
|
||||
}); // 217
|
||||
// 218
|
||||
readStream.pipe(self.createWriteStream()); // 219
|
||||
}; // 220
|
||||
|
||||
const originalHandler = FS.HTTP.Handlers.Get;
|
||||
FS.HTTP.Handlers.Get = function (ref) {
|
||||
//console.log(ref.filename);
|
||||
try {
|
||||
var userAgent = (this.requestHeaders['user-agent']||'').toLowerCase();
|
||||
|
||||
if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('trident') >= 0 || userAgent.indexOf('chrome') >= 0) {
|
||||
ref.filename = encodeURIComponent(ref.filename);
|
||||
} else if(userAgent.indexOf('firefox') >= 0) {
|
||||
ref.filename = new Buffer(ref.filename).toString('binary');
|
||||
} else {
|
||||
/* safari*/
|
||||
ref.filename = new Buffer(ref.filename).toString('binary');
|
||||
}
|
||||
} catch (ex){
|
||||
ref.filename = 'tempfix';
|
||||
}
|
||||
return originalHandler.call(this, ref);
|
||||
};
|
||||
// 221
|
||||
/** // 222
|
||||
* @method FS.HTTP.Handlers.PutInsert // 223
|
||||
* @public // 224
|
||||
* @returns {Object} response object with _id property // 225
|
||||
* // 226
|
||||
* HTTP PUT file insert request handler // 227
|
||||
*/ // 228
|
||||
FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { // 229
|
||||
var self = this; // 230
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 231
|
||||
// 232
|
||||
FS.debug && console.log("HTTP PUT (insert) handler"); // 233
|
||||
// 234
|
||||
// Create the nice FS.File // 235
|
||||
var fileObj = new FS.File(); // 236
|
||||
// 237
|
||||
// Set its name // 238
|
||||
fileObj.name(opts.filename || null); // 239
|
||||
// 240
|
||||
// Attach the readstream as the file's data // 241
|
||||
fileObj.attachData(self.createReadStream(), {type: self.requestHeaders['content-type'] || 'application/octet-stream'});
|
||||
// 243
|
||||
// Validate with insert allow/deny // 244
|
||||
FS.Utility.validateAction(ref.collection.files._validators['insert'], fileObj, self.userId); // 245
|
||||
// 246
|
||||
// Insert file into collection, triggering readStream storage // 247
|
||||
ref.collection.insert(fileObj); // 248
|
||||
// 249
|
||||
// Send response // 250
|
||||
self.setStatusCode(200); // 251
|
||||
// 252
|
||||
// Return the new file id // 253
|
||||
return {_id: fileObj._id}; // 254
|
||||
}; // 255
|
||||
// 256
|
||||
/** // 257
|
||||
* @method FS.HTTP.Handlers.PutUpdate // 258
|
||||
* @public // 259
|
||||
* @returns {Object} response object with _id and chunk properties // 260
|
||||
* // 261
|
||||
* HTTP PUT file update chunk request handler // 262
|
||||
*/ // 263
|
||||
FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { // 264
|
||||
var self = this; // 265
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 266
|
||||
// 267
|
||||
var chunk = parseInt(opts.chunk, 10); // 268
|
||||
if (isNaN(chunk)) chunk = 0; // 269
|
||||
// 270
|
||||
FS.debug && console.log("HTTP PUT (update) handler received chunk: ", chunk); // 271
|
||||
// 272
|
||||
// Validate with insert allow/deny; also mounts and retrieves the file // 273
|
||||
FS.Utility.validateAction(ref.collection.files._validators['insert'], ref.file, self.userId); // 274
|
||||
// 275
|
||||
self.createReadStream().pipe( FS.TempStore.createWriteStream(ref.file, chunk) ); // 276
|
||||
// 277
|
||||
// Send response // 278
|
||||
self.setStatusCode(200); // 279
|
||||
// 280
|
||||
return { _id: ref.file._id, chunk: chunk }; // 281
|
||||
}; // 282
|
||||
// 283
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
}).call(this);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(function () {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// packages/cfs:access-point/access-point-server.js //
|
||||
// //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
var path = Npm.require("path"); // 1
|
||||
// 2
|
||||
HTTP.publishFormats({ // 3
|
||||
fileRecordFormat: function (input) { // 4
|
||||
// Set the method scope content type to json // 5
|
||||
this.setContentType('application/json'); // 6
|
||||
if (FS.Utility.isArray(input)) { // 7
|
||||
return EJSON.stringify(FS.Utility.map(input, function (obj) { // 8
|
||||
return FS.Utility.cloneFileRecord(obj); // 9
|
||||
})); // 10
|
||||
} else { // 11
|
||||
return EJSON.stringify(FS.Utility.cloneFileRecord(input)); // 12
|
||||
} // 13
|
||||
} // 14
|
||||
}); // 15
|
||||
// 16
|
||||
/** // 17
|
||||
* @method FS.HTTP.setHeadersForGet // 18
|
||||
* @public // 19
|
||||
* @param {Array} headers - List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
|
||||
* @param {Array|String} [collections] - Which collections the headers should be added for. Omit this argument to add the header for all collections.
|
||||
* @returns {undefined} // 22
|
||||
*/ // 23
|
||||
FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { // 24
|
||||
if (typeof collections === "string") { // 25
|
||||
collections = [collections]; // 26
|
||||
} // 27
|
||||
if (collections) { // 28
|
||||
FS.Utility.each(collections, function(collectionName) { // 29
|
||||
getHeadersByCollection[collectionName] = headers || []; // 30
|
||||
}); // 31
|
||||
} else { // 32
|
||||
getHeaders = headers || []; // 33
|
||||
} // 34
|
||||
}; // 35
|
||||
// 36
|
||||
/** // 37
|
||||
* @method FS.HTTP.publish // 38
|
||||
* @public // 39
|
||||
* @param {FS.Collection} collection // 40
|
||||
* @param {Function} func - Publish function that returns a cursor. // 41
|
||||
* @returns {undefined} // 42
|
||||
* // 43
|
||||
* Publishes all documents returned by the cursor at a GET URL // 44
|
||||
* with the format baseUrl/record/collectionName. The publish // 45
|
||||
* function `this` is similar to normal `Meteor.publish`. // 46
|
||||
*/ // 47
|
||||
FS.HTTP.publish = function fsHttpPublish(collection, func) { // 48
|
||||
var name = baseUrl + '/record/' + collection.name; // 49
|
||||
// Mount collection listing URL using http-publish package // 50
|
||||
HTTP.publish({ // 51
|
||||
name: name, // 52
|
||||
defaultFormat: 'fileRecordFormat', // 53
|
||||
collection: collection, // 54
|
||||
collectionGet: true, // 55
|
||||
collectionPost: false, // 56
|
||||
documentGet: true, // 57
|
||||
documentPut: false, // 58
|
||||
documentDelete: false // 59
|
||||
}, func); // 60
|
||||
// 61
|
||||
FS.debug && console.log("Registered HTTP method GET URLs:\n\n" + name + '\n' + name + '/:id\n'); // 62
|
||||
}; // 63
|
||||
// 64
|
||||
/** // 65
|
||||
* @method FS.HTTP.unpublish // 66
|
||||
* @public // 67
|
||||
* @param {FS.Collection} collection // 68
|
||||
* @returns {undefined} // 69
|
||||
* // 70
|
||||
* Unpublishes a restpoint created by a call to `FS.HTTP.publish` // 71
|
||||
*/ // 72
|
||||
FS.HTTP.unpublish = function fsHttpUnpublish(collection) { // 73
|
||||
// Mount collection listing URL using http-publish package // 74
|
||||
HTTP.unpublish(baseUrl + '/record/' + collection.name); // 75
|
||||
}; // 76
|
||||
// 77
|
||||
_existingMountPoints = {}; // 78
|
||||
// 79
|
||||
/** // 80
|
||||
* @method defaultSelectorFunction // 81
|
||||
* @private // 82
|
||||
* @returns { collection, file } // 83
|
||||
* // 84
|
||||
* This is the default selector function // 85
|
||||
*/ // 86
|
||||
var defaultSelectorFunction = function() { // 87
|
||||
var self = this; // 88
|
||||
// Selector function // 89
|
||||
// // 90
|
||||
// This function will have to return the collection and the // 91
|
||||
// file. If file not found undefined is returned - if null is returned the // 92
|
||||
// search was not possible // 93
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {}); // 94
|
||||
// 95
|
||||
// Get the collection name from the url // 96
|
||||
var collectionName = opts.collectionName; // 97
|
||||
// 98
|
||||
// Get the id from the url // 99
|
||||
var id = opts.id; // 100
|
||||
// 101
|
||||
// Get the collection // 102
|
||||
var collection = FS._collections[collectionName]; // 103
|
||||
// 104
|
||||
// Get the file if possible else return null // 105
|
||||
var file = (id && collection)? collection.findOne({ _id: id }): null; // 106
|
||||
// 107
|
||||
// Return the collection and the file // 108
|
||||
return { // 109
|
||||
collection: collection, // 110
|
||||
file: file, // 111
|
||||
storeName: opts.store, // 112
|
||||
download: opts.download, // 113
|
||||
filename: opts.filename // 114
|
||||
}; // 115
|
||||
}; // 116
|
||||
// 117
|
||||
/* // 118
|
||||
* @method FS.HTTP.mount // 119
|
||||
* @public // 120
|
||||
* @param {array of string} mountPoints mount points to map rest functinality on // 121
|
||||
* @param {function} selector_f [selector] function returns `{ collection, file }` for mount points to work with // 122
|
||||
* // 123
|
||||
*/ // 124
|
||||
FS.HTTP.mount = function(mountPoints, selector_f) { // 125
|
||||
// We take mount points as an array and we get a selector function // 126
|
||||
var selectorFunction = selector_f || defaultSelectorFunction; // 127
|
||||
// 128
|
||||
var accessPoint = { // 129
|
||||
'stream': true, // 130
|
||||
'auth': expirationAuth, // 131
|
||||
'post': function(data) { // 132
|
||||
// Use the selector for finding the collection and file reference // 133
|
||||
var ref = selectorFunction.call(this); // 134
|
||||
// 135
|
||||
// We dont support post - this would be normal insert eg. of filerecord? // 136
|
||||
throw new Meteor.Error(501, "Not implemented", "Post is not supported"); // 137
|
||||
}, // 138
|
||||
'put': function(data) { // 139
|
||||
// Use the selector for finding the collection and file reference // 140
|
||||
var ref = selectorFunction.call(this); // 141
|
||||
// 142
|
||||
// Make sure we have a collection reference // 143
|
||||
if (!ref.collection) // 144
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found"); // 145
|
||||
// 146
|
||||
// Make sure we have a file reference // 147
|
||||
if (ref.file === null) { // 148
|
||||
// No id supplied so we will create a new FS.File instance and // 149
|
||||
// insert the supplied data. // 150
|
||||
return FS.HTTP.Handlers.PutInsert.apply(this, [ref]); // 151
|
||||
} else { // 152
|
||||
if (ref.file) { // 153
|
||||
return FS.HTTP.Handlers.PutUpdate.apply(this, [ref]); // 154
|
||||
} else { // 155
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found'); // 156
|
||||
} // 157
|
||||
} // 158
|
||||
}, // 159
|
||||
'get': function(data) { // 160
|
||||
// Use the selector for finding the collection and file reference // 161
|
||||
var ref = selectorFunction.call(this); // 162
|
||||
// 163
|
||||
// Make sure we have a collection reference // 164
|
||||
if (!ref.collection) // 165
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found"); // 166
|
||||
// 167
|
||||
// Make sure we have a file reference // 168
|
||||
if (ref.file === null) { // 169
|
||||
// No id supplied so we will return the published list of files ala // 170
|
||||
// http.publish in json format // 171
|
||||
return FS.HTTP.Handlers.GetList.apply(this, [ref]); // 172
|
||||
} else { // 173
|
||||
if (ref.file) { // 174
|
||||
return FS.HTTP.Handlers.Get.apply(this, [ref]); // 175
|
||||
} else { // 176
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found'); // 177
|
||||
} // 178
|
||||
} // 179
|
||||
}, // 180
|
||||
'delete': function(data) { // 181
|
||||
// Use the selector for finding the collection and file reference // 182
|
||||
var ref = selectorFunction.call(this); // 183
|
||||
// 184
|
||||
// Make sure we have a collection reference // 185
|
||||
if (!ref.collection) // 186
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found"); // 187
|
||||
// 188
|
||||
// Make sure we have a file reference // 189
|
||||
if (ref.file) { // 190
|
||||
return FS.HTTP.Handlers.Del.apply(this, [ref]); // 191
|
||||
} else { // 192
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found'); // 193
|
||||
} // 194
|
||||
} // 195
|
||||
}; // 196
|
||||
// 197
|
||||
var accessPoints = {}; // 198
|
||||
// 199
|
||||
// Add debug message // 200
|
||||
FS.debug && console.log('Registered HTTP method URLs:'); // 201
|
||||
// 202
|
||||
FS.Utility.each(mountPoints, function(mountPoint) { // 203
|
||||
// Couple mountpoint and accesspoint // 204
|
||||
accessPoints[mountPoint] = accessPoint; // 205
|
||||
// Remember our mountpoints // 206
|
||||
_existingMountPoints[mountPoint] = mountPoint; // 207
|
||||
// Add debug message // 208
|
||||
FS.debug && console.log(mountPoint); // 209
|
||||
}); // 210
|
||||
// 211
|
||||
// XXX: HTTP:methods should unmount existing mounts in case of overwriting? // 212
|
||||
HTTP.methods(accessPoints); // 213
|
||||
// 214
|
||||
}; // 215
|
||||
// 216
|
||||
/** // 217
|
||||
* @method FS.HTTP.unmount // 218
|
||||
* @public // 219
|
||||
* @param {string | array of string} [mountPoints] Optional, if not specified all mountpoints are unmounted // 220
|
||||
* // 221
|
||||
*/ // 222
|
||||
FS.HTTP.unmount = function(mountPoints) { // 223
|
||||
// The mountPoints is optional, can be string or array if undefined then // 224
|
||||
// _existingMountPoints will be used // 225
|
||||
var unmountList; // 226
|
||||
// Container for the mount points to unmount // 227
|
||||
var unmountPoints = {}; // 228
|
||||
// 229
|
||||
if (typeof mountPoints === 'undefined') { // 230
|
||||
// Use existing mount points - unmount all // 231
|
||||
unmountList = _existingMountPoints; // 232
|
||||
} else if (mountPoints === ''+mountPoints) { // 233
|
||||
// Got a string // 234
|
||||
unmountList = [mountPoints]; // 235
|
||||
} else if (mountPoints.length) { // 236
|
||||
// Got an array // 237
|
||||
unmountList = mountPoints; // 238
|
||||
} // 239
|
||||
// 240
|
||||
// If we have a list to unmount // 241
|
||||
if (unmountList) { // 242
|
||||
// Iterate over each item // 243
|
||||
FS.Utility.each(unmountList, function(mountPoint) { // 244
|
||||
// Check _existingMountPoints to make sure the mount point exists in our // 245
|
||||
// context / was created by the FS.HTTP.mount // 246
|
||||
if (_existingMountPoints[mountPoint]) { // 247
|
||||
// Mark as unmount // 248
|
||||
unmountPoints[mountPoint] = false; // 249
|
||||
// Release // 250
|
||||
delete _existingMountPoints[mountPoint]; // 251
|
||||
} // 252
|
||||
}); // 253
|
||||
FS.debug && console.log('FS.HTTP.unmount:'); // 254
|
||||
FS.debug && console.log(unmountPoints); // 255
|
||||
// Complete unmount // 256
|
||||
HTTP.methods(unmountPoints); // 257
|
||||
} // 258
|
||||
}; // 259
|
||||
// 260
|
||||
// ### FS.Collection maps on HTTP pr. default on the following restpoints: // 261
|
||||
// * // 262
|
||||
// baseUrl + '/files/:collectionName/:id/:filename', // 263
|
||||
// baseUrl + '/files/:collectionName/:id', // 264
|
||||
// baseUrl + '/files/:collectionName' // 265
|
||||
// // 266
|
||||
// Change/ replace the existing mount point by: // 267
|
||||
// ```js // 268
|
||||
// // unmount all existing // 269
|
||||
// FS.HTTP.unmount(); // 270
|
||||
// // Create new mount point // 271
|
||||
// FS.HTTP.mount([ // 272
|
||||
// '/cfs/files/:collectionName/:id/:filename', // 273
|
||||
// '/cfs/files/:collectionName/:id', // 274
|
||||
// '/cfs/files/:collectionName' // 275
|
||||
// ]); // 276
|
||||
// ``` // 277
|
||||
// // 278
|
||||
mountUrls = function mountUrls() { // 279
|
||||
// We unmount first in case we are calling this a second time // 280
|
||||
FS.HTTP.unmount(); // 281
|
||||
// 282
|
||||
FS.HTTP.mount([ // 283
|
||||
baseUrl + '/files/:collectionName/:id/:filename', // 284
|
||||
baseUrl + '/files/:collectionName/:id', // 285
|
||||
baseUrl + '/files/:collectionName' // 286
|
||||
]); // 287
|
||||
}; // 288
|
||||
// 289
|
||||
// Returns the userId from URL token // 290
|
||||
var expirationAuth = function expirationAuth() { // 291
|
||||
var self = this; // 292
|
||||
// 293
|
||||
// Read the token from '/hello?token=base64' // 294
|
||||
var encodedToken = self.query.token; // 295
|
||||
// 296
|
||||
FS.debug && console.log("token: "+encodedToken); // 297
|
||||
// 298
|
||||
if (!encodedToken || !Meteor.users) return false; // 299
|
||||
// 300
|
||||
// Check the userToken before adding it to the db query // 301
|
||||
// Set the this.userId // 302
|
||||
var tokenString = FS.Utility.atob(encodedToken); // 303
|
||||
// 304
|
||||
var tokenObject; // 305
|
||||
try { // 306
|
||||
tokenObject = JSON.parse(tokenString); // 307
|
||||
} catch(err) { // 308
|
||||
throw new Meteor.Error(400, 'Bad Request'); // 309
|
||||
} // 310
|
||||
// 311
|
||||
// XXX: Do some check here of the object // 312
|
||||
var userToken = tokenObject.authToken; // 313
|
||||
if (userToken !== ''+userToken) { // 314
|
||||
throw new Meteor.Error(400, 'Bad Request'); // 315
|
||||
} // 316
|
||||
// 317
|
||||
// If we have an expiration token we should check that it's still valid // 318
|
||||
if (tokenObject.expiration != null) { // 319
|
||||
// check if its too old // 320
|
||||
var now = Date.now(); // 321
|
||||
if (tokenObject.expiration < now) { // 322
|
||||
FS.debug && console.log('Expired token: ' + tokenObject.expiration + ' is less than ' + now); // 323
|
||||
throw new Meteor.Error(500, 'Expired token'); // 324
|
||||
} // 325
|
||||
} // 326
|
||||
// 327
|
||||
// We are not on a secure line - so we have to look up the user... // 328
|
||||
var user = Meteor.users.findOne({ // 329
|
||||
$or: [ // 330
|
||||
{'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)}, // 331
|
||||
{'services.resume.loginTokens.token': userToken} // 332
|
||||
] // 333
|
||||
}); // 334
|
||||
// 335
|
||||
// Set the userId in the scope // 336
|
||||
return user && user._id; // 337
|
||||
}; // 338
|
||||
// 339
|
||||
HTTP.methods( // 340
|
||||
{'/cfs/servertime': { // 341
|
||||
get: function(data) { // 342
|
||||
return Date.now().toString(); // 343
|
||||
} // 344
|
||||
} // 345
|
||||
}); // 346
|
||||
// 347
|
||||
// Unify client / server api // 348
|
||||
FS.HTTP.now = function() { // 349
|
||||
return Date.now(); // 350
|
||||
}; // 351
|
||||
// 352
|
||||
// Start up the basic mount points // 353
|
||||
Meteor.startup(function () { // 354
|
||||
mountUrls(); // 355
|
||||
}); // 356
|
||||
// 357
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
}).call(this);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
}).call(this);
|
||||
|
||||
|
||||
/* Exports */
|
||||
if (typeof Package === 'undefined') Package = {};
|
||||
Package['cfs:access-point'] = {};
|
||||
|
||||
})();
|
238
.snap-meteor-1.8/export.js
Normal file
238
.snap-meteor-1.8/export.js
Normal file
|
@ -0,0 +1,238 @@
|
|||
/* global JsonRoutes */
|
||||
if (Meteor.isServer) {
|
||||
// todo XXX once we have a real API in place, move that route there
|
||||
// todo XXX also share the route definition between the client and the server
|
||||
// so that we could use something like
|
||||
// `ApiRoutes.path('boards/export', boardId)``
|
||||
// on the client instead of copy/pasting the route path manually between the
|
||||
// client and the server.
|
||||
/**
|
||||
* @operation export
|
||||
* @tag Boards
|
||||
*
|
||||
* @summary This route is used to export the board.
|
||||
*
|
||||
* @description If user is already logged-in, pass loginToken as param
|
||||
* "authToken": '/api/boards/:boardId/export?authToken=:token'
|
||||
*
|
||||
* See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
|
||||
* for detailed explanations
|
||||
*
|
||||
* @param {string} boardId the ID of the board we are exporting
|
||||
* @param {string} authToken the loginToken
|
||||
*/
|
||||
JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) {
|
||||
const boardId = req.params.boardId;
|
||||
let user = null;
|
||||
|
||||
const loginToken = req.query.authToken;
|
||||
if (loginToken) {
|
||||
const hashToken = Accounts._hashLoginToken(loginToken);
|
||||
user = Meteor.users.findOne({
|
||||
'services.resume.loginTokens.hashedToken': hashToken,
|
||||
});
|
||||
} else if (!Meteor.settings.public.sandstorm) {
|
||||
Authentication.checkUserId(req.userId);
|
||||
user = Users.findOne({ _id: req.userId, isAdmin: true });
|
||||
}
|
||||
|
||||
const exporter = new Exporter(boardId);
|
||||
if (exporter.canExport(user)) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: exporter.build(),
|
||||
});
|
||||
} else {
|
||||
// we could send an explicit error message, but on the other hand the only
|
||||
// way to get there is by hacking the UI so let's keep it raw.
|
||||
JsonRoutes.sendResult(res, 403);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// exporter maybe is broken since Gridfs introduced, add fs and path
|
||||
|
||||
export class Exporter {
|
||||
constructor(boardId) {
|
||||
this._boardId = boardId;
|
||||
}
|
||||
|
||||
build() {
|
||||
const fs = Npm.require('fs');
|
||||
const os = Npm.require('os');
|
||||
const path = Npm.require('path');
|
||||
|
||||
const byBoard = { boardId: this._boardId };
|
||||
const byBoardNoLinked = {
|
||||
boardId: this._boardId,
|
||||
linkedId: { $in: ['', null] },
|
||||
};
|
||||
// we do not want to retrieve boardId in related elements
|
||||
const noBoardId = {
|
||||
fields: {
|
||||
boardId: 0,
|
||||
},
|
||||
};
|
||||
const result = {
|
||||
_format: 'wekan-board-1.0.0',
|
||||
};
|
||||
_.extend(
|
||||
result,
|
||||
Boards.findOne(this._boardId, {
|
||||
fields: {
|
||||
stars: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
result.lists = Lists.find(byBoard, noBoardId).fetch();
|
||||
result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch();
|
||||
result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch();
|
||||
result.customFields = CustomFields.find(
|
||||
{ boardIds: { $in: [this.boardId] } },
|
||||
{ fields: { boardId: 0 } },
|
||||
).fetch();
|
||||
result.comments = CardComments.find(byBoard, noBoardId).fetch();
|
||||
result.activities = Activities.find(byBoard, noBoardId).fetch();
|
||||
result.rules = Rules.find(byBoard, noBoardId).fetch();
|
||||
result.checklists = [];
|
||||
result.checklistItems = [];
|
||||
result.subtaskItems = [];
|
||||
result.triggers = [];
|
||||
result.actions = [];
|
||||
result.cards.forEach(card => {
|
||||
result.checklists.push(
|
||||
...Checklists.find({
|
||||
cardId: card._id,
|
||||
}).fetch(),
|
||||
);
|
||||
result.checklistItems.push(
|
||||
...ChecklistItems.find({
|
||||
cardId: card._id,
|
||||
}).fetch(),
|
||||
);
|
||||
result.subtaskItems.push(
|
||||
...Cards.find({
|
||||
parentId: card._id,
|
||||
}).fetch(),
|
||||
);
|
||||
});
|
||||
result.rules.forEach(rule => {
|
||||
result.triggers.push(
|
||||
...Triggers.find(
|
||||
{
|
||||
_id: rule.triggerId,
|
||||
},
|
||||
noBoardId,
|
||||
).fetch(),
|
||||
);
|
||||
result.actions.push(
|
||||
...Actions.find(
|
||||
{
|
||||
_id: rule.actionId,
|
||||
},
|
||||
noBoardId,
|
||||
).fetch(),
|
||||
);
|
||||
});
|
||||
|
||||
// [Old] for attachments we only export IDs and absolute url to original doc
|
||||
// [New] Encode attachment to base64
|
||||
const getBase64Data = function(doc, callback) {
|
||||
let buffer = new Buffer(0);
|
||||
// callback has the form function (err, res) {}
|
||||
const tmpFile = path.join(
|
||||
os.tmpdir(),
|
||||
`tmpexport${process.pid}${Math.random()}`,
|
||||
);
|
||||
const tmpWriteable = fs.createWriteStream(tmpFile);
|
||||
const readStream = doc.createReadStream();
|
||||
readStream.on('data', function(chunk) {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
});
|
||||
readStream.on('error', function(err) {
|
||||
callback(err, null);
|
||||
});
|
||||
readStream.on('end', function() {
|
||||
// done
|
||||
fs.unlink(tmpFile, () => {
|
||||
//ignored
|
||||
});
|
||||
callback(null, buffer.toString('base64'));
|
||||
});
|
||||
readStream.pipe(tmpWriteable);
|
||||
};
|
||||
const getBase64DataSync = Meteor.wrapAsync(getBase64Data);
|
||||
result.attachments = Attachments.find(byBoard)
|
||||
.fetch()
|
||||
.map(attachment => {
|
||||
return {
|
||||
_id: attachment._id,
|
||||
cardId: attachment.cardId,
|
||||
// url: FlowRouter.url(attachment.url()),
|
||||
file: getBase64DataSync(attachment),
|
||||
name: attachment.original.name,
|
||||
type: attachment.original.type,
|
||||
};
|
||||
});
|
||||
|
||||
// we also have to export some user data - as the other elements only
|
||||
// include id but we have to be careful:
|
||||
// 1- only exports users that are linked somehow to that board
|
||||
// 2- do not export any sensitive information
|
||||
const users = {};
|
||||
result.members.forEach(member => {
|
||||
users[member.userId] = true;
|
||||
});
|
||||
result.lists.forEach(list => {
|
||||
users[list.userId] = true;
|
||||
});
|
||||
result.cards.forEach(card => {
|
||||
users[card.userId] = true;
|
||||
if (card.members) {
|
||||
card.members.forEach(memberId => {
|
||||
users[memberId] = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
result.comments.forEach(comment => {
|
||||
users[comment.userId] = true;
|
||||
});
|
||||
result.activities.forEach(activity => {
|
||||
users[activity.userId] = true;
|
||||
});
|
||||
result.checklists.forEach(checklist => {
|
||||
users[checklist.userId] = true;
|
||||
});
|
||||
const byUserIds = {
|
||||
_id: {
|
||||
$in: Object.getOwnPropertyNames(users),
|
||||
},
|
||||
};
|
||||
// we use whitelist to be sure we do not expose inadvertently
|
||||
// some secret fields that gets added to User later.
|
||||
const userFields = {
|
||||
fields: {
|
||||
_id: 1,
|
||||
username: 1,
|
||||
'profile.fullname': 1,
|
||||
'profile.initials': 1,
|
||||
'profile.avatarUrl': 1,
|
||||
},
|
||||
};
|
||||
result.users = Users.find(byUserIds, userFields)
|
||||
.fetch()
|
||||
.map(user => {
|
||||
// user avatar is stored as a relative url, we export absolute
|
||||
if ((user.profile || {}).avatarUrl) {
|
||||
user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl);
|
||||
}
|
||||
return user;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
canExport(user) {
|
||||
const board = Boards.findOne(this._boardId);
|
||||
return board && board.isVisibleBy(user);
|
||||
}
|
||||
}
|
155
.snap-meteor-1.8/future/snapcraft.yaml
Normal file
155
.snap-meteor-1.8/future/snapcraft.yaml
Normal file
|
@ -0,0 +1,155 @@
|
|||
name: wekan
|
||||
version: git
|
||||
summary: The open-source kanban
|
||||
description: |
|
||||
Wekan is an open-source and collaborative kanban board application.
|
||||
|
||||
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.
|
||||
Depending on target environment, some configuration settings might need to be adjusted.
|
||||
For full list of configuration options call:
|
||||
$ wekan.help
|
||||
|
||||
confinement: strict
|
||||
grade: stable
|
||||
base: core18
|
||||
|
||||
architectures:
|
||||
- amd64
|
||||
|
||||
plugs:
|
||||
mongodb-plug:
|
||||
interface: content
|
||||
target: $SNAP_DATA/shared
|
||||
|
||||
hooks:
|
||||
configure:
|
||||
plugs:
|
||||
- network
|
||||
- network-bind
|
||||
|
||||
slots:
|
||||
mongodb-slot:
|
||||
interface: content
|
||||
write:
|
||||
- $SNAP_DATA/share
|
||||
|
||||
apps:
|
||||
wekan:
|
||||
command: wekan-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind]
|
||||
|
||||
mongodb:
|
||||
command: mongodb-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind]
|
||||
|
||||
caddy:
|
||||
command: caddy-control
|
||||
daemon: simple
|
||||
plugs: [network, network-bind]
|
||||
|
||||
help:
|
||||
command: wekan-help
|
||||
|
||||
database-backup:
|
||||
command: mongodb-backup
|
||||
plugs: [network, network-bind]
|
||||
|
||||
database-list-backups:
|
||||
command: ls -al $SNAP_COMMON/db-backups/
|
||||
|
||||
database-restore:
|
||||
command: mongodb-restore
|
||||
plugs: [network, network-bind]
|
||||
|
||||
parts:
|
||||
mongodb:
|
||||
source: https://repo.mongodb.org/apt/ubuntu/dists/xenial/mongodb-org/4.2/multiverse/binary-amd64/mongodb-org-server_4.2.2_amd64.deb
|
||||
#https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.14.tgz
|
||||
#https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.22.tgz
|
||||
plugin: dump
|
||||
stage-packages: [libssl1.0.0, libcurl3]
|
||||
filesets:
|
||||
mongo:
|
||||
- usr
|
||||
- bin
|
||||
- lib
|
||||
stage:
|
||||
- $mongo
|
||||
prime:
|
||||
- $mongo
|
||||
|
||||
wekan:
|
||||
source: .
|
||||
plugin: nodejs
|
||||
node-engine: 12.14.1
|
||||
node-packages:
|
||||
- node-gyp
|
||||
- node-pre-gyp
|
||||
- fibers
|
||||
build-packages:
|
||||
- ca-certificates
|
||||
- apt-utils
|
||||
- build-essential
|
||||
- python
|
||||
- python3
|
||||
- g++
|
||||
- capnproto
|
||||
- curl
|
||||
- libcurl3
|
||||
- execstack
|
||||
- nodejs
|
||||
- npm
|
||||
stage-packages:
|
||||
- libfontconfig1
|
||||
override-build: |
|
||||
echo "Cleaning environment first"
|
||||
rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules
|
||||
rm -rf .build
|
||||
echo "Installing meteor"
|
||||
curl https://install.meteor.com/ -o install_meteor.sh
|
||||
chmod +x install_meteor.sh
|
||||
sh install_meteor.sh
|
||||
rm install_meteor.sh
|
||||
rm -rf .build
|
||||
meteor add standard-minifier-js --allow-superuser
|
||||
meteor npm install --allow-superuser
|
||||
meteor npm install --allow-superuser --save babel-runtime
|
||||
meteor build .build --directory --allow-superuser
|
||||
cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
|
||||
cd .build/bundle/programs/server
|
||||
npm install
|
||||
npm install --allow-superuser --save babel-runtime
|
||||
# Change back to Wekan source directory
|
||||
cd ../../../..
|
||||
cp -r .build/bundle/* $SNAPCRAFT_PART_INSTALL/
|
||||
cp .build/bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/tar/lib/.mkdir.js.swp
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
|
||||
organize:
|
||||
README: README.wekan
|
||||
prime:
|
||||
- -lib/node_modules/node-pre-gyp/node_modules/tar/lib/.unpack.js.swp
|
||||
|
||||
helpers:
|
||||
source: snap-src
|
||||
plugin: dump
|
||||
|
||||
caddy:
|
||||
plugin: dump
|
||||
source: https://caddyserver.com/download/linux/amd64?license=personal&telemetry=off
|
||||
source-type: tar
|
||||
organize:
|
||||
caddy: bin/caddy
|
||||
CHANGES.txt: CADDY_CHANGES.txt
|
||||
EULA.txt: CADDY_EULA.txt
|
||||
LICENSES.txt: CADDY_LICENSES.txt
|
||||
README.txt: CADDY_README.txt
|
||||
stage:
|
||||
- -init
|
584
.snap-meteor-1.8/ldap.js
Normal file
584
.snap-meteor-1.8/ldap.js
Normal file
|
@ -0,0 +1,584 @@
|
|||
import ldapjs from 'ldapjs';
|
||||
import util from 'util';
|
||||
import Bunyan from 'bunyan';
|
||||
import {log_debug, log_info, log_warn, log_error} from './logger';
|
||||
|
||||
|
||||
export default class LDAP {
|
||||
constructor() {
|
||||
this.ldapjs = ldapjs;
|
||||
|
||||
this.connected = false;
|
||||
|
||||
this.options = {
|
||||
host : this.constructor.settings_get('LDAP_HOST'),
|
||||
port : this.constructor.settings_get('LDAP_PORT'),
|
||||
Reconnect : this.constructor.settings_get('LDAP_RECONNECT'),
|
||||
timeout : this.constructor.settings_get('LDAP_TIMEOUT'),
|
||||
connect_timeout : this.constructor.settings_get('LDAP_CONNECT_TIMEOUT'),
|
||||
idle_timeout : this.constructor.settings_get('LDAP_IDLE_TIMEOUT'),
|
||||
encryption : this.constructor.settings_get('LDAP_ENCRYPTION'),
|
||||
ca_cert : this.constructor.settings_get('LDAP_CA_CERT'),
|
||||
reject_unauthorized : this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false,
|
||||
Authentication : this.constructor.settings_get('LDAP_AUTHENTIFICATION'),
|
||||
Authentication_UserDN : this.constructor.settings_get('LDAP_AUTHENTIFICATION_USERDN'),
|
||||
Authentication_Password : this.constructor.settings_get('LDAP_AUTHENTIFICATION_PASSWORD'),
|
||||
Authentication_Fallback : this.constructor.settings_get('LDAP_LOGIN_FALLBACK'),
|
||||
BaseDN : this.constructor.settings_get('LDAP_BASEDN'),
|
||||
Internal_Log_Level : this.constructor.settings_get('INTERNAL_LOG_LEVEL'),
|
||||
User_Authentication : this.constructor.settings_get('LDAP_USER_AUTHENTICATION'),
|
||||
User_Authentication_Field : this.constructor.settings_get('LDAP_USER_AUTHENTICATION_FIELD'),
|
||||
User_Attributes : this.constructor.settings_get('LDAP_USER_ATTRIBUTES'),
|
||||
User_Search_Filter : this.constructor.settings_get('LDAP_USER_SEARCH_FILTER'),
|
||||
User_Search_Scope : this.constructor.settings_get('LDAP_USER_SEARCH_SCOPE'),
|
||||
User_Search_Field : this.constructor.settings_get('LDAP_USER_SEARCH_FIELD'),
|
||||
Search_Page_Size : this.constructor.settings_get('LDAP_SEARCH_PAGE_SIZE'),
|
||||
Search_Size_Limit : this.constructor.settings_get('LDAP_SEARCH_SIZE_LIMIT'),
|
||||
group_filter_enabled : this.constructor.settings_get('LDAP_GROUP_FILTER_ENABLE'),
|
||||
group_filter_object_class : this.constructor.settings_get('LDAP_GROUP_FILTER_OBJECTCLASS'),
|
||||
group_filter_group_id_attribute : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE'),
|
||||
group_filter_group_member_attribute: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE'),
|
||||
group_filter_group_member_format : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT'),
|
||||
group_filter_group_name : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_NAME'),
|
||||
};
|
||||
}
|
||||
|
||||
static settings_get(name, ...args) {
|
||||
let value = process.env[name];
|
||||
if (value !== undefined) {
|
||||
if (value === 'true' || value === 'false') {
|
||||
value = JSON.parse(value);
|
||||
} else if (value !== '' && !isNaN(value)) {
|
||||
value = Number(value);
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
log_warn(`Lookup for unset variable: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
connectSync(...args) {
|
||||
if (!this._connectSync) {
|
||||
this._connectSync = Meteor.wrapAsync(this.connectAsync, this);
|
||||
}
|
||||
return this._connectSync(...args);
|
||||
}
|
||||
|
||||
searchAllSync(...args) {
|
||||
|
||||
if (!this._searchAllSync) {
|
||||
this._searchAllSync = Meteor.wrapAsync(this.searchAllAsync, this);
|
||||
}
|
||||
return this._searchAllSync(...args);
|
||||
}
|
||||
|
||||
connectAsync(callback) {
|
||||
log_info('Init setup');
|
||||
|
||||
let replied = false;
|
||||
|
||||
const connectionOptions = {
|
||||
url : `${this.options.host}:${this.options.port}`,
|
||||
timeout : this.options.timeout,
|
||||
connectTimeout: this.options.connect_timeout,
|
||||
idleTimeout : this.options.idle_timeout,
|
||||
reconnect : this.options.Reconnect,
|
||||
};
|
||||
|
||||
if (this.options.Internal_Log_Level !== 'disabled') {
|
||||
connectionOptions.log = new Bunyan({
|
||||
name : 'ldapjs',
|
||||
component: 'client',
|
||||
stream : process.stderr,
|
||||
level : this.options.Internal_Log_Level,
|
||||
});
|
||||
}
|
||||
|
||||
const tlsOptions = {
|
||||
rejectUnauthorized: this.options.reject_unauthorized,
|
||||
};
|
||||
|
||||
if (this.options.ca_cert && this.options.ca_cert !== '') {
|
||||
// Split CA cert into array of strings
|
||||
const chainLines = this.constructor.settings_get('LDAP_CA_CERT').split('\n');
|
||||
let cert = [];
|
||||
const ca = [];
|
||||
chainLines.forEach((line) => {
|
||||
cert.push(line);
|
||||
if (line.match(/-END CERTIFICATE-/)) {
|
||||
ca.push(cert.join('\n'));
|
||||
cert = [];
|
||||
}
|
||||
});
|
||||
tlsOptions.ca = ca;
|
||||
}
|
||||
|
||||
if (this.options.encryption === 'ssl') {
|
||||
connectionOptions.url = `ldaps://${connectionOptions.url}`;
|
||||
connectionOptions.tlsOptions = tlsOptions;
|
||||
} else {
|
||||
connectionOptions.url = `ldap://${connectionOptions.url}`;
|
||||
}
|
||||
|
||||
log_info('Connecting', connectionOptions.url);
|
||||
log_debug(`connectionOptions${util.inspect(connectionOptions)}`);
|
||||
|
||||
this.client = ldapjs.createClient(connectionOptions);
|
||||
|
||||
this.bindSync = Meteor.wrapAsync(this.client.bind, this.client);
|
||||
|
||||
this.client.on('error', (error) => {
|
||||
log_error('connection', error);
|
||||
if (replied === false) {
|
||||
replied = true;
|
||||
callback(error, null);
|
||||
}
|
||||
});
|
||||
|
||||
this.client.on('idle', () => {
|
||||
log_info('Idle');
|
||||
this.disconnect();
|
||||
});
|
||||
|
||||
this.client.on('close', () => {
|
||||
log_info('Closed');
|
||||
});
|
||||
|
||||
if (this.options.encryption === 'tls') {
|
||||
// Set host parameter for tls.connect which is used by ldapjs starttls. This shouldn't be needed in newer nodejs versions (e.g v5.6.0).
|
||||
// https://github.com/RocketChat/Rocket.Chat/issues/2035
|
||||
// https://github.com/mcavage/node-ldapjs/issues/349
|
||||
tlsOptions.host = this.options.host;
|
||||
|
||||
log_info('Starting TLS');
|
||||
log_debug('tlsOptions', tlsOptions);
|
||||
|
||||
this.client.starttls(tlsOptions, null, (error, response) => {
|
||||
if (error) {
|
||||
log_error('TLS connection', error);
|
||||
if (replied === false) {
|
||||
replied = true;
|
||||
callback(error, null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
log_info('TLS connected');
|
||||
this.connected = true;
|
||||
if (replied === false) {
|
||||
replied = true;
|
||||
callback(null, response);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.client.on('connect', (response) => {
|
||||
log_info('LDAP connected');
|
||||
this.connected = true;
|
||||
if (replied === false) {
|
||||
replied = true;
|
||||
callback(null, response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (replied === false) {
|
||||
log_error('connection time out', connectionOptions.connectTimeout);
|
||||
replied = true;
|
||||
callback(new Error('Timeout'));
|
||||
}
|
||||
}, connectionOptions.connectTimeout);
|
||||
}
|
||||
|
||||
getUserFilter(username) {
|
||||
const filter = [];
|
||||
|
||||
if (this.options.User_Search_Filter !== '') {
|
||||
if (this.options.User_Search_Filter[0] === '(') {
|
||||
filter.push(`${this.options.User_Search_Filter}`);
|
||||
} else {
|
||||
filter.push(`(${this.options.User_Search_Filter})`);
|
||||
}
|
||||
}
|
||||
|
||||
const usernameFilter = this.options.User_Search_Field.split(',').map((item) => `(${item}=${username})`);
|
||||
|
||||
if (usernameFilter.length === 0) {
|
||||
log_error('LDAP_LDAP_User_Search_Field not defined');
|
||||
} else if (usernameFilter.length === 1) {
|
||||
filter.push(`${usernameFilter[0]}`);
|
||||
} else {
|
||||
filter.push(`(|${usernameFilter.join('')})`);
|
||||
}
|
||||
|
||||
return `(&${filter.join('')})`;
|
||||
}
|
||||
|
||||
bindUserIfNecessary(username, password) {
|
||||
|
||||
if (this.domainBinded === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.User_Authentication) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!this.options.BaseDN) throw new Error('BaseDN is not provided');
|
||||
|
||||
const userDn = `${this.options.User_Authentication_Field}=${username},${this.options.BaseDN}`;
|
||||
|
||||
this.bindSync(userDn, password);
|
||||
this.domainBinded = true;
|
||||
}
|
||||
|
||||
bindIfNecessary() {
|
||||
if (this.domainBinded === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.options.Authentication !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_info('Binding UserDN', this.options.Authentication_UserDN);
|
||||
|
||||
this.bindSync(this.options.Authentication_UserDN, this.options.Authentication_Password);
|
||||
this.domainBinded = true;
|
||||
}
|
||||
|
||||
searchUsersSync(username, page) {
|
||||
this.bindIfNecessary();
|
||||
const searchOptions = {
|
||||
filter : this.getUserFilter(username),
|
||||
scope : this.options.User_Search_Scope || 'sub',
|
||||
sizeLimit: this.options.Search_Size_Limit,
|
||||
};
|
||||
|
||||
if (!!this.options.User_Attributes) searchOptions.attributes = this.options.User_Attributes.split(',');
|
||||
|
||||
if (this.options.Search_Page_Size > 0) {
|
||||
searchOptions.paged = {
|
||||
pageSize : this.options.Search_Page_Size,
|
||||
pagePause: !!page,
|
||||
};
|
||||
}
|
||||
|
||||
log_info('Searching user', username);
|
||||
log_debug('searchOptions', searchOptions);
|
||||
log_debug('BaseDN', this.options.BaseDN);
|
||||
|
||||
if (page) {
|
||||
return this.searchAllPaged(this.options.BaseDN, searchOptions, page);
|
||||
}
|
||||
|
||||
return this.searchAllSync(this.options.BaseDN, searchOptions);
|
||||
}
|
||||
|
||||
getUserByIdSync(id, attribute) {
|
||||
this.bindIfNecessary();
|
||||
|
||||
const Unique_Identifier_Field = this.constructor.settings_get('LDAP_UNIQUE_IDENTIFIER_FIELD').split(',');
|
||||
|
||||
let filter;
|
||||
|
||||
if (attribute) {
|
||||
filter = new this.ldapjs.filters.EqualityFilter({
|
||||
attribute,
|
||||
value: new Buffer(id, 'hex'),
|
||||
});
|
||||
} else {
|
||||
const filters = [];
|
||||
Unique_Identifier_Field.forEach((item) => {
|
||||
filters.push(new this.ldapjs.filters.EqualityFilter({
|
||||
attribute: item,
|
||||
value : new Buffer(id, 'hex'),
|
||||
}));
|
||||
});
|
||||
|
||||
filter = new this.ldapjs.filters.OrFilter({ filters });
|
||||
}
|
||||
|
||||
const searchOptions = {
|
||||
filter,
|
||||
scope: 'sub',
|
||||
};
|
||||
|
||||
log_info('Searching by id', id);
|
||||
log_debug('search filter', searchOptions.filter.toString());
|
||||
log_debug('BaseDN', this.options.BaseDN);
|
||||
|
||||
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
|
||||
|
||||
if (!Array.isArray(result) || result.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.length > 1) {
|
||||
log_error('Search by id', id, 'returned', result.length, 'records');
|
||||
}
|
||||
|
||||
return result[0];
|
||||
}
|
||||
|
||||
getUserByUsernameSync(username) {
|
||||
this.bindIfNecessary();
|
||||
|
||||
const searchOptions = {
|
||||
filter: this.getUserFilter(username),
|
||||
scope : this.options.User_Search_Scope || 'sub',
|
||||
};
|
||||
|
||||
log_info('Searching user', username);
|
||||
log_debug('searchOptions', searchOptions);
|
||||
log_debug('BaseDN', this.options.BaseDN);
|
||||
|
||||
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
|
||||
|
||||
if (!Array.isArray(result) || result.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.length > 1) {
|
||||
log_error('Search by username', username, 'returned', result.length, 'records');
|
||||
}
|
||||
|
||||
return result[0];
|
||||
}
|
||||
|
||||
getUserGroups(username, ldapUser) {
|
||||
if (!this.options.group_filter_enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const filter = ['(&'];
|
||||
|
||||
if (this.options.group_filter_object_class !== '') {
|
||||
filter.push(`(objectclass=${this.options.group_filter_object_class})`);
|
||||
}
|
||||
|
||||
if (this.options.group_filter_group_member_attribute !== '') {
|
||||
const format_value = ldapUser[this.options.group_filter_group_member_format];
|
||||
if (format_value) {
|
||||
filter.push(`(${this.options.group_filter_group_member_attribute}=${format_value})`);
|
||||
}
|
||||
}
|
||||
|
||||
filter.push(')');
|
||||
|
||||
const searchOptions = {
|
||||
filter: filter.join('').replace(/#{username}/g, username),
|
||||
scope : 'sub',
|
||||
};
|
||||
|
||||
log_debug('Group list filter LDAP:', searchOptions.filter);
|
||||
|
||||
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
|
||||
|
||||
if (!Array.isArray(result) || result.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const grp_identifier = this.options.group_filter_group_id_attribute || 'cn';
|
||||
const groups = [];
|
||||
result.map((item) => {
|
||||
groups.push(item[grp_identifier]);
|
||||
});
|
||||
log_debug(`Groups: ${groups.join(', ')}`);
|
||||
return groups;
|
||||
|
||||
}
|
||||
|
||||
isUserInGroup(username, ldapUser) {
|
||||
if (!this.options.group_filter_enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const grps = this.getUserGroups(username, ldapUser);
|
||||
|
||||
const filter = ['(&'];
|
||||
|
||||
if (this.options.group_filter_object_class !== '') {
|
||||
filter.push(`(objectclass=${this.options.group_filter_object_class})`);
|
||||
}
|
||||
|
||||
if (this.options.group_filter_group_member_attribute !== '') {
|
||||
const format_value = ldapUser[this.options.group_filter_group_member_format];
|
||||
if (format_value) {
|
||||
filter.push(`(${this.options.group_filter_group_member_attribute}=${format_value})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.group_filter_group_id_attribute !== '') {
|
||||
filter.push(`(${this.options.group_filter_group_id_attribute}=${this.options.group_filter_group_name})`);
|
||||
}
|
||||
filter.push(')');
|
||||
|
||||
const searchOptions = {
|
||||
filter: filter.join('').replace(/#{username}/g, username),
|
||||
scope : 'sub',
|
||||
};
|
||||
|
||||
log_debug('Group filter LDAP:', searchOptions.filter);
|
||||
|
||||
const result = this.searchAllSync(this.options.BaseDN, searchOptions);
|
||||
|
||||
if (!Array.isArray(result) || result.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
extractLdapEntryData(entry) {
|
||||
const values = {
|
||||
_raw: entry.raw,
|
||||
};
|
||||
|
||||
Object.keys(values._raw).forEach((key) => {
|
||||
const value = values._raw[key];
|
||||
|
||||
if (!['thumbnailPhoto', 'jpegPhoto'].includes(key)) {
|
||||
if (value instanceof Buffer) {
|
||||
values[key] = value.toString();
|
||||
} else {
|
||||
values[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
searchAllPaged(BaseDN, options, page) {
|
||||
this.bindIfNecessary();
|
||||
|
||||
const processPage = ({ entries, title, end, next }) => {
|
||||
log_info(title);
|
||||
// Force LDAP idle to wait the record processing
|
||||
this.client._updateIdle(true);
|
||||
page(null, entries, {
|
||||
end, next: () => {
|
||||
// Reset idle timer
|
||||
this.client._updateIdle();
|
||||
next && next();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.client.search(BaseDN, options, (error, res) => {
|
||||
if (error) {
|
||||
log_error(error);
|
||||
page(error);
|
||||
return;
|
||||
}
|
||||
|
||||
res.on('error', (error) => {
|
||||
log_error(error);
|
||||
page(error);
|
||||
return;
|
||||
});
|
||||
|
||||
let entries = [];
|
||||
|
||||
const internalPageSize = options.paged && options.paged.pageSize > 0 ? options.paged.pageSize * 2 : 500;
|
||||
|
||||
res.on('searchEntry', (entry) => {
|
||||
entries.push(this.extractLdapEntryData(entry));
|
||||
|
||||
if (entries.length >= internalPageSize) {
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Internal Page',
|
||||
end : false,
|
||||
});
|
||||
entries = [];
|
||||
}
|
||||
});
|
||||
|
||||
res.on('page', (result, next) => {
|
||||
if (!next) {
|
||||
this.client._updateIdle(true);
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Final Page',
|
||||
end : true,
|
||||
});
|
||||
} else if (entries.length) {
|
||||
log_info('Page');
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Page',
|
||||
end : false,
|
||||
next,
|
||||
});
|
||||
entries = [];
|
||||
}
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
if (entries.length) {
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Final Page',
|
||||
end : true,
|
||||
});
|
||||
entries = [];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
searchAllAsync(BaseDN, options, callback) {
|
||||
this.bindIfNecessary();
|
||||
|
||||
this.client.search(BaseDN, options, (error, res) => {
|
||||
if (error) {
|
||||
log_error(error);
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
|
||||
res.on('error', (error) => {
|
||||
log_error(error);
|
||||
callback(error);
|
||||
return;
|
||||
});
|
||||
|
||||
const entries = [];
|
||||
|
||||
res.on('searchEntry', (entry) => {
|
||||
entries.push(this.extractLdapEntryData(entry));
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
log_info('Search result count', entries.length);
|
||||
callback(null, entries);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
authSync(dn, password) {
|
||||
log_info('Authenticating', dn);
|
||||
|
||||
try {
|
||||
if (password === '') {
|
||||
throw new Error('Password is not provided');
|
||||
}
|
||||
this.bindSync(dn, password);
|
||||
log_info('Authenticated', dn);
|
||||
return true;
|
||||
} catch (error) {
|
||||
log_info('Not authenticated', dn);
|
||||
log_debug('error', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.connected = false;
|
||||
this.domainBinded = false;
|
||||
log_info('Disconecting');
|
||||
this.client.unbind();
|
||||
}
|
||||
}
|
149
.snap-meteor-1.8/oidc_server.js
Normal file
149
.snap-meteor-1.8/oidc_server.js
Normal file
|
@ -0,0 +1,149 @@
|
|||
Oidc = {};
|
||||
|
||||
OAuth.registerService('oidc', 2, null, function (query) {
|
||||
|
||||
var debug = process.env.DEBUG || false;
|
||||
var token = getToken(query);
|
||||
if (debug) console.log('XXX: register token:', token);
|
||||
|
||||
var accessToken = token.access_token || token.id_token;
|
||||
var expiresAt = (+new Date) + (1000 * parseInt(token.expires_in, 10));
|
||||
|
||||
var userinfo = getUserInfo(accessToken);
|
||||
if (debug) console.log('XXX: userinfo:', userinfo);
|
||||
|
||||
var serviceData = {};
|
||||
serviceData.id = userinfo[process.env.OAUTH2_ID_MAP]; // || userinfo["id"];
|
||||
serviceData.username = userinfo[process.env.OAUTH2_USERNAME_MAP]; // || userinfo["uid"];
|
||||
serviceData.fullname = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"];
|
||||
serviceData.accessToken = accessToken;
|
||||
serviceData.expiresAt = expiresAt;
|
||||
serviceData.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"];
|
||||
|
||||
if (accessToken) {
|
||||
var tokenContent = getTokenContent(accessToken);
|
||||
var fields = _.pick(tokenContent, getConfiguration().idTokenWhitelistFields);
|
||||
_.extend(serviceData, fields);
|
||||
}
|
||||
|
||||
if (token.refresh_token)
|
||||
serviceData.refreshToken = token.refresh_token;
|
||||
if (debug) console.log('XXX: serviceData:', serviceData);
|
||||
|
||||
var profile = {};
|
||||
profile.name = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"];
|
||||
profile.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"];
|
||||
if (debug) console.log('XXX: profile:', profile);
|
||||
|
||||
return {
|
||||
serviceData: serviceData,
|
||||
options: { profile: profile }
|
||||
};
|
||||
});
|
||||
|
||||
var userAgent = "Meteor";
|
||||
if (Meteor.release) {
|
||||
userAgent += "/" + Meteor.release;
|
||||
}
|
||||
|
||||
var getToken = function (query) {
|
||||
var debug = process.env.DEBUG || false;
|
||||
var config = getConfiguration();
|
||||
if(config.tokenEndpoint.includes('https://')){
|
||||
var serverTokenEndpoint = config.tokenEndpoint;
|
||||
}else{
|
||||
var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint;
|
||||
}
|
||||
var requestPermissions = config.requestPermissions;
|
||||
var response;
|
||||
|
||||
try {
|
||||
response = HTTP.post(
|
||||
serverTokenEndpoint,
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
"User-Agent": userAgent
|
||||
},
|
||||
params: {
|
||||
code: query.code,
|
||||
client_id: config.clientId,
|
||||
client_secret: OAuth.openSecret(config.secret),
|
||||
redirect_uri: OAuth._redirectUri('oidc', config),
|
||||
grant_type: 'authorization_code',
|
||||
scope: requestPermissions,
|
||||
state: query.state
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message),
|
||||
{ response: err.response });
|
||||
}
|
||||
if (response.data.error) {
|
||||
// if the http response was a json object with an error attribute
|
||||
throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error);
|
||||
} else {
|
||||
if (debug) console.log('XXX: getToken response: ', response.data);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
|
||||
var getUserInfo = function (accessToken) {
|
||||
var debug = process.env.DEBUG || false;
|
||||
var config = getConfiguration();
|
||||
// Some userinfo endpoints use a different base URL than the authorization or token endpoints.
|
||||
// This logic allows the end user to override the setting by providing the full URL to userinfo in their config.
|
||||
if (config.userinfoEndpoint.includes("https://")) {
|
||||
var serverUserinfoEndpoint = config.userinfoEndpoint;
|
||||
} else {
|
||||
var serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint;
|
||||
}
|
||||
var response;
|
||||
try {
|
||||
response = HTTP.get(
|
||||
serverUserinfoEndpoint,
|
||||
{
|
||||
headers: {
|
||||
"User-Agent": userAgent,
|
||||
"Authorization": "Bearer " + accessToken
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
throw _.extend(new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + err.message),
|
||||
{response: err.response});
|
||||
}
|
||||
if (debug) console.log('XXX: getUserInfo response: ', response.data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
var getConfiguration = function () {
|
||||
var config = ServiceConfiguration.configurations.findOne({ service: 'oidc' });
|
||||
if (!config) {
|
||||
throw new ServiceConfiguration.ConfigError('Service oidc not configured.');
|
||||
}
|
||||
return config;
|
||||
};
|
||||
|
||||
var getTokenContent = function (token) {
|
||||
var content = null;
|
||||
if (token) {
|
||||
try {
|
||||
var parts = token.split('.');
|
||||
var header = JSON.parse(new Buffer(parts[0], 'base64').toString());
|
||||
content = JSON.parse(new Buffer(parts[1], 'base64').toString());
|
||||
var signature = new Buffer(parts[2], 'base64');
|
||||
var signed = parts[0] + '.' + parts[1];
|
||||
} catch (err) {
|
||||
this.content = {
|
||||
exp: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
Oidc.retrieveCredential = function (credentialToken, credentialSecret) {
|
||||
return OAuth.retrieveCredential(credentialToken, credentialSecret);
|
||||
};
|
5184
.snap-meteor-1.8/package-lock.json
generated
Normal file
5184
.snap-meteor-1.8/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
73
.snap-meteor-1.8/package.json
Normal file
73
.snap-meteor-1.8/package.json
Normal file
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"name": "wekan",
|
||||
"version": "v3.78.0",
|
||||
"description": "Open-Source kanban",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "eslint --cache --ext .js --ignore-path .eslintignore .",
|
||||
"lint:eslint:fix": "eslint --ext .js --ignore-path .eslintignore --fix .",
|
||||
"lint:staged": "lint-staged",
|
||||
"prettify": "prettier --write '**/*.js' '**/*.jsx'",
|
||||
"test": "npm run lint"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"meteor npm run prettify",
|
||||
"meteor npm run lint:eslint:fix",
|
||||
"git add --force"
|
||||
],
|
||||
"*.jsx": [
|
||||
"meteor npm run prettify",
|
||||
"meteor npm run lint:eslint:fix",
|
||||
"git add --force"
|
||||
],
|
||||
"*.json": [
|
||||
"prettier --write",
|
||||
"git add --force"
|
||||
]
|
||||
},
|
||||
"pre-commit": "lint:staged",
|
||||
"eslintConfig": {
|
||||
"extends": "@meteorjs/eslint-config-meteor"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wekan/wekan.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/wekan/wekan/issues"
|
||||
},
|
||||
"homepage": "https://wekan.github.io",
|
||||
"devDependencies": {
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-meteor": "0.0.9",
|
||||
"eslint-config-prettier": "^3.6.0",
|
||||
"eslint-import-resolver-meteor": "^0.4.0",
|
||||
"eslint-plugin-import": "^2.18.0",
|
||||
"eslint-plugin-meteor": "^5.1.0",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"lint-staged": "^7.3.0",
|
||||
"pre-commit": "^1.2.2",
|
||||
"prettier": "^1.18.2",
|
||||
"prettier-eslint": "^8.8.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.6.2",
|
||||
"ajv": "^5.0.0",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"bcrypt": "^3.0.2",
|
||||
"bson": "^4.0.0",
|
||||
"bunyan": "^1.8.12",
|
||||
"es6-promise": "^4.2.4",
|
||||
"gridfs-stream": "^0.5.3",
|
||||
"ldapjs": "^1.0.2",
|
||||
"meteor-node-stubs": "^0.4.1",
|
||||
"mongodb": "^3.3.3",
|
||||
"os": "^0.1.1",
|
||||
"page": "^1.8.6",
|
||||
"qs": "^6.8.0",
|
||||
"source-map-support": "^0.5.12",
|
||||
"xss": "^1.0.6"
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
name: wekan
|
||||
version: '0'
|
||||
version-script: git describe --dirty --tags | cut -c 2-
|
||||
version: 0
|
||||
version-script: git describe --tags | cut -c 2-
|
||||
summary: The open-source kanban
|
||||
description: |
|
||||
Wekan is an open-source and collaborative kanban board application.
|
||||
|
@ -65,75 +65,59 @@ apps:
|
|||
|
||||
parts:
|
||||
mongodb:
|
||||
source: https://repo.mongodb.org/apt/ubuntu/dists/xenial/mongodb-org/4.0/multiverse/binary-amd64/mongodb-org-server_4.0.11_amd64.deb
|
||||
source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.22.tgz
|
||||
plugin: dump
|
||||
stage-packages: [libssl-dev]
|
||||
stage-packages: [libssl1.0.0]
|
||||
filesets:
|
||||
mongo:
|
||||
- usr
|
||||
- bin
|
||||
- lib
|
||||
# stage:
|
||||
# - $mongo
|
||||
# prime:
|
||||
# - $mongo
|
||||
stage:
|
||||
- $mongo
|
||||
prime:
|
||||
- $mongo
|
||||
|
||||
wekan:
|
||||
source: .
|
||||
plugin: nodejs
|
||||
node-engine: 8.16.0
|
||||
# node-packages:
|
||||
# - node-gyp
|
||||
# - node-pre-gyp
|
||||
# - fibers@4.0.1
|
||||
node-engine: 8.17.0
|
||||
node-packages:
|
||||
- node-gyp
|
||||
- node-pre-gyp
|
||||
- fibers@2.0.0
|
||||
build-packages:
|
||||
- ca-certificates
|
||||
- apt-utils
|
||||
- bsdtar
|
||||
- gnupg
|
||||
- bzip2
|
||||
## - python3
|
||||
- python
|
||||
# - python3
|
||||
- g++
|
||||
- build-essential
|
||||
- git
|
||||
# - capnproto
|
||||
- capnproto
|
||||
- curl
|
||||
- libcurl3
|
||||
- php-curl
|
||||
# - execstack
|
||||
# - nodejs
|
||||
# - npm
|
||||
## - python3-pip
|
||||
## - python3-venv
|
||||
- execstack
|
||||
- nodejs
|
||||
- npm
|
||||
stage-packages:
|
||||
- libfontconfig1
|
||||
override-build: |
|
||||
set -o xtrace
|
||||
echo "Cleaning environment first"
|
||||
rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules
|
||||
# Meteor installer doesn't work with the default tar binary, so using bsdtar while installing.
|
||||
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
|
||||
cp $(which tar) $(which tar)~
|
||||
ln -sf $(which bsdtar) $(which tar)
|
||||
# Create the OpenAPI specification
|
||||
rm -rf .build
|
||||
mkdir .build
|
||||
##mkdir -p .build/python
|
||||
##cd .build/python
|
||||
##python3 -m venv env
|
||||
##. ./env/bin/activate
|
||||
##pip3 install -U setuptools wheel
|
||||
##git clone --depth 1 -b master https://github.com/Kronuz/esprima-python
|
||||
##cd esprima-python
|
||||
##python3 setup.py install
|
||||
##cd ../../..
|
||||
##mkdir -p ./public/api
|
||||
##python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml
|
||||
#mkdir -p .build/python
|
||||
#cd .build/python
|
||||
#git clone --depth 1 -b master https://github.com/Kronuz/esprima-python
|
||||
#cd esprima-python
|
||||
#python3 setup.py install
|
||||
#cd ../../..
|
||||
#mkdir -p ./public/api
|
||||
#python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml
|
||||
# we temporary need api2html and mkdirp
|
||||
##npm install -g --unsafe-perm api2html@0.3.0
|
||||
##npm install -g --unsafe-perm mkdirp
|
||||
##api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml
|
||||
##npm uninstall -g --unsafe-perm mkdirp
|
||||
##npm uninstall -g --unsafe-perm api2html
|
||||
#npm install -g api2html@0.3.0
|
||||
#npm install -g mkdirp
|
||||
#api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml
|
||||
#npm uninstall -g mkdirp
|
||||
#npm uninstall -g api2html
|
||||
# Node Fibers 100% CPU usage issue:
|
||||
# https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-381453161
|
||||
# https://github.com/meteor/meteor/issues/9796#issuecomment-381676326
|
||||
|
@ -206,17 +190,12 @@ parts:
|
|||
# git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git
|
||||
# cd ..
|
||||
#fi
|
||||
rm -rf package-lock.json .build
|
||||
#meteor add standard-minifier-js --allow-superuser
|
||||
meteor npm install --allow-superuser --unsafe-perm
|
||||
rm -rf .build
|
||||
meteor add standard-minifier-js --allow-superuser
|
||||
meteor npm install --allow-superuser
|
||||
meteor npm install --allow-superuser --save babel-runtime
|
||||
meteor build .build --directory --allow-superuser
|
||||
cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
|
||||
# Disable code coverage on Snap, because it crashes Wekan starting.
|
||||
# https://github.com/wekan/wekan/issues/2533#issuecomment-513506939
|
||||
#sed -i 's|Profile(fileInfo.path, func).apply(global, args);|//Profile(fileInfo.path, func).apply(global, args);|g' .build/bundle/programs/server/boot.js
|
||||
# although it did not help, it said unable to find main function.
|
||||
# Other option to fix would be to drop indexes at start of snap:
|
||||
# https://stackoverflow.com/questions/24241742/drop-all-indexes-from-all-collections-in-a-mongodb-database-using-the-command-li
|
||||
#Removed binary version of bcrypt because of security vulnerability that is not fixed yet.
|
||||
#https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac
|
||||
#https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c
|
||||
|
@ -227,18 +206,21 @@ parts:
|
|||
#cd ../../../../
|
||||
# Change to directory .build/bundle/programs/server
|
||||
cd .build/bundle/programs/server
|
||||
npm install --unsafe-perm
|
||||
npm install
|
||||
npm install --allow-superuser --save babel-runtime
|
||||
#meteor npm install --save bcrypt
|
||||
# Change back to Wekan source directory
|
||||
cd ../../../..
|
||||
cp -r .build/bundle/* $SNAPCRAFT_PART_INSTALL/
|
||||
cp .build/bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
|
||||
# rm $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
|
||||
#rm $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
|
||||
# rm $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
# rm $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
# Put back the original tar
|
||||
mv $(which tar)~ $(which tar)
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/tar/lib/.mkdir.js.swp
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
# Meteor 1.8.x additional .swp remove
|
||||
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
|
||||
|
||||
organize:
|
||||
README: README.wekan
|
||||
prime:
|
853
.snap-meteor-1.8/wekanCreator.js
Normal file
853
.snap-meteor-1.8/wekanCreator.js
Normal file
|
@ -0,0 +1,853 @@
|
|||
const DateString = Match.Where(function(dateAsString) {
|
||||
check(dateAsString, String);
|
||||
return moment(dateAsString, moment.ISO_8601).isValid();
|
||||
});
|
||||
|
||||
export class WekanCreator {
|
||||
constructor(data) {
|
||||
// we log current date, to use the same timestamp for all our actions.
|
||||
// this helps to retrieve all elements performed by the same import.
|
||||
this._nowDate = new Date();
|
||||
// The object creation dates, indexed by Wekan id
|
||||
// (so we only parse actions once!)
|
||||
this.createdAt = {
|
||||
board: null,
|
||||
cards: {},
|
||||
lists: {},
|
||||
swimlanes: {},
|
||||
};
|
||||
// The object creator Wekan Id, indexed by the object Wekan id
|
||||
// (so we only parse actions once!)
|
||||
this.createdBy = {
|
||||
cards: {}, // only cards have a field for that
|
||||
};
|
||||
|
||||
// Map of labels Wekan ID => Wekan ID
|
||||
this.labels = {};
|
||||
// Map of swimlanes Wekan ID => Wekan ID
|
||||
this.swimlanes = {};
|
||||
// Map of lists Wekan ID => Wekan ID
|
||||
this.lists = {};
|
||||
// Map of cards Wekan ID => Wekan ID
|
||||
this.cards = {};
|
||||
// Map of comments Wekan ID => Wekan ID
|
||||
this.commentIds = {};
|
||||
// Map of attachments Wekan ID => Wekan ID
|
||||
this.attachmentIds = {};
|
||||
// Map of checklists Wekan ID => Wekan ID
|
||||
this.checklists = {};
|
||||
// Map of checklistItems Wekan ID => Wekan ID
|
||||
this.checklistItems = {};
|
||||
// The comments, indexed by Wekan card id (to map when importing cards)
|
||||
this.comments = {};
|
||||
// Map of rules Wekan ID => Wekan ID
|
||||
this.rules = {};
|
||||
// the members, indexed by Wekan member id => Wekan user ID
|
||||
this.members = data.membersMapping ? data.membersMapping : {};
|
||||
// Map of triggers Wekan ID => Wekan ID
|
||||
this.triggers = {};
|
||||
// Map of actions Wekan ID => Wekan ID
|
||||
this.actions = {};
|
||||
|
||||
// maps a wekanCardId to an array of wekanAttachments
|
||||
this.attachments = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* If dateString is provided,
|
||||
* return the Date it represents.
|
||||
* If not, will return the date when it was first called.
|
||||
* This is useful for us, as we want all import operations to
|
||||
* have the exact same date for easier later retrieval.
|
||||
*
|
||||
* @param {String} dateString a properly formatted Date
|
||||
*/
|
||||
_now(dateString) {
|
||||
if (dateString) {
|
||||
return new Date(dateString);
|
||||
}
|
||||
if (!this._nowDate) {
|
||||
this._nowDate = new Date();
|
||||
}
|
||||
return this._nowDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* if wekanUserId is provided and we have a mapping,
|
||||
* return it.
|
||||
* Otherwise return current logged user.
|
||||
* @param wekanUserId
|
||||
* @private
|
||||
*/
|
||||
_user(wekanUserId) {
|
||||
if (wekanUserId && this.members[wekanUserId]) {
|
||||
return this.members[wekanUserId];
|
||||
}
|
||||
return Meteor.userId();
|
||||
}
|
||||
|
||||
checkActivities(wekanActivities) {
|
||||
check(wekanActivities, [
|
||||
Match.ObjectIncluding({
|
||||
activityType: String,
|
||||
createdAt: DateString,
|
||||
}),
|
||||
]);
|
||||
// XXX we could perform more thorough checks based on action type
|
||||
}
|
||||
|
||||
checkBoard(wekanBoard) {
|
||||
check(
|
||||
wekanBoard,
|
||||
Match.ObjectIncluding({
|
||||
archived: Boolean,
|
||||
title: String,
|
||||
// XXX refine control by validating 'color' against a list of
|
||||
// allowed values (is it worth the maintenance?)
|
||||
color: String,
|
||||
permission: Match.Where(value => {
|
||||
return ['private', 'public'].indexOf(value) >= 0;
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
checkCards(wekanCards) {
|
||||
check(wekanCards, [
|
||||
Match.ObjectIncluding({
|
||||
archived: Boolean,
|
||||
dateLastActivity: DateString,
|
||||
labelIds: [String],
|
||||
title: String,
|
||||
sort: Number,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
checkLabels(wekanLabels) {
|
||||
check(wekanLabels, [
|
||||
Match.ObjectIncluding({
|
||||
// XXX refine control by validating 'color' against a list of allowed
|
||||
// values (is it worth the maintenance?)
|
||||
color: String,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
checkLists(wekanLists) {
|
||||
check(wekanLists, [
|
||||
Match.ObjectIncluding({
|
||||
archived: Boolean,
|
||||
title: String,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
checkSwimlanes(wekanSwimlanes) {
|
||||
check(wekanSwimlanes, [
|
||||
Match.ObjectIncluding({
|
||||
archived: Boolean,
|
||||
title: String,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
checkChecklists(wekanChecklists) {
|
||||
check(wekanChecklists, [
|
||||
Match.ObjectIncluding({
|
||||
cardId: String,
|
||||
title: String,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
checkChecklistItems(wekanChecklistItems) {
|
||||
check(wekanChecklistItems, [
|
||||
Match.ObjectIncluding({
|
||||
cardId: String,
|
||||
title: String,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
checkRules(wekanRules) {
|
||||
check(wekanRules, [
|
||||
Match.ObjectIncluding({
|
||||
triggerId: String,
|
||||
actionId: String,
|
||||
title: String,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
checkTriggers(wekanTriggers) {
|
||||
// XXX More check based on trigger type
|
||||
check(wekanTriggers, [
|
||||
Match.ObjectIncluding({
|
||||
activityType: String,
|
||||
desc: String,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
getMembersToMap(data) {
|
||||
// we will work on the list itself (an ordered array of objects) when a
|
||||
// mapping is done, we add a 'wekan' field to the object representing the
|
||||
// imported member
|
||||
const membersToMap = data.members;
|
||||
const users = data.users;
|
||||
// auto-map based on username
|
||||
membersToMap.forEach(importedMember => {
|
||||
importedMember.id = importedMember.userId;
|
||||
delete importedMember.userId;
|
||||
const user = users.filter(user => {
|
||||
return user._id === importedMember.id;
|
||||
})[0];
|
||||
if (user.profile && user.profile.fullname) {
|
||||
importedMember.fullName = user.profile.fullname;
|
||||
}
|
||||
importedMember.username = user.username;
|
||||
const wekanUser = Users.findOne({ username: importedMember.username });
|
||||
if (wekanUser) {
|
||||
importedMember.wekanId = wekanUser._id;
|
||||
}
|
||||
});
|
||||
return membersToMap;
|
||||
}
|
||||
|
||||
checkActions(wekanActions) {
|
||||
// XXX More check based on action type
|
||||
check(wekanActions, [
|
||||
Match.ObjectIncluding({
|
||||
actionType: String,
|
||||
desc: String,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
// You must call parseActions before calling this one.
|
||||
createBoardAndLabels(boardToImport) {
|
||||
const boardToCreate = {
|
||||
archived: boardToImport.archived,
|
||||
color: boardToImport.color,
|
||||
// very old boards won't have a creation activity so no creation date
|
||||
createdAt: this._now(boardToImport.createdAt),
|
||||
labels: [],
|
||||
members: [
|
||||
{
|
||||
userId: Meteor.userId(),
|
||||
wekanId: Meteor.userId(),
|
||||
isActive: true,
|
||||
isAdmin: true,
|
||||
isNoComments: false,
|
||||
isCommentOnly: false,
|
||||
swimlaneId: false,
|
||||
},
|
||||
],
|
||||
// Standalone Export has modifiedAt missing, adding modifiedAt to fix it
|
||||
modifiedAt: this._now(boardToImport.modifiedAt),
|
||||
permission: boardToImport.permission,
|
||||
slug: getSlug(boardToImport.title) || 'board',
|
||||
stars: 0,
|
||||
title: boardToImport.title,
|
||||
};
|
||||
// now add other members
|
||||
if (boardToImport.members) {
|
||||
boardToImport.members.forEach(wekanMember => {
|
||||
// do we already have it in our list?
|
||||
if (
|
||||
!boardToCreate.members.some(
|
||||
member => member.wekanId === wekanMember.wekanId,
|
||||
)
|
||||
)
|
||||
boardToCreate.members.push({
|
||||
...wekanMember,
|
||||
userId: wekanMember.wekanId,
|
||||
});
|
||||
});
|
||||
}
|
||||
boardToImport.labels.forEach(label => {
|
||||
const labelToCreate = {
|
||||
_id: Random.id(6),
|
||||
color: label.color,
|
||||
name: label.name,
|
||||
};
|
||||
// We need to remember them by Wekan ID, as this is the only ref we have
|
||||
// when importing cards.
|
||||
this.labels[label._id] = labelToCreate._id;
|
||||
boardToCreate.labels.push(labelToCreate);
|
||||
});
|
||||
const boardId = Boards.direct.insert(boardToCreate);
|
||||
Boards.direct.update(boardId, {
|
||||
$set: {
|
||||
modifiedAt: this._now(),
|
||||
},
|
||||
});
|
||||
// log activity
|
||||
Activities.direct.insert({
|
||||
activityType: 'importBoard',
|
||||
boardId,
|
||||
createdAt: this._now(),
|
||||
source: {
|
||||
id: boardToImport.id,
|
||||
system: 'Wekan',
|
||||
},
|
||||
// We attribute the import to current user,
|
||||
// not the author from the original object.
|
||||
userId: this._user(),
|
||||
});
|
||||
return boardId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Wekan cards corresponding to the supplied Wekan cards,
|
||||
* as well as all linked data: activities, comments, and attachments
|
||||
* @param wekanCards
|
||||
* @param boardId
|
||||
* @returns {Array}
|
||||
*/
|
||||
createCards(wekanCards, boardId) {
|
||||
const result = [];
|
||||
wekanCards.forEach(card => {
|
||||
const cardToCreate = {
|
||||
archived: card.archived,
|
||||
boardId,
|
||||
// very old boards won't have a creation activity so no creation date
|
||||
createdAt: this._now(this.createdAt.cards[card._id]),
|
||||
dateLastActivity: this._now(),
|
||||
description: card.description,
|
||||
listId: this.lists[card.listId],
|
||||
swimlaneId: this.swimlanes[card.swimlaneId],
|
||||
sort: card.sort,
|
||||
title: card.title,
|
||||
// we attribute the card to its creator if available
|
||||
userId: this._user(this.createdBy.cards[card._id]),
|
||||
isOvertime: card.isOvertime || false,
|
||||
startAt: card.startAt ? this._now(card.startAt) : null,
|
||||
dueAt: card.dueAt ? this._now(card.dueAt) : null,
|
||||
spentTime: card.spentTime || null,
|
||||
};
|
||||
// add labels
|
||||
if (card.labelIds) {
|
||||
cardToCreate.labelIds = card.labelIds.map(wekanId => {
|
||||
return this.labels[wekanId];
|
||||
});
|
||||
}
|
||||
// add members {
|
||||
if (card.members) {
|
||||
const wekanMembers = [];
|
||||
// we can't just map, as some members may not have been mapped
|
||||
card.members.forEach(sourceMemberId => {
|
||||
if (this.members[sourceMemberId]) {
|
||||
const wekanId = this.members[sourceMemberId];
|
||||
// we may map multiple Wekan members to the same wekan user
|
||||
// in which case we risk adding the same user multiple times
|
||||
if (!wekanMembers.find(wId => wId === wekanId)) {
|
||||
wekanMembers.push(wekanId);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (wekanMembers.length > 0) {
|
||||
cardToCreate.members = wekanMembers;
|
||||
}
|
||||
}
|
||||
// set color
|
||||
if (card.color) {
|
||||
cardToCreate.color = card.color;
|
||||
}
|
||||
// insert card
|
||||
const cardId = Cards.direct.insert(cardToCreate);
|
||||
// keep track of Wekan id => Wekan id
|
||||
this.cards[card._id] = cardId;
|
||||
// // log activity
|
||||
// Activities.direct.insert({
|
||||
// activityType: 'importCard',
|
||||
// boardId,
|
||||
// cardId,
|
||||
// createdAt: this._now(),
|
||||
// listId: cardToCreate.listId,
|
||||
// source: {
|
||||
// id: card._id,
|
||||
// system: 'Wekan',
|
||||
// },
|
||||
// // we attribute the import to current user,
|
||||
// // not the author of the original card
|
||||
// userId: this._user(),
|
||||
// });
|
||||
// add comments
|
||||
const comments = this.comments[card._id];
|
||||
if (comments) {
|
||||
comments.forEach(comment => {
|
||||
const commentToCreate = {
|
||||
boardId,
|
||||
cardId,
|
||||
createdAt: this._now(comment.createdAt),
|
||||
text: comment.text,
|
||||
// we attribute the comment to the original author, default to current user
|
||||
userId: this._user(comment.userId),
|
||||
};
|
||||
// dateLastActivity will be set from activity insert, no need to
|
||||
// update it ourselves
|
||||
const commentId = CardComments.direct.insert(commentToCreate);
|
||||
this.commentIds[comment._id] = commentId;
|
||||
// Activities.direct.insert({
|
||||
// activityType: 'addComment',
|
||||
// boardId: commentToCreate.boardId,
|
||||
// cardId: commentToCreate.cardId,
|
||||
// commentId,
|
||||
// createdAt: this._now(commentToCreate.createdAt),
|
||||
// // we attribute the addComment (not the import)
|
||||
// // to the original author - it is needed by some UI elements.
|
||||
// userId: commentToCreate.userId,
|
||||
// });
|
||||
});
|
||||
}
|
||||
const attachments = this.attachments[card._id];
|
||||
const wekanCoverId = card.coverId;
|
||||
if (attachments) {
|
||||
attachments.forEach(att => {
|
||||
const file = new FS.File();
|
||||
// Simulating file.attachData on the client generates multiple errors
|
||||
// - HEAD returns null, which causes exception down the line
|
||||
// - the template then tries to display the url to the attachment which causes other errors
|
||||
// so we make it server only, and let UI catch up once it is done, forget about latency comp.
|
||||
const self = this;
|
||||
if (Meteor.isServer) {
|
||||
if (att.url) {
|
||||
file.attachData(att.url, function(error) {
|
||||
file.boardId = boardId;
|
||||
file.cardId = cardId;
|
||||
file.userId = self._user(att.userId);
|
||||
// The field source will only be used to prevent adding
|
||||
// attachments' related activities automatically
|
||||
file.source = 'import';
|
||||
if (error) {
|
||||
throw error;
|
||||
} else {
|
||||
const wekanAtt = Attachments.insert(file, () => {
|
||||
// we do nothing
|
||||
});
|
||||
self.attachmentIds[att._id] = wekanAtt._id;
|
||||
//
|
||||
if (wekanCoverId === att._id) {
|
||||
Cards.direct.update(cardId, {
|
||||
$set: {
|
||||
coverId: wekanAtt._id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (att.file) {
|
||||
file.attachData(
|
||||
new Buffer(att.file, 'base64'),
|
||||
{
|
||||
type: att.type,
|
||||
},
|
||||
error => {
|
||||
file.name(att.name);
|
||||
file.boardId = boardId;
|
||||
file.cardId = cardId;
|
||||
file.userId = self._user(att.userId);
|
||||
// The field source will only be used to prevent adding
|
||||
// attachments' related activities automatically
|
||||
file.source = 'import';
|
||||
if (error) {
|
||||
throw error;
|
||||
} else {
|
||||
const wekanAtt = Attachments.insert(file, () => {
|
||||
// we do nothing
|
||||
});
|
||||
this.attachmentIds[att._id] = wekanAtt._id;
|
||||
//
|
||||
if (wekanCoverId === att._id) {
|
||||
Cards.direct.update(cardId, {
|
||||
$set: {
|
||||
coverId: wekanAtt._id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
// todo XXX set cover - if need be
|
||||
});
|
||||
}
|
||||
result.push(cardId);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create labels if they do not exist and load this.labels.
|
||||
createLabels(wekanLabels, board) {
|
||||
wekanLabels.forEach(label => {
|
||||
const color = label.color;
|
||||
const name = label.name;
|
||||
const existingLabel = board.getLabel(name, color);
|
||||
if (existingLabel) {
|
||||
this.labels[label.id] = existingLabel._id;
|
||||
} else {
|
||||
const idLabelCreated = board.pushLabel(name, color);
|
||||
this.labels[label.id] = idLabelCreated;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createLists(wekanLists, boardId) {
|
||||
wekanLists.forEach((list, listIndex) => {
|
||||
const listToCreate = {
|
||||
archived: list.archived,
|
||||
boardId,
|
||||
// We are being defensing here by providing a default date (now) if the
|
||||
// creation date wasn't found on the action log. This happen on old
|
||||
// Wekan boards (eg from 2013) that didn't log the 'createList' action
|
||||
// we require.
|
||||
createdAt: this._now(this.createdAt.lists[list.id]),
|
||||
title: list.title,
|
||||
sort: list.sort ? list.sort : listIndex,
|
||||
};
|
||||
const listId = Lists.direct.insert(listToCreate);
|
||||
Lists.direct.update(listId, {
|
||||
$set: {
|
||||
updatedAt: this._now(),
|
||||
},
|
||||
});
|
||||
this.lists[list._id] = listId;
|
||||
// // log activity
|
||||
// Activities.direct.insert({
|
||||
// activityType: 'importList',
|
||||
// boardId,
|
||||
// createdAt: this._now(),
|
||||
// listId,
|
||||
// source: {
|
||||
// id: list._id,
|
||||
// system: 'Wekan',
|
||||
// },
|
||||
// // We attribute the import to current user,
|
||||
// // not the creator of the original object
|
||||
// userId: this._user(),
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
createSwimlanes(wekanSwimlanes, boardId) {
|
||||
wekanSwimlanes.forEach((swimlane, swimlaneIndex) => {
|
||||
const swimlaneToCreate = {
|
||||
archived: swimlane.archived,
|
||||
boardId,
|
||||
// We are being defensing here by providing a default date (now) if the
|
||||
// creation date wasn't found on the action log. This happen on old
|
||||
// Wekan boards (eg from 2013) that didn't log the 'createList' action
|
||||
// we require.
|
||||
createdAt: this._now(this.createdAt.swimlanes[swimlane._id]),
|
||||
title: swimlane.title,
|
||||
sort: swimlane.sort ? swimlane.sort : swimlaneIndex,
|
||||
};
|
||||
// set color
|
||||
if (swimlane.color) {
|
||||
swimlaneToCreate.color = swimlane.color;
|
||||
}
|
||||
const swimlaneId = Swimlanes.direct.insert(swimlaneToCreate);
|
||||
Swimlanes.direct.update(swimlaneId, {
|
||||
$set: {
|
||||
updatedAt: this._now(),
|
||||
},
|
||||
});
|
||||
this.swimlanes[swimlane._id] = swimlaneId;
|
||||
});
|
||||
}
|
||||
|
||||
createChecklists(wekanChecklists) {
|
||||
const result = [];
|
||||
wekanChecklists.forEach((checklist, checklistIndex) => {
|
||||
// Create the checklist
|
||||
const checklistToCreate = {
|
||||
cardId: this.cards[checklist.cardId],
|
||||
title: checklist.title,
|
||||
createdAt: checklist.createdAt,
|
||||
sort: checklist.sort ? checklist.sort : checklistIndex,
|
||||
};
|
||||
const checklistId = Checklists.direct.insert(checklistToCreate);
|
||||
this.checklists[checklist._id] = checklistId;
|
||||
result.push(checklistId);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
createTriggers(wekanTriggers, boardId) {
|
||||
wekanTriggers.forEach(trigger => {
|
||||
if (trigger.hasOwnProperty('labelId')) {
|
||||
trigger.labelId = this.labels[trigger.labelId];
|
||||
}
|
||||
if (trigger.hasOwnProperty('memberId')) {
|
||||
trigger.memberId = this.members[trigger.memberId];
|
||||
}
|
||||
trigger.boardId = boardId;
|
||||
const oldId = trigger._id;
|
||||
delete trigger._id;
|
||||
this.triggers[oldId] = Triggers.direct.insert(trigger);
|
||||
});
|
||||
}
|
||||
|
||||
createActions(wekanActions, boardId) {
|
||||
wekanActions.forEach(action => {
|
||||
if (action.hasOwnProperty('labelId')) {
|
||||
action.labelId = this.labels[action.labelId];
|
||||
}
|
||||
if (action.hasOwnProperty('memberId')) {
|
||||
action.memberId = this.members[action.memberId];
|
||||
}
|
||||
action.boardId = boardId;
|
||||
const oldId = action._id;
|
||||
delete action._id;
|
||||
this.actions[oldId] = Actions.direct.insert(action);
|
||||
});
|
||||
}
|
||||
|
||||
createRules(wekanRules, boardId) {
|
||||
wekanRules.forEach(rule => {
|
||||
// Create the rule
|
||||
rule.boardId = boardId;
|
||||
rule.triggerId = this.triggers[rule.triggerId];
|
||||
rule.actionId = this.actions[rule.actionId];
|
||||
delete rule._id;
|
||||
Rules.direct.insert(rule);
|
||||
});
|
||||
}
|
||||
|
||||
createChecklistItems(wekanChecklistItems) {
|
||||
wekanChecklistItems.forEach((checklistitem, checklistitemIndex) => {
|
||||
// Create the checklistItem
|
||||
const checklistItemTocreate = {
|
||||
title: checklistitem.title,
|
||||
checklistId: this.checklists[checklistitem.checklistId],
|
||||
cardId: this.cards[checklistitem.cardId],
|
||||
sort: checklistitem.sort ? checklistitem.sort : checklistitemIndex,
|
||||
isFinished: checklistitem.isFinished,
|
||||
};
|
||||
const checklistItemId = ChecklistItems.direct.insert(
|
||||
checklistItemTocreate,
|
||||
);
|
||||
this.checklistItems[checklistitem._id] = checklistItemId;
|
||||
});
|
||||
}
|
||||
|
||||
parseActivities(wekanBoard) {
|
||||
wekanBoard.activities.forEach(activity => {
|
||||
switch (activity.activityType) {
|
||||
case 'addAttachment': {
|
||||
// We have to be cautious, because the attachment could have been removed later.
|
||||
// In that case Wekan still reports its addition, but removes its 'url' field.
|
||||
// So we test for that
|
||||
const wekanAttachment = wekanBoard.attachments.filter(attachment => {
|
||||
return attachment._id === activity.attachmentId;
|
||||
})[0];
|
||||
|
||||
if (typeof wekanAttachment !== 'undefined' && wekanAttachment) {
|
||||
if (wekanAttachment.url || wekanAttachment.file) {
|
||||
// we cannot actually create the Wekan attachment, because we don't yet
|
||||
// have the cards to attach it to, so we store it in the instance variable.
|
||||
const wekanCardId = activity.cardId;
|
||||
if (!this.attachments[wekanCardId]) {
|
||||
this.attachments[wekanCardId] = [];
|
||||
}
|
||||
this.attachments[wekanCardId].push(wekanAttachment);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'addComment': {
|
||||
const wekanComment = wekanBoard.comments.filter(comment => {
|
||||
return comment._id === activity.commentId;
|
||||
})[0];
|
||||
const id = activity.cardId;
|
||||
if (!this.comments[id]) {
|
||||
this.comments[id] = [];
|
||||
}
|
||||
this.comments[id].push(wekanComment);
|
||||
break;
|
||||
}
|
||||
case 'createBoard': {
|
||||
this.createdAt.board = activity.createdAt;
|
||||
break;
|
||||
}
|
||||
case 'createCard': {
|
||||
const cardId = activity.cardId;
|
||||
this.createdAt.cards[cardId] = activity.createdAt;
|
||||
this.createdBy.cards[cardId] = activity.userId;
|
||||
break;
|
||||
}
|
||||
case 'createList': {
|
||||
const listId = activity.listId;
|
||||
this.createdAt.lists[listId] = activity.createdAt;
|
||||
break;
|
||||
}
|
||||
case 'createSwimlane': {
|
||||
const swimlaneId = activity.swimlaneId;
|
||||
this.createdAt.swimlanes[swimlaneId] = activity.createdAt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
importActivities(activities, boardId) {
|
||||
activities.forEach(activity => {
|
||||
switch (activity.activityType) {
|
||||
// Board related activities
|
||||
// TODO: addBoardMember, removeBoardMember
|
||||
case 'createBoard': {
|
||||
Activities.direct.insert({
|
||||
userId: this._user(activity.userId),
|
||||
type: 'board',
|
||||
activityTypeId: boardId,
|
||||
activityType: activity.activityType,
|
||||
boardId,
|
||||
createdAt: this._now(activity.createdAt),
|
||||
});
|
||||
break;
|
||||
}
|
||||
// List related activities
|
||||
// TODO: removeList, archivedList
|
||||
case 'createList': {
|
||||
Activities.direct.insert({
|
||||
userId: this._user(activity.userId),
|
||||
type: 'list',
|
||||
activityType: activity.activityType,
|
||||
listId: this.lists[activity.listId],
|
||||
boardId,
|
||||
createdAt: this._now(activity.createdAt),
|
||||
});
|
||||
break;
|
||||
}
|
||||
// Card related activities
|
||||
// TODO: archivedCard, restoredCard, joinMember, unjoinMember
|
||||
case 'createCard': {
|
||||
Activities.direct.insert({
|
||||
userId: this._user(activity.userId),
|
||||
activityType: activity.activityType,
|
||||
listId: this.lists[activity.listId],
|
||||
cardId: this.cards[activity.cardId],
|
||||
boardId,
|
||||
createdAt: this._now(activity.createdAt),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'moveCard': {
|
||||
Activities.direct.insert({
|
||||
userId: this._user(activity.userId),
|
||||
oldListId: this.lists[activity.oldListId],
|
||||
activityType: activity.activityType,
|
||||
listId: this.lists[activity.listId],
|
||||
cardId: this.cards[activity.cardId],
|
||||
boardId,
|
||||
createdAt: this._now(activity.createdAt),
|
||||
});
|
||||
break;
|
||||
}
|
||||
// Comment related activities
|
||||
case 'addComment': {
|
||||
Activities.direct.insert({
|
||||
userId: this._user(activity.userId),
|
||||
activityType: activity.activityType,
|
||||
cardId: this.cards[activity.cardId],
|
||||
commentId: this.commentIds[activity.commentId],
|
||||
boardId,
|
||||
createdAt: this._now(activity.createdAt),
|
||||
});
|
||||
break;
|
||||
}
|
||||
// Attachment related activities
|
||||
case 'addAttachment': {
|
||||
Activities.direct.insert({
|
||||
userId: this._user(activity.userId),
|
||||
type: 'card',
|
||||
activityType: activity.activityType,
|
||||
attachmentId: this.attachmentIds[activity.attachmentId],
|
||||
cardId: this.cards[activity.cardId],
|
||||
boardId,
|
||||
createdAt: this._now(activity.createdAt),
|
||||
});
|
||||
break;
|
||||
}
|
||||
// Checklist related activities
|
||||
case 'addChecklist': {
|
||||
Activities.direct.insert({
|
||||
userId: this._user(activity.userId),
|
||||
activityType: activity.activityType,
|
||||
cardId: this.cards[activity.cardId],
|
||||
checklistId: this.checklists[activity.checklistId],
|
||||
boardId,
|
||||
createdAt: this._now(activity.createdAt),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'addChecklistItem': {
|
||||
Activities.direct.insert({
|
||||
userId: this._user(activity.userId),
|
||||
activityType: activity.activityType,
|
||||
cardId: this.cards[activity.cardId],
|
||||
checklistId: this.checklists[activity.checklistId],
|
||||
checklistItemId: activity.checklistItemId.replace(
|
||||
activity.checklistId,
|
||||
this.checklists[activity.checklistId],
|
||||
),
|
||||
boardId,
|
||||
createdAt: this._now(activity.createdAt),
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//check(board) {
|
||||
check() {
|
||||
//try {
|
||||
// check(data, {
|
||||
// membersMapping: Match.Optional(Object),
|
||||
// });
|
||||
// this.checkActivities(board.activities);
|
||||
// this.checkBoard(board);
|
||||
// this.checkLabels(board.labels);
|
||||
// this.checkLists(board.lists);
|
||||
// this.checkSwimlanes(board.swimlanes);
|
||||
// this.checkCards(board.cards);
|
||||
//this.checkChecklists(board.checklists);
|
||||
// this.checkRules(board.rules);
|
||||
// this.checkActions(board.actions);
|
||||
//this.checkTriggers(board.triggers);
|
||||
//this.checkChecklistItems(board.checklistItems);
|
||||
//} catch (e) {
|
||||
// throw new Meteor.Error('error-json-schema');
|
||||
// }
|
||||
}
|
||||
|
||||
create(board, currentBoardId) {
|
||||
// TODO : Make isSandstorm variable global
|
||||
const isSandstorm =
|
||||
Meteor.settings &&
|
||||
Meteor.settings.public &&
|
||||
Meteor.settings.public.sandstorm;
|
||||
if (isSandstorm && currentBoardId) {
|
||||
const currentBoard = Boards.findOne(currentBoardId);
|
||||
currentBoard.archive();
|
||||
}
|
||||
this.parseActivities(board);
|
||||
const boardId = this.createBoardAndLabels(board);
|
||||
this.createLists(board.lists, boardId);
|
||||
this.createSwimlanes(board.swimlanes, boardId);
|
||||
this.createCards(board.cards, boardId);
|
||||
this.createChecklists(board.checklists);
|
||||
this.createChecklistItems(board.checklistItems);
|
||||
this.importActivities(board.activities, boardId);
|
||||
this.createTriggers(board.triggers, boardId);
|
||||
this.createActions(board.actions, boardId);
|
||||
this.createRules(board.rules, boardId);
|
||||
// XXX add members
|
||||
return boardId;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
dist: disco
|
||||
dist: eoan
|
||||
sudo: required
|
||||
|
||||
env:
|
||||
TRAVIS_DOCKER_COMPOSE_VERSION: 1.24.0
|
||||
TRAVIS_NODE_VERSION: 8.16.1
|
||||
TRAVIS_NPM_VERSION: 6.4.1
|
||||
TRAVIS_NODE_VERSION: 12.15.0
|
||||
TRAVIS_NPM_VERSION: latest
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update -y
|
||||
|
|
449
CHANGELOG.md
449
CHANGELOG.md
|
@ -1,4 +1,416 @@
|
|||
# Upcoming Wekan release
|
||||
# v3.78 2020-02-12 Wekan release
|
||||
|
||||
This release adds the following features:
|
||||
|
||||
- [Card Settings / Show on Card: Description Title and Description Text](https://github.com/wekan/wekan/commit/e89965f6422fd95b4ad2112ae407b1dde4853510).
|
||||
Thanks to e-stoniauk, 2020product and xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Remove card element grouping to create compact card layout](https://github.com/wekan/wekan/commit/e89965f6422fd95b4ad2112ae407b1dde4853510).
|
||||
Thanks to e-stoniauk, 2020product and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.77 2020-02-10 Wekan release
|
||||
|
||||
This release removes the following features:
|
||||
|
||||
- [Remove hiding comments and activities](https://github.com/wekan/wekan/commit/2a54218f3f68547032bd53a04a968b233be21e15).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- Fix Copy Card Link to Clipboard button at card title did not
|
||||
work [Part 1](https://github.com/wekan/wekan/commit/9a21b0a1c933e7f778e4e57a8258e150ccea1620)
|
||||
and [Part2](https://github.com/wekan/wekan/commit/4467a68b97a3fbf0fbae7f05177d978f2aa80287).
|
||||
Thanks to 2020product and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.76 2020-02-07 Wekan release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- [Use Meteor 1.9 and Node.js 12.15.0 on Snap and Docker](https://github.com/wekan/wekan/commit/8384d68a060ef8f2c202ce2fa6064c5c823d28dc).
|
||||
This also fixes bug that exporting some boards was not possible, downloading export file failed.
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix Bug enable/disable Comments in Card Settings](https://github.com/wekan/wekan/issues/2923).
|
||||
Thanks to warnt, mdurokov and xet7.
|
||||
- [Try to disable dragging Swimlanes/Lists/Cards/Checklists/Subtasks on small mobile smartphones webbrowsers,
|
||||
and hide drag handles on mobile web](https://github.com/wekan/wekan/commit/bf78b093bad7d463ee325ad96e8b485264d4a3be).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.75 2020-02-05 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Fix](https://github.com/wekan/wekan/commit/f22785dbcde42e425c9ead209ec224aef6e11c16)
|
||||
[adding comments](https://github.com/wekan/wekan/issues/2918).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Added some working layout changes like activities using less space from https://github.com/wekan/wekan/pull/2920](https://github.com/wekan/wekan/commit/f22785dbcde42e425c9ead209ec224aef6e11c16).
|
||||
Thanks to 2020product.
|
||||
- [Fixed Card Settings not working at Sandstorm](https://github.com/wekan/wekan/commit/f22785dbcde42e425c9ead209ec224aef6e11c16).
|
||||
Thanks to xet7.
|
||||
- Add [Card Description title](https://github.com/wekan/wekan/issues/2918#issuecomment-582346577)
|
||||
[back](https://github.com/wekan/wekan/commit/f22785dbcde42e425c9ead209ec224aef6e11c16).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.74 2020-02-05 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [For BoardAdmin, add way to hide parts of a card, at Board Settings/Card Settings/Show on Card: Received, Start, ... etc.
|
||||
Add to card title bar Copy card to Clipboard button](https://github.com/wekan/wekan/pull/2915).
|
||||
Thanks to 2020product and xet7.
|
||||
- [Set default to RICHER_CARD_COMMENT_EDITOR=false](https://github.com/wekan/wekan/commit/65fa2f626f503b8089e0d982901cffb3990426cb).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.73 2020-01-29 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Login to Wekan with Nextcloud](https://github.com/wekan/wekan/pull/2897).
|
||||
Thanks to bogie.
|
||||
- [Add rule action to move cards to other boards](https://github.com/wekan/wekan/pull/2899).
|
||||
Thanks to peterverraedt.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Show System Wide Announcement in one line](https://github.com/wekan/wekan/pull/2891).
|
||||
Thanks to tsia.
|
||||
- [Fixed board export with attachment in Wekan Meteor 1.9.x version](https://github.com/wekan/wekan/pull/2898).
|
||||
Thanks to izadpoor.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.72 2020-01-19 Sandstorm-only Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- Try to fix Wekan at Sandstorm.
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.71 2020-01-18 Sandstorm-only Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Try to fix Wekan at Sandstorm by using Meteor 1.8.x and Node 8.17.0 at Sandstorm](https://github.com/wekan/wekan/commit/5e5ab95410c715a4379631456fc5547c497898b0).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.70 2020-01-18 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Add missing LD_LIBRARY_PATH to use libssl and libcurl](https://github.com/wekan/wekan/10f142a1a05acb98a175ccb0326fb0c1d3e3713f).
|
||||
Thanks to xet7.
|
||||
- [Use Meteor 1.8.x](https://github.com/wekan/wekan/commit/55a2aa90cbbf44200e9b0b9f4bd08b6177f1bb95)
|
||||
[on Snap](https://github.com/wekan/wekan/commit/6a01170d8696322462c4065ce0cf4a637a058975), because
|
||||
Snap builds do not work yet for Meteor 1.9, Node 12.14.1 and MongoDB 4.2.2.
|
||||
Docker version works with Meteor 1.9.
|
||||
Thanks to xet7.
|
||||
- [Try to fix Node 12 Buffer() deprecation errors](https://github.com/wekan/wekan/commit/9b905c2833d54cf34d1875148075b2bf756d943a).
|
||||
Thanks to xet7.
|
||||
- [Add Snap Meteor 1.8.x files to lint ignore files](https://github.com/wekan/wekan/commit/48f8050c25e40f737dfdd3a98923cb87cd4e77e2).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.69 2020-01-10 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fix docker-compose.yml to not use --smallfiles that is not supported in
|
||||
MongoDB 4.x](https://github.com/wekan/wekan/commit/ecb76842fcbd81701afcab8db0ed106e6be0fdec).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.68 2020-01-10 Wekan release
|
||||
|
||||
This release tries to fix the following bugs:
|
||||
|
||||
- [Try to fix Snap by removing MongoDB option --smallfiles that is not supported
|
||||
in MongoDB 4.x](https://github.com/wekan/wekan/commit/031df54a2e0a03dcb7a2586667e60e5bd4eef706)
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.67 2020-01-10 Wekan release
|
||||
|
||||
This release tries to fix the following bugs:
|
||||
|
||||
- [Try to fix Snap](https://github.com/wekan/wekan/commit/2b382b940be9af575fab4c2e955702d8cde55ae9).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.66 2020-01-10 Wekan release
|
||||
|
||||
This release tries to fix the following bugs:
|
||||
|
||||
- [Try to fix Snap](https://github.com/wekan/wekan/commit/39bf1e375e2962f824e6f8cfa425ea51aa4efa24).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.65 2020-01-10 Wekan release
|
||||
|
||||
This release adds the following features:
|
||||
|
||||
- [More keyboard shortcuts: c for archive card](https://github.com/wekan/wekan/commit/d16a601c04aeb1d3550c5c541be02a67276a34cf).
|
||||
Thanks to xet7.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Upgrade to Meteor 1.9, Node 12.14.1 and MongoDB 4.2.2](https://github.com/wekan/wekan/commit/785f3cf88b61f687ef5ad4a529768221d1a54c86).
|
||||
Thanks to xet7.
|
||||
- [Add more issue repo links to GitHub issue template](https://github.com/wekan/wekan/commit/5724674e73246f4e52843a6d6906c0ecdd85cccc).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.64 2020-01-06 Wekan release
|
||||
|
||||
This release adds the following warning for CentOS 7 users:
|
||||
|
||||
- [WARNING: DO NOT USE SNAP ON CENTOS 7, THERE IS UPDATE BUG](https://github.com/wekan/wekan-snap/wiki/CentOS-7).
|
||||
Thanks to andy-twosticks and xet7.
|
||||
|
||||
and adds the following features:
|
||||
|
||||
- [Wider sidebar](https://github.com/wekan/wekan/commit/5058233509e44916296e38fb8a6c5dd591c46d8b).
|
||||
Thanks to vjrj.
|
||||
|
||||
and removes the following features:
|
||||
|
||||
- [Removed Custom HTML feature that does not work](https://github.com/wekan/wekan/commit/ddce0ada094e6450be260b4cda21fdfa09ae0133).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.63 2020-01-06 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fix: Unable to find Archive Card/List/Swimlane in board
|
||||
settings](https://github.com/wekan/wekan/commit/8ce993921718f3e10c2daa5fabb145b939d789dd).
|
||||
Thanks to neobradley and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.62 2020-01-05 Wekan release
|
||||
|
||||
This release adds the following features:
|
||||
|
||||
- [Add Worker role](https://github.com/wekan/wekan/issues/2788).
|
||||
This was originally added at Wekan v3.58, reverted at Wekan v3.60 because of bugs,
|
||||
and now after fixes added back.
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.61 2020-01-03 Wekan release
|
||||
|
||||
This release adds the following features:
|
||||
|
||||
- [Add more Font Awesome icons. This was originally added
|
||||
at Wekan v3.58, removed at Wekan v3.60, and now
|
||||
added back at Wekan v3.61](https://github.com/wekan/wekan/commit/cd253522a305523e3e36bb73313e8c4db500a717).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix browser javascript console errors when editing profile. This was originally added
|
||||
at Wekan v3.58, removed at Wekan v3.60, and now added back at
|
||||
Wekan v3.61](https://github.com/wekan/wekan/commit/cd253522a305523e3e36bb73313e8c4db500a717).
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.60 2020-01-03 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Revert to Wekan v3.57 version of client and models directories,
|
||||
removing Worker role temporarily, because Worker role changes
|
||||
broke saving card](https://github.com/wekan/wekan/commit/27943796ade78ca3c503637a1340918bf06a1267).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.59 2020-01-03 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Fix not being able to edit received date](https://github.com/wekan/wekan/commit/5376bc7b7905c0dd99fae1aeae3f63b4583a3e3f).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.58 2020-01-03 Wekan release
|
||||
|
||||
This release adds the following features:
|
||||
|
||||
- [Add Worker role](https://github.com/wekan/wekan/issues/2788). Thanks to xet7.
|
||||
- [Add more Font Awesome icons](https://github.com/wekan/wekan/commit/2bf004120d5a43cd3c3c060fc7c0c30d1b01f220).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix: k8s templates update for helm](https://github.com/wekan/wekan/pull/2867).
|
||||
1. Upgrade mongo replica version.
|
||||
2. Access mongo via service url.
|
||||
3. Change the expose servicePort to numeric.
|
||||
Thanks to jiangytcn.
|
||||
- [Fix browser console errors when editing user profile name](https://github.com/wekan/wekan/commit/2bf004120d5a43cd3c3c060fc7c0c30d1b01f220).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.57 2019-12-22 Wekan release
|
||||
|
||||
This release adds the following features:
|
||||
|
||||
- [Allow card and checklist API creation for authorized board members](https://github.com/wekan/wekan/pull/2854).
|
||||
Thanks to Robert-Lebedeu.
|
||||
- [Visual difference for inactive user in Administration: strikethrough](https://github.com/wekan/wekan/commit/1f1aea87a421ca5e7931d220d10c838574208e2c).
|
||||
Thanks to hever and xet7.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Upgrade to Meteor 1.8.3 and Node 8.17.0. Update release scripts. Fix ldap background sync documentation part 2](https://github.com/wekan/wekan/commit/782d0b620988628f40f50f9cd824f6652cfb0dd9).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix: Don't add a blank space for empty custom fields on minicards](https://github.com/wekan/wekan/commit/e2a374f0aad8489a84d6de9966c281a812b5eca3).
|
||||
Thanks to roobre and xet7.
|
||||
- [Fix: Allow to set empty card title, AssignedBy and RequestedBy](https://github.com/wekan/wekan/commit/25561946edf37351f67cf7500902dde7d9114d2f).
|
||||
Thanks to justinr1234 and xet7.
|
||||
- [Fix comment text disappearing when clicking outside of comment text area.
|
||||
Fix lint error](https://github.com/wekan/wekan/commit/3b3950369ce07aa9e6fc4ab1bef9fb8a4993e398).
|
||||
Thanks to xet7.
|
||||
- [Fix ldap background sync documentation](https://github.com/wekan/wekan/pull/2855).
|
||||
Thanks to koelle25.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.56 2019-11-21 Wekan release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- [Update to Meteor 1.8.2. Update dependencies](https://github.com/wekan/wekan/commit/38dfe0b9a71a083adc2de1a81170fea0e4a8e53f).
|
||||
Thanks to xet7.
|
||||
- [Fix lint errors and update travis NPM version](https://github.com/wekan/wekan/commit/b0f345ba21830b033c9edcc8ee5252b280111ae7).
|
||||
Thanks to xet7.
|
||||
- [Change base image to rolling, that is currently Ubuntu eoan
|
||||
version](https://github.com/wekan/wekan/commit/c66cc3d4dadb15b669256530cfda89359cdb9340).
|
||||
Thanks to xet7.
|
||||
- [It seems Ubuntu eoan package bsdtar has been renamed to
|
||||
libarchive-tools](https://github.com/wekan/wekan/commit/c60967e935bdc0e7e9aea0a1c23178aee8a73c29).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix slow scroll on card detail by setting scrollInertia to 0](https://github.com/wekan/wekan/commit/599ace1db7918df41d9708d14b0351acb0f8688e).
|
||||
Thanks to cafeoh.
|
||||
- [Fix lint errors](https://github.com/wekan/wekan/commit/788dd0a81a06efee165007a92780f9e8c2c754ac).
|
||||
Thanks to xet7.
|
||||
- [Remove eslint option that does not work](https://github.com/wekan/wekan/commit/a06daff92e5f7cca55d1698252e3aa6526877c8b).
|
||||
Thanks to xet7.
|
||||
- [Try to fix lint errors](https://github.com/wekan/wekan/commit/58e505f79a0617011576bdded9427b0d448d6107).
|
||||
Thanks to xet7.
|
||||
- [Add to Snap MongoDB logging option --quiet](https://github.com/wekan/wekan/commit/c7ded515022fff2c1167ce8938405a846185a710).
|
||||
Thanks to fmeehan and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.55 2019-11-19 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [When logged in, use database for setting, so that changes are immediate. Only on public board use cookies.
|
||||
Comment out Collapse CSS that is not in use](https://github.com/wekan/wekan/commit/351d4767d7e93c90ac798769d6071da8730d834f).
|
||||
Thanks to xet7.
|
||||
- [Use database when logged in. Part 2](https://github.com/wekan/wekan/commit/4786b0c18ddeb8f48525216eabebdced7159467d).
|
||||
Thanks to xet7.
|
||||
- [Use database when logged in. Part 3](https://github.com/wekan/wekan/commit/115d23f9293cad8a93f18f75a47a8a65756f71ce).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.54 2019-11-18 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [New feature: Now there is popup selection of Lists/Swimlanes/Calendar/Roles](https://github.com/wekan/wekan/commit/96abe3c6914ce37d9fb44da8fda375e40ad65c9e).
|
||||
Thanks to xet7.
|
||||
- [New feature, not set visible yet, because switching to it does not
|
||||
work properly yet: Collapsible Swimlanes](https://github.com/wekan/wekan/issues/2804).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix: Public board now loads correctly. When you select one of Lists/Swimlanes/Calendar view and
|
||||
reload webbrowser page, it can change view](https://github.com/wekan/wekan/issues/2311).
|
||||
Thanks to xet7.
|
||||
- [Fix: List sorting commented out](https://github.com/wekan/wekan/issues/2800).
|
||||
Thanks to xet7.
|
||||
- [Fix: Errors hasHiddenMinicardText, hasShowDragHandles, showSort, hasSortBy, profile,
|
||||
FirefoxAndroid/IE11/Vivaldi/Chromium browsers not working by using cookies instead of
|
||||
database](https://github.com/wekan/wekan/issues/2643#issuecomment-554907955).
|
||||
Note: Cookie changes are not always immediate, if there is no effect, you may need to
|
||||
reload webbrowser page. This could be improved later.
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.53 2019-11-14 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Revert list sorting change of Wekan v3.51 because it reversed alphabetical sorting of
|
||||
lists](https://github.com/wekan/wekan/commit/ab2a721a1443b903cdbbbe275f41ffd3269012c6).
|
||||
Thanks to Dalisay and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.52 2019-11-14 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Add database migration for assignee](https://github.com/wekan/wekan/commit/5b41d72e8de93833e1788962427422cff62c09a2).
|
||||
Thanks to ocdtrekkie and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.51 2019-11-14 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- [Change sorting lists to work on desktop drag handle page instead,
|
||||
where it seems to work better](https://github.com/wekan/wekan/commit/bbc3ab3f994c5a61a4414bc64b05f5a03d259e46).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.50 2019-11-13 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
|
@ -6,7 +418,7 @@ This release adds the following new features:
|
|||
mode](https://github.com/wekan/wekan/commits/77f8b76d4e13c35ea3451622176bbb69a4d39a32).
|
||||
Thanks to whowillcare.
|
||||
- Allow user to sort Lists in Board by his own preference boardadmin can star
|
||||
list [1](https://github.com/wekan/wekan/commit/bc2a20f04e32607f8488a9cecd815647fb43e40e),
|
||||
list [1](https://github.com/wekan/wekan/commit/bc2a20f04e32607f8488a9cecd815647fb43e40e),
|
||||
[2](https://github.com/wekan/wekan/commit/bc2a20f04e32607f8488a9cecd815647fb43e40e).
|
||||
Thanks to whowillcare.
|
||||
- [Allowing user to filter list in Filter function not just cards
|
||||
|
@ -17,6 +429,31 @@ This release adds the following new features:
|
|||
- Enhancement: [Set card times more sensible using the 'Today' button in
|
||||
datepicker](https://github.com/wekan/wekan/pull/2747).
|
||||
Thanks to liske.
|
||||
- [At card, added Assignee field like Jira, and REST API for it](https://github.com/wekan/wekan/issues/2452).
|
||||
Parts:
|
||||
[Add assignee](https://github.com/wekan/wekan/commit/9e1aaf163f3bd0b3c2d2aee8225d111f83b3d421),
|
||||
[Remove Assignee. Avatar icon is at card and assignee details](https://github.com/wekan/wekan/commit/3e8f9ef1a5275a5e9b691c7e74dc73b97a43689a),
|
||||
[When selecting new assignee (+) icon, list shows names who to add](https://github.com/wekan/wekan/commit/32ce2b51d8bff5e8851732394a8bae3c56f8b0b6),
|
||||
[More progress](https://github.com/wekan/wekan/commit/ea823ab68fd5243c8485177e44a074be836836b8),
|
||||
[In add assignee popup, avatars are now visible](https://github.com/wekan/wekan/commit/56efb5c41075151eeb259d99990a7e86695b2b69),
|
||||
[Add assignee popup title](https://github.com/wekan/wekan/commit/31dbdc835d5a092b8360a4dbe93e9fbcce068855),
|
||||
[Prevent more than one assignee](https://github.com/wekan/wekan/commit/1728298659521ee8e6fc94fedad3160030b9a2c3),
|
||||
[When there is one selected assignee on card, don't show + button for adding more assignees, because there can only be one
|
||||
assignee](https://github.com/wekan/wekan/commit/3cf09efb13438d66db6cf739591c679ea538d812),
|
||||
[Now assignee is visible also at minicard](https://github.com/wekan/wekan/commit/9fd14f7ecb593d3debf5adff8f6c61adb0c3feca),
|
||||
[Update REST API docs, there can only be one assignee in array](https://github.com/wekan/wekan/commit/de7509dc60257667192054e320b381f9dd0f0a31).
|
||||
Thanks to xet7.
|
||||
- [More mobile drag handles, and optional desktop drag handles](https://github.com/wekan/wekan/issues/2081): In Progress.
|
||||
Parts:
|
||||
[Some drag handle fixes](https://github.com/wekan/wekan/commit/6a8960547729148bd3085cb469f9e93d510ed66c),
|
||||
[Fix desktop swimlane drag handle position](https://github.com/wekan/wekan/commit/2ec15602d284122fce1a45bed352d0d4050162e2),
|
||||
[Fix card, list and swimlane move. Allow moving cards in multiselect mode](https://github.com/wekan/wekan/commit/537a48bede250155b30ec264904ba320625bab73).
|
||||
Thanks to xet7.
|
||||
|
||||
and adds the following updates:
|
||||
|
||||
- [Update Node.js to v8.16.2](https://github.com/wekan/wekan/commit/1eb3d25b40797fdab41d7dd59405cfcea81dcc61).
|
||||
Thanks to xet7.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
|
@ -45,6 +482,8 @@ and fixes the following bugs:
|
|||
Thanks to jymcheong.
|
||||
- [Fixed OpenAPI docs generation](https://github.com/wekan/wekan/pull/2783).
|
||||
Thanks to bentiss.
|
||||
- [Fixed close card button not visible on mobile web](https://github.com/wekan/wekan/36b5965dd07e3f0fd90069353310739c394c220f).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
|
@ -96,7 +535,7 @@ This release adds the following new features:
|
|||
NOTIFY_DUE_DAYS_BEFORE_AND_AFTER = 2,0 it means notification will be sent on both due day and two days before.
|
||||
Thanks to whowillcare.
|
||||
- [Added modifications the help files, related to NOTIFY_DUE_DAYS_BEFORE_AND_AFTER](https://github.com/wekan/wekan/pull/2740).
|
||||
Thanks to whowillcare.
|
||||
Thanks to whowillcare.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
|
@ -147,7 +586,7 @@ This release adds the following new features:
|
|||
|
||||
- [More Mobile and Desktop drag handles for Swimlanes/Lists/Cards. Part 1](https://github.com/wekan/wekan/commit/ff550e91103115e7b731dd80c4588b93b2d4c64f).
|
||||
Thanks to xet7.
|
||||
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.40 2019-09-11 Wekan release
|
||||
|
@ -556,7 +995,7 @@ This release fixes the following bugs:
|
|||
|
||||
- [Add missing dependencies back and revert deleting phantomjs](https://github.com/wekan/wekan/commit/32e9aa0ddaf1b015825b8c62ad17ed74b449e4b1).
|
||||
Thanks to whowillcare and xet7.
|
||||
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v3.09 2019-08-07 Wekan release
|
||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -1,13 +1,13 @@
|
|||
FROM ubuntu:disco
|
||||
FROM ubuntu:rolling
|
||||
LABEL maintainer="wekan"
|
||||
|
||||
# Set the environment variables (defaults where required)
|
||||
# DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
|
||||
# ENV BUILD_DEPS="paxctl"
|
||||
ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates" \
|
||||
ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates python3" \
|
||||
DEBUG=false \
|
||||
NODE_VERSION=v8.16.1 \
|
||||
METEOR_RELEASE=1.8.1 \
|
||||
NODE_VERSION=v12.15.0 \
|
||||
METEOR_RELEASE=1.9.0 \
|
||||
USE_EDGE=false \
|
||||
METEOR_EDGE=1.5-beta.17 \
|
||||
NPM_VERSION=latest \
|
||||
|
@ -21,7 +21,7 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 g++ build-essential
|
|||
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 \
|
||||
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 \
|
||||
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 \
|
||||
RICHER_CARD_COMMENT_EDITOR=true \
|
||||
RICHER_CARD_COMMENT_EDITOR=false \
|
||||
CARD_OPENED_WEBHOOK_ENABLED=false \
|
||||
ATTACHMENTS_STORE_PATH="" \
|
||||
MAX_IMAGE_PIXEL="" \
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v3.49.0"
|
||||
appVersion: "v3.78.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
|
|
@ -201,20 +201,20 @@ template(name="cardActivities")
|
|||
.activity-checklist(href="{{ card.absoluteUrl }}")
|
||||
+viewer
|
||||
= checklistItem.title
|
||||
|
||||
|
||||
if(currentData.timeKey)
|
||||
| {{{_ activityType }}}
|
||||
= ' '
|
||||
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
|
||||
if (currentData.timeOldValue)
|
||||
= ' '
|
||||
= ' '
|
||||
| {{{_ "previous_as" }}}
|
||||
= ' '
|
||||
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
|
||||
= ' @'
|
||||
else if(currentData.timeValue)
|
||||
| {{{_ activityType currentData.timeValue}}}
|
||||
|
||||
|
||||
|
||||
if($eq activityType 'deleteComment')
|
||||
| {{{_ 'activity-deleteComment' currentData.commentId}}}.
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
clear: both
|
||||
|
||||
.activity
|
||||
margin: 10px 0
|
||||
margin: 0.5px 0
|
||||
display: flex
|
||||
|
||||
.member
|
||||
|
|
|
@ -16,9 +16,6 @@ BlazeComponent.extendComponent({
|
|||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-new-comment:not(.focus)'() {
|
||||
commentFormIsOpen.set(true);
|
||||
},
|
||||
'submit .js-new-comment-form'(evt) {
|
||||
const input = this.getInput();
|
||||
const text = input.val().trim();
|
||||
|
|
|
@ -46,3 +46,23 @@
|
|||
|
||||
&:is-open
|
||||
cursor: auto
|
||||
|
||||
.comment-item
|
||||
background-color: #fff
|
||||
border: 0
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .23)
|
||||
color: #8c8c8c
|
||||
height: 36px
|
||||
margin: 4px 4px 6px 0
|
||||
width: 92%
|
||||
|
||||
&:hover
|
||||
background: darken(white, 12%)
|
||||
|
||||
&.add-comment
|
||||
display: flex
|
||||
margin: 5px
|
||||
|
||||
a
|
||||
display: block
|
||||
margin: auto
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Cookies } from 'meteor/ostrio:cookies';
|
||||
const cookies = new Cookies();
|
||||
const subManager = new SubsManager();
|
||||
const { calculateIndex, enableClickOnTouch } = Utils;
|
||||
const swimlaneWhileSortingHeight = 150;
|
||||
|
@ -89,7 +91,6 @@ BlazeComponent.extendComponent({
|
|||
helper.append(list.clone());
|
||||
return helper;
|
||||
},
|
||||
handle: '.js-swimlane-header-handle',
|
||||
items: '.swimlane:not(.placeholder)',
|
||||
placeholder: 'swimlane placeholder',
|
||||
distance: 7,
|
||||
|
@ -193,6 +194,32 @@ BlazeComponent.extendComponent({
|
|||
// ugly touch event hotfix
|
||||
enableClickOnTouch('.js-swimlane:not(.placeholder)');
|
||||
|
||||
this.autorun(() => {
|
||||
let showDesktopDragHandles = false;
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
showDesktopDragHandles = (currentUser.profile || {})
|
||||
.showDesktopDragHandles;
|
||||
} else if (cookies.has('showDesktopDragHandles')) {
|
||||
showDesktopDragHandles = true;
|
||||
} else {
|
||||
showDesktopDragHandles = false;
|
||||
}
|
||||
if (!Utils.isMiniScreen() && showDesktopDragHandles) {
|
||||
$swimlanesDom.sortable({
|
||||
handle: '.js-swimlane-header-handle',
|
||||
});
|
||||
} else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
|
||||
$swimlanesDom.sortable({
|
||||
handle: '.swimlane-header',
|
||||
});
|
||||
}
|
||||
|
||||
// Disable drag-dropping if the current user is not a board member or is miniscreen
|
||||
$swimlanesDom.sortable('option', 'disabled', !userIsMember());
|
||||
$swimlanesDom.sortable('option', 'disabled', Utils.isMiniScreen());
|
||||
});
|
||||
|
||||
function userIsMember() {
|
||||
return (
|
||||
Meteor.user() &&
|
||||
|
@ -210,21 +237,30 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
isViewSwimlanes() {
|
||||
const currentUser = Meteor.user();
|
||||
if (!currentUser) return false;
|
||||
return (currentUser.profile || {}).boardView === 'board-view-swimlanes';
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).boardView === 'board-view-swimlanes';
|
||||
} else {
|
||||
return cookies.get('boardView') === 'board-view-swimlanes';
|
||||
}
|
||||
},
|
||||
|
||||
isViewLists() {
|
||||
const currentUser = Meteor.user();
|
||||
if (!currentUser) return true;
|
||||
return (currentUser.profile || {}).boardView === 'board-view-lists';
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).boardView === 'board-view-lists';
|
||||
} else {
|
||||
return cookies.get('boardView') === 'board-view-lists';
|
||||
}
|
||||
},
|
||||
|
||||
isViewCalendar() {
|
||||
const currentUser = Meteor.user();
|
||||
if (!currentUser) return false;
|
||||
return (currentUser.profile || {}).boardView === 'board-view-cal';
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).boardView === 'board-view-cal';
|
||||
} else {
|
||||
return cookies.get('boardView') === 'board-view-cal';
|
||||
}
|
||||
},
|
||||
|
||||
openNewListForm() {
|
||||
|
@ -381,8 +417,11 @@ BlazeComponent.extendComponent({
|
|||
};
|
||||
},
|
||||
isViewCalendar() {
|
||||
const currentUser = Meteor.user();
|
||||
if (!currentUser) return false;
|
||||
return (currentUser.profile || {}).boardView === 'board-view-cal';
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).boardView === 'board-view-cal';
|
||||
} else {
|
||||
return cookies.get('boardView') === 'board-view-cal';
|
||||
}
|
||||
},
|
||||
}).register('calendarView');
|
||||
|
|
|
@ -77,10 +77,11 @@ template(name="boardHeaderBar")
|
|||
i.fa.fa-archive
|
||||
span {{_ 'archives'}}
|
||||
|
||||
if showSort
|
||||
a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}")
|
||||
i.fa(class="{{directionClass}}")
|
||||
span {{_ 'sort'}}{{_ listSortShortDesc}}
|
||||
//if showSort
|
||||
// a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}")
|
||||
// i.fa(class="{{directionClass}}")
|
||||
// span {{_ 'sort'}}{{_ listSortShortDesc}}
|
||||
|
||||
a.board-header-btn.js-open-filter-view(
|
||||
title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
|
||||
class="{{#if Filter.isActive}}emphasis{{/if}}")
|
||||
|
@ -89,15 +90,6 @@ 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
|
||||
|
@ -106,8 +98,14 @@ template(name="boardHeaderBar")
|
|||
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}}
|
||||
i.fa.fa-caret-down
|
||||
if $eq boardView 'board-view-lists'
|
||||
i.fa.fa-trello
|
||||
if $eq boardView 'board-view-swimlanes'
|
||||
i.fa.fa-th-large
|
||||
if $eq boardView 'board-view-cal'
|
||||
i.fa.fa-calendar
|
||||
span {{#if boardView}}{{_ boardView}}{{else}}{{_ 'board-view-lists'}}{{/if}}
|
||||
|
||||
if canModifyBoard
|
||||
a.board-header-btn.js-multiselection-activate(
|
||||
|
@ -172,6 +170,44 @@ template(name="boardChangeWatchPopup")
|
|||
i.fa.fa-check
|
||||
span.sub-name {{_ 'muted-info'}}
|
||||
|
||||
template(name="boardChangeViewPopup")
|
||||
ul.pop-over-list
|
||||
li
|
||||
with "board-view-lists"
|
||||
a.js-open-lists-view
|
||||
i.fa.fa-trello.colorful
|
||||
| {{_ 'board-view-lists'}}
|
||||
if $eq Utils.boardView "board-view-lists"
|
||||
i.fa.fa-check
|
||||
li
|
||||
with "board-view-swimlanes"
|
||||
a.js-open-swimlanes-view
|
||||
i.fa.fa-th-large.colorful
|
||||
| {{_ 'board-view-swimlanes'}}
|
||||
if $eq Utils.boardView "board-view-swimlanes"
|
||||
i.fa.fa-check
|
||||
li
|
||||
with "board-view-cal"
|
||||
a.js-open-cal-view
|
||||
i.fa.fa-calendar.colorful
|
||||
| {{_ 'board-view-cal'}}
|
||||
if $eq Utils.boardView "board-view-cal"
|
||||
i.fa.fa-check
|
||||
if currentUser.isAdmin
|
||||
hr
|
||||
li
|
||||
with "board-view-rules"
|
||||
a.js-open-rules-view(title="{{_ 'rules'}}")
|
||||
i.fa.fa-magic
|
||||
| {{_ 'rules'}}
|
||||
else if currentUser.isBoardAdmin
|
||||
hr
|
||||
li
|
||||
with "board-view-rules"
|
||||
a.js-open-rules-view(title="{{_ 'rules'}}")
|
||||
i.fa.fa-magic
|
||||
| {{_ 'rules'}}
|
||||
|
||||
template(name="createBoard")
|
||||
form
|
||||
label
|
||||
|
@ -198,19 +234,19 @@ template(name="createBoard")
|
|||
| /
|
||||
a.js-board-template {{_ 'template'}}
|
||||
|
||||
template(name="listsortPopup")
|
||||
h2
|
||||
| {{_ 'list-sort-by'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
each value in allowedSortValues
|
||||
li
|
||||
a.js-sort-by(name="{{value.name}}")
|
||||
if $eq sortby value.name
|
||||
i(class="fa {{Direction}}")
|
||||
| {{_ value.label }}{{_ value.shortLabel}}
|
||||
if $eq sortby value.name
|
||||
i(class="fa fa-check")
|
||||
//template(name="listsortPopup")
|
||||
// h2
|
||||
// | {{_ 'list-sort-by'}}
|
||||
// hr
|
||||
// ul.pop-over-list
|
||||
// each value in allowedSortValues
|
||||
// li
|
||||
// a.js-sort-by(name="{{value.name}}")
|
||||
// if $eq sortby value.name
|
||||
// i(class="fa {{Direction}}")
|
||||
// | {{_ value.label }}{{_ value.shortLabel}}
|
||||
// if $eq sortby value.name
|
||||
// i(class="fa fa-check")
|
||||
|
||||
template(name="boardChangeTitlePopup")
|
||||
form
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
/*
|
||||
const DOWNCLS = 'fa-sort-down';
|
||||
const UPCLS = 'fa-sort-up';
|
||||
*/
|
||||
Template.boardMenuPopup.events({
|
||||
'click .js-rename-board': Popup.open('boardChangeTitle'),
|
||||
'click .js-custom-fields'() {
|
||||
|
@ -28,6 +30,7 @@ Template.boardMenuPopup.events({
|
|||
'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
|
||||
'click .js-import-board': Popup.open('chooseBoardSource'),
|
||||
'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
|
||||
'click .js-card-settings': Popup.open('boardCardSettings'),
|
||||
});
|
||||
|
||||
Template.boardMenuPopup.helpers({
|
||||
|
@ -82,6 +85,7 @@ BlazeComponent.extendComponent({
|
|||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
return currentBoard && currentBoard.stars >= 2;
|
||||
},
|
||||
/*
|
||||
showSort() {
|
||||
return Meteor.user().hasSortBy();
|
||||
},
|
||||
|
@ -101,6 +105,7 @@ BlazeComponent.extendComponent({
|
|||
listSortShortDesc() {
|
||||
return `list-label-short-${this.currentListSortBy()}`;
|
||||
},
|
||||
*/
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
|
@ -114,30 +119,14 @@ BlazeComponent.extendComponent({
|
|||
'click .js-open-archived-board'() {
|
||||
Modal.open('archivedBoards');
|
||||
},
|
||||
'click .js-toggle-board-view'() {
|
||||
const currentUser = Meteor.user();
|
||||
if (
|
||||
(currentUser.profile || {}).boardView === 'board-view-swimlanes'
|
||||
) {
|
||||
currentUser.setBoardView('board-view-cal');
|
||||
} else if (
|
||||
(currentUser.profile || {}).boardView === 'board-view-lists'
|
||||
) {
|
||||
currentUser.setBoardView('board-view-swimlanes');
|
||||
} else if (
|
||||
(currentUser.profile || {}).boardView === 'board-view-cal'
|
||||
) {
|
||||
currentUser.setBoardView('board-view-lists');
|
||||
} else {
|
||||
currentUser.setBoardView('board-view-swimlanes');
|
||||
}
|
||||
},
|
||||
'click .js-toggle-board-view': Popup.open('boardChangeView'),
|
||||
'click .js-toggle-sidebar'() {
|
||||
Sidebar.toggle();
|
||||
},
|
||||
'click .js-open-filter-view'() {
|
||||
Sidebar.setView('filter');
|
||||
},
|
||||
/*
|
||||
'click .js-open-sort-view'(evt) {
|
||||
const target = evt.target;
|
||||
if (target.tagName === 'I') {
|
||||
|
@ -148,6 +137,7 @@ BlazeComponent.extendComponent({
|
|||
Popup.open('listsort')(evt);
|
||||
}
|
||||
},
|
||||
*/
|
||||
'click .js-filter-reset'(event) {
|
||||
event.stopPropagation();
|
||||
Sidebar.setView();
|
||||
|
@ -156,9 +146,6 @@ BlazeComponent.extendComponent({
|
|||
'click .js-open-search-view'() {
|
||||
Sidebar.setView('search');
|
||||
},
|
||||
'click .js-open-rules-view'() {
|
||||
Modal.openWide('rulesMain');
|
||||
},
|
||||
'click .js-multiselection-activate'() {
|
||||
const currentCard = Session.get('currentCard');
|
||||
MultiSelection.activate();
|
||||
|
@ -186,6 +173,28 @@ Template.boardHeaderBar.helpers({
|
|||
!Meteor.user().isCommentOnly()
|
||||
);
|
||||
},
|
||||
boardView() {
|
||||
return Utils.boardView();
|
||||
},
|
||||
});
|
||||
|
||||
Template.boardChangeViewPopup.events({
|
||||
'click .js-open-lists-view'() {
|
||||
Utils.setBoardView('board-view-lists');
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-open-swimlanes-view'() {
|
||||
Utils.setBoardView('board-view-swimlanes');
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-open-cal-view'() {
|
||||
Utils.setBoardView('board-view-cal');
|
||||
Popup.close();
|
||||
},
|
||||
'click .js-open-rules-view'() {
|
||||
Modal.openWide('rulesMain');
|
||||
Popup.close();
|
||||
},
|
||||
});
|
||||
|
||||
const CreateBoard = BlazeComponent.extendComponent({
|
||||
|
@ -308,6 +317,7 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
}).register('boardChangeWatchPopup');
|
||||
|
||||
/*
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
//this.sortBy = new ReactiveVar();
|
||||
|
@ -377,3 +387,4 @@ BlazeComponent.extendComponent({
|
|||
];
|
||||
},
|
||||
}).register('listsortPopup');
|
||||
*/
|
||||
|
|
|
@ -45,19 +45,22 @@ template(name="attachmentsGalery")
|
|||
| {{_ 'download'}}
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
if isImage
|
||||
a(class="{{#if $eq ../coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}")
|
||||
i.fa.fa-thumb-tack
|
||||
if($eq ../coverId _id)
|
||||
| {{_ 'remove-cover'}}
|
||||
else
|
||||
| {{_ 'add-cover'}}
|
||||
a.js-confirm-delete
|
||||
i.fa.fa-close
|
||||
| {{_ 'delete'}}
|
||||
unless currentUser.isWorker
|
||||
if isImage
|
||||
a(class="{{#if $eq ../coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}")
|
||||
i.fa.fa-thumb-tack
|
||||
if($eq ../coverId _id)
|
||||
| {{_ 'remove-cover'}}
|
||||
else
|
||||
| {{_ 'add-cover'}}
|
||||
a.js-confirm-delete
|
||||
i.fa.fa-close
|
||||
| {{_ 'delete'}}
|
||||
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
li.attachment-item.add-attachment
|
||||
a.js-add-attachment {{_ 'add-attachment' }}
|
||||
|
||||
unless currentUser.isWorker
|
||||
//li.attachment-item.add-attachment
|
||||
a.js-add-attachment
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'add-attachment' }}
|
||||
|
|
|
@ -97,7 +97,8 @@ Template.dateBadge.helpers({
|
|||
return (
|
||||
Meteor.user() &&
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly()
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -4,15 +4,27 @@ template(name="cardDetails")
|
|||
+inlinedForm(classNames="js-card-details-title")
|
||||
+editCardTitleForm
|
||||
else
|
||||
a.fa.fa-times-thin.close-card-details.js-close-card-details
|
||||
if currentUser.isBoardMember
|
||||
a.fa.fa-navicon.card-details-menu.js-open-card-details-menu
|
||||
unless isMiniScreen
|
||||
a.fa.fa-times-thin.close-card-details.js-close-card-details
|
||||
if currentUser.isBoardMember
|
||||
a.fa.fa-navicon.card-details-menu.js-open-card-details-menu
|
||||
input.inline-input(type="text" id="cardURL_copy" value="{{ absoluteUrl }}")
|
||||
a.fa.fa-link.card-copy-button.js-copy-link(
|
||||
class="fa-link"
|
||||
title="{{_ 'copy-card-link-to-clipboard'}}"
|
||||
value="{{ absoluteUrl }}"
|
||||
)
|
||||
if isMiniScreen
|
||||
a.fa.fa-times-thin.close-card-details-mobile-web.js-close-card-details
|
||||
if currentUser.isBoardMember
|
||||
a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu
|
||||
a.fa.fa-link.card-copy-mobile-button
|
||||
h2.card-details-title.js-card-title(
|
||||
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
|
||||
+viewer
|
||||
= getTitle
|
||||
if isWatching
|
||||
i.fa.fa-eye.card-details-watch
|
||||
if isWatching
|
||||
i.card-details-watch.fa.fa-eye
|
||||
.card-details-path
|
||||
each parentList
|
||||
| >
|
||||
|
@ -31,70 +43,105 @@ template(name="cardDetails")
|
|||
p.warning {{_ 'card-archived'}}
|
||||
|
||||
.card-details-items
|
||||
.card-details-item.card-details-item-received
|
||||
h3.card-details-item-title {{_ 'card-received'}}
|
||||
if getReceived
|
||||
+cardReceivedDate
|
||||
else
|
||||
if currentBoard.allowsReceivedDate
|
||||
.card-details-item.card-details-item-received
|
||||
h3
|
||||
i.fa.fa-sign-out
|
||||
card-details-item-title {{_ 'card-received'}}
|
||||
if getReceived
|
||||
+cardReceivedDate
|
||||
else
|
||||
if canModifyCard
|
||||
unless currentUser.isWorker
|
||||
a.card-label.add-label.js-received-date
|
||||
i.fa.fa-plus
|
||||
|
||||
if currentBoard.allowsStartDate
|
||||
.card-details-item.card-details-item-start
|
||||
h3
|
||||
i.fa.fa-hourglass-start
|
||||
card-details-item-title {{_ 'card-start'}}
|
||||
if getStart
|
||||
+cardStartDate
|
||||
else
|
||||
if canModifyCard
|
||||
unless currentUser.isWorker
|
||||
a.card-label.add-label.js-start-date
|
||||
i.fa.fa-plus
|
||||
|
||||
if currentBoard.allowsDueDate
|
||||
.card-details-item.card-details-item-due
|
||||
h3
|
||||
i.fa.fa-sign-in
|
||||
card-details-item-title {{_ 'card-due'}}
|
||||
if getDue
|
||||
+cardDueDate
|
||||
else
|
||||
if canModifyCard
|
||||
unless currentUser.isWorker
|
||||
a.card-label.add-label.js-due-date
|
||||
i.fa.fa-plus
|
||||
|
||||
if currentBoard.allowsEndDate
|
||||
.card-details-item.card-details-item-end
|
||||
h3
|
||||
i.fa.fa-hourglass-end
|
||||
card-details-item-title {{_ 'card-end'}}
|
||||
if getEnd
|
||||
+cardEndDate
|
||||
else
|
||||
if canModifyCard
|
||||
unless currentUser.isWorker
|
||||
a.card-label.add-label.js-end-date
|
||||
i.fa.fa-plus
|
||||
|
||||
//.card-details-items
|
||||
if currentBoard.allowsMembers
|
||||
.card-details-item.card-details-item-members
|
||||
h3
|
||||
i.fa.fa-users
|
||||
card-details-item-title {{_ 'members'}}
|
||||
each getMembers
|
||||
+userAvatar(userId=this cardId=../_id)
|
||||
| {{! XXX Hack to hide syntaxic coloration /// }}
|
||||
if canModifyCard
|
||||
a.js-received-date {{_ 'add'}}
|
||||
unless currentUser.isWorker
|
||||
a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
.card-details-item.card-details-item-start
|
||||
h3.card-details-item-title {{_ 'card-start'}}
|
||||
if getStart
|
||||
+cardStartDate
|
||||
else
|
||||
//if assigneeSelected
|
||||
if currentBoard.allowsAssignee
|
||||
.card-details-item.card-details-item-assignees
|
||||
h3
|
||||
i.fa.fa-user
|
||||
card-details-item-title {{_ 'assignee'}}
|
||||
each getAssignees
|
||||
+userAvatarAssignee(userId=this cardId=../_id)
|
||||
| {{! XXX Hack to hide syntaxic coloration /// }}
|
||||
if canModifyCard
|
||||
a.js-start-date {{_ 'add'}}
|
||||
|
||||
.card-details-item.card-details-item-due
|
||||
h3.card-details-item-title {{_ 'card-due'}}
|
||||
if getDue
|
||||
+cardDueDate
|
||||
else
|
||||
if canModifyCard
|
||||
a.js-due-date {{_ 'add'}}
|
||||
|
||||
.card-details-item.card-details-item-end
|
||||
h3.card-details-item-title {{_ 'card-end'}}
|
||||
if getEnd
|
||||
+cardEndDate
|
||||
else
|
||||
if canModifyCard
|
||||
a.js-end-date {{_ 'add'}}
|
||||
|
||||
.card-details-items
|
||||
.card-details-item.card-details-item-members
|
||||
h3.card-details-item-title {{_ 'members'}}
|
||||
each getMembers
|
||||
+userAvatar(userId=this cardId=../_id)
|
||||
| {{! XXX Hack to hide syntaxic coloration /// }}
|
||||
if canModifyCard
|
||||
a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
.card-details-item.card-details-item-assignees
|
||||
h3.card-details-item-title {{_ 'assignee'}}
|
||||
each getAssignees
|
||||
+userAvatarAssignee(userId=this cardId=../_id)
|
||||
| {{! XXX Hack to hide syntaxic coloration /// }}
|
||||
if canModifyCard
|
||||
unless assigneeSelected
|
||||
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
|
||||
i.fa.fa-plus
|
||||
if currentUser.isWorker
|
||||
unless assigneeSelected
|
||||
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
.card-details-item.card-details-item-labels
|
||||
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
|
||||
if canModifyCard
|
||||
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
|
||||
i.fa.fa-plus
|
||||
if currentBoard.allowsLabels
|
||||
.card-details-item.card-details-item-labels
|
||||
h3
|
||||
i.fa.fa-tags
|
||||
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
|
||||
if canModifyCard
|
||||
unless currentUser.isWorker
|
||||
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
.card-details-items
|
||||
//.card-details-items
|
||||
each customFieldsWD
|
||||
.card-details-item.card-details-item-customfield
|
||||
h3.card-details-item-title
|
||||
|
@ -102,7 +149,7 @@ template(name="cardDetails")
|
|||
= definition.name
|
||||
+cardCustomField
|
||||
|
||||
.card-details-items
|
||||
//.card-details-items
|
||||
if getSpentTime
|
||||
.card-details-item.card-details-item-spent
|
||||
if getIsOvertime
|
||||
|
@ -111,84 +158,103 @@ template(name="cardDetails")
|
|||
h3.card-details-item-title {{_ 'spent-time-hours'}}
|
||||
+cardSpentTime
|
||||
|
||||
//.card-details-items
|
||||
if currentBoard.allowsRequestedBy
|
||||
.card-details-item.card-details-item-name
|
||||
h3
|
||||
i.fa.fa-shopping-cart
|
||||
card-details-item-title {{_ 'requested-by'}}
|
||||
if canModifyCard
|
||||
unless currentUser.isWorker
|
||||
+inlinedForm(classNames="js-card-details-requester")
|
||||
+editCardRequesterForm
|
||||
else
|
||||
a.js-open-inlined-form
|
||||
if getRequestedBy
|
||||
+viewer
|
||||
= getRequestedBy
|
||||
else
|
||||
| {{_ 'add'}}
|
||||
else if getRequestedBy
|
||||
+viewer
|
||||
= getRequestedBy
|
||||
|
||||
if currentBoard.allowsAssignedBy
|
||||
.card-details-item.card-details-item-name
|
||||
h3
|
||||
i.fa.fa-user-plus
|
||||
card-details-item-title {{_ 'assigned-by'}}
|
||||
if canModifyCard
|
||||
unless currentUser.isWorker
|
||||
+inlinedForm(classNames="js-card-details-assigner")
|
||||
+editCardAssignerForm
|
||||
else
|
||||
a.js-open-inlined-form
|
||||
if getAssignedBy
|
||||
+viewer
|
||||
= getAssignedBy
|
||||
else
|
||||
| {{_ 'add'}}
|
||||
else if getRequestedBy
|
||||
+viewer
|
||||
= getAssignedBy
|
||||
|
||||
//- XXX We should use "editable" to avoid repetiting ourselves
|
||||
if canModifyCard
|
||||
h3.card-details-item-title {{_ 'description'}}
|
||||
+inlinedCardDescription(classNames="card-description js-card-description")
|
||||
+editor(autofocus=true)
|
||||
| {{getUnsavedValue 'cardDescription' _id getDescription}}
|
||||
.edit-controls.clearfix
|
||||
button.primary(type="submit") {{_ 'save'}}
|
||||
a.fa.fa-times-thin.js-close-inlined-form
|
||||
else
|
||||
a.js-open-inlined-form
|
||||
if getDescription
|
||||
+viewer
|
||||
= getDescription
|
||||
unless currentUser.isWorker
|
||||
if currentBoard.allowsDescriptionTitle
|
||||
h3
|
||||
i.fa.fa-align-left
|
||||
card-details-item-title {{_ 'description'}}
|
||||
if currentBoard.allowsDescriptionText
|
||||
+inlinedCardDescription(classNames="card-description js-card-description")
|
||||
+editor(autofocus=true)
|
||||
| {{getUnsavedValue 'cardDescription' _id getDescription}}
|
||||
.edit-controls.clearfix
|
||||
button.primary(type="submit") {{_ 'save'}}
|
||||
a.fa.fa-times-thin.js-close-inlined-form
|
||||
else
|
||||
| {{_ 'edit'}}
|
||||
if (hasUnsavedValue 'cardDescription' _id)
|
||||
p.quiet
|
||||
| {{_ 'unsaved-description'}}
|
||||
a.js-open-inlined-form {{_ 'view-it'}}
|
||||
= ' - '
|
||||
a.js-close-inlined-form {{_ 'discard'}}
|
||||
if currentBoard.allowsDescriptionText
|
||||
a.js-open-inlined-form
|
||||
if getDescription
|
||||
+viewer
|
||||
= getDescription
|
||||
else
|
||||
| {{_ 'edit'}}
|
||||
if (hasUnsavedValue 'cardDescription' _id)
|
||||
p.quiet
|
||||
| {{_ 'unsaved-description'}}
|
||||
a.js-open-inlined-form {{_ 'view-it'}}
|
||||
= ' - '
|
||||
a.js-close-inlined-form {{_ 'discard'}}
|
||||
else if getDescription
|
||||
h3.card-details-item-title {{_ 'description'}}
|
||||
+viewer
|
||||
= getDescription
|
||||
if currentBoard.allowsDescriptionTitle
|
||||
h3.card-details-item-title {{_ 'description'}}
|
||||
if currentBoard.allowsDescriptionText
|
||||
+viewer
|
||||
= getDescription
|
||||
|
||||
.card-details-items
|
||||
.card-details-item.card-details-item-name
|
||||
h3.card-details-item-title {{_ 'requested-by'}}
|
||||
if canModifyCard
|
||||
+inlinedForm(classNames="js-card-details-requester")
|
||||
+editCardRequesterForm
|
||||
else
|
||||
a.js-open-inlined-form
|
||||
if getRequestedBy
|
||||
+viewer
|
||||
= getRequestedBy
|
||||
else
|
||||
| {{_ 'add'}}
|
||||
else if getRequestedBy
|
||||
+viewer
|
||||
= getRequestedBy
|
||||
|
||||
.card-details-item.card-details-item-name
|
||||
h3.card-details-item-title {{_ 'assigned-by'}}
|
||||
if canModifyCard
|
||||
+inlinedForm(classNames="js-card-details-assigner")
|
||||
+editCardAssignerForm
|
||||
else
|
||||
a.js-open-inlined-form
|
||||
if getAssignedBy
|
||||
+viewer
|
||||
= getAssignedBy
|
||||
else
|
||||
| {{_ 'add'}}
|
||||
else if getRequestedBy
|
||||
+viewer
|
||||
= getAssignedBy
|
||||
|
||||
hr
|
||||
+checklists(cardId = _id)
|
||||
|
||||
if currentBoard.allowsSubtasks
|
||||
hr
|
||||
+subtasks(cardId = _id)
|
||||
|
||||
hr
|
||||
h3
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'attachments'}}
|
||||
|
||||
+attachmentsGalery
|
||||
.card-checklist-attachmentGalerys
|
||||
.card-checklist-attachmentGalery.card-checklists
|
||||
if currentBoard.allowsChecklists
|
||||
+checklists(cardId = _id)
|
||||
if currentBoard.allowsSubtasks
|
||||
hr
|
||||
+subtasks(cardId = _id)
|
||||
if currentBoard.allowsAttachments
|
||||
//- hr
|
||||
//- h3
|
||||
//- i.fa.fa-paperclip
|
||||
//- | {{_ 'attachments'}}
|
||||
.card-checklist-attachmentGalery.card-attachmentGalery
|
||||
+attachmentsGalery
|
||||
|
||||
hr
|
||||
unless currentUser.isNoComments
|
||||
.activity-title
|
||||
h3 {{ _ 'activity'}}
|
||||
h3
|
||||
i.fa.fa-history
|
||||
| {{ _ 'activity'}}
|
||||
if currentUser.isBoardMember
|
||||
.material-toggle-switch
|
||||
span.toggle-switch-title {{_ 'hide-system-messages'}}
|
||||
|
@ -197,9 +263,10 @@ template(name="cardDetails")
|
|||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleButton")
|
||||
label.toggle-label(for="toggleButton")
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isNoComments
|
||||
+commentForm
|
||||
if currentBoard.allowsComments
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isNoComments
|
||||
+commentForm
|
||||
unless currentUser.isNoComments
|
||||
if isLoaded.get
|
||||
if isLinkedCard
|
||||
|
@ -230,32 +297,79 @@ template(name="editCardAssignerForm")
|
|||
|
||||
template(name="cardDetailsActionsPopup")
|
||||
ul.pop-over-list
|
||||
li: a.js-toggle-watch-card {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
|
||||
li
|
||||
a.js-toggle-watch-card
|
||||
if isWatching
|
||||
i.fa.fa-eye
|
||||
| {{_ 'unwatch'}}
|
||||
else
|
||||
i.fa.fa-eye-slash
|
||||
| {{_ 'watch'}}
|
||||
if canModifyCard
|
||||
hr
|
||||
unless currentUser.isWorker
|
||||
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-custom-fields
|
||||
i.fa.fa-list-alt
|
||||
| {{_ '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-spent-time
|
||||
i.fa.fa-clock-o
|
||||
| {{_ 'editCardSpentTimePopup-title'}}
|
||||
li
|
||||
a.js-set-card-color
|
||||
i.fa.fa-paint-brush
|
||||
| {{_ 'setCardColorPopup-title'}}
|
||||
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-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-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'}}
|
||||
li: a.js-move-card-to-bottom {{_ 'moveCardToBottom-title'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-move-card {{_ 'moveCardPopup-title'}}
|
||||
li: a.js-copy-card {{_ 'copyCardPopup-title'}}
|
||||
li: a.js-copy-checklist-cards {{_ 'copyChecklistToManyCardsPopup-title'}}
|
||||
li
|
||||
a.js-move-card-to-top
|
||||
i.fa.fa-arrow-up
|
||||
| {{_ 'moveCardToTop-title'}}
|
||||
li
|
||||
a.js-move-card-to-bottom
|
||||
i.fa.fa-arrow-down
|
||||
| {{_ 'moveCardToBottom-title'}}
|
||||
unless currentUser.isWorker
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-move-card
|
||||
i.fa.fa-arrow-right
|
||||
| {{_ 'moveCardPopup-title'}}
|
||||
li
|
||||
a.js-copy-card
|
||||
i.fa.fa-copy
|
||||
| {{_ 'copyCardPopup-title'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-copy-checklist-cards
|
||||
i.fa.fa-list
|
||||
i.fa.fa-copy
|
||||
| {{_ 'copyChecklistToManyCardsPopup-title'}}
|
||||
unless archived
|
||||
li: a.js-archive {{_ 'archive-card'}}
|
||||
li: a.js-more {{_ 'cardMorePopup-title'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-archive
|
||||
i.fa.fa-arrow-right
|
||||
i.fa.fa-archive
|
||||
| {{_ 'archive-card'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-more
|
||||
i.fa.fa-link
|
||||
| {{_ 'cardMorePopup-title'}}
|
||||
|
||||
template(name="moveCardPopup")
|
||||
+boardsAndLists
|
||||
|
@ -307,16 +421,27 @@ template(name="cardMembersPopup")
|
|||
i.fa.fa-check
|
||||
|
||||
template(name="cardAssigneesPopup")
|
||||
ul.pop-over-list.js-card-assignee-list
|
||||
each board.activeMembers
|
||||
li.item(class="{{#if isCardAssignee}}active{{/if}}")
|
||||
a.name.js-select-assignee(href="#")
|
||||
+userAvatar(userId=user._id)
|
||||
span.full-name
|
||||
= user.profile.fullname
|
||||
| (<span class="username">{{ user.username }}</span>)
|
||||
if isCardAssignee
|
||||
i.fa.fa-check
|
||||
unless currentUser.isWorker
|
||||
ul.pop-over-list.js-card-assignee-list
|
||||
each board.activeMembers
|
||||
li.item(class="{{#if isCardAssignee}}active{{/if}}")
|
||||
a.name.js-select-assignee(href="#")
|
||||
+userAvatar(userId=user._id)
|
||||
span.full-name
|
||||
= user.profile.fullname
|
||||
| (<span class="username">{{ user.username }}</span>)
|
||||
if isCardAssignee
|
||||
i.fa.fa-check
|
||||
if currentUser.isWorker
|
||||
ul.pop-over-list.js-card-assignee-list
|
||||
li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}")
|
||||
a.name.js-select-assignee(href="#")
|
||||
+userAvatar(userId=currentUser._id)
|
||||
span.full-name
|
||||
= currentUser.profile.fullname
|
||||
| (<span class="username">{{ currentUser.username }}</span>)
|
||||
if currentUser.isCardAssignee
|
||||
i.fa.fa-check
|
||||
|
||||
template(name="userAvatarAssignee")
|
||||
a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
|
||||
|
@ -344,11 +469,13 @@ template(name="cardAssigneePopup")
|
|||
p.quiet @{{ user.username }}
|
||||
ul.pop-over-list
|
||||
if currentUser.isNotCommentOnly
|
||||
unless currentUser.isWorker
|
||||
li: a.js-remove-assignee {{_ 'remove-member-from-card'}}
|
||||
|
||||
if $eq currentUser._id user._id
|
||||
with currentUser
|
||||
li: a.js-edit-profile {{_ 'edit-profile'}}
|
||||
unless currentUser.isWorker
|
||||
if $eq currentUser._id user._id
|
||||
with currentUser
|
||||
li: a.js-edit-profile {{_ 'edit-profile'}}
|
||||
|
||||
template(name="userAvatarAssigneeInitials")
|
||||
svg.avatar.avatar-assignee-initials(viewBox="0 0 {{viewPortWidth}} 15")
|
||||
|
|
|
@ -51,7 +51,8 @@ BlazeComponent.extendComponent({
|
|||
return (
|
||||
Meteor.user() &&
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly()
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -252,6 +253,12 @@ BlazeComponent.extendComponent({
|
|||
if ($subtasksDom.data('sortable')) {
|
||||
$subtasksDom.sortable('option', 'disabled', !userIsMember());
|
||||
}
|
||||
if ($checklistsDom.data('sortable')) {
|
||||
$checklistsDom.sortable('option', 'disabled', Utils.isMiniScreen());
|
||||
}
|
||||
if ($subtasksDom.data('sortable')) {
|
||||
$subtasksDom.sortable('option', 'disabled', Utils.isMiniScreen());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -278,6 +285,29 @@ BlazeComponent.extendComponent({
|
|||
'click .js-close-card-details'() {
|
||||
Utils.goBoardId(this.data().boardId);
|
||||
},
|
||||
'click .js-copy-link'() {
|
||||
StringToCopyElement = document.getElementById('cardURL_copy');
|
||||
StringToCopyElement.select();
|
||||
if (document.execCommand('copy')) {
|
||||
StringToCopyElement.blur();
|
||||
} else {
|
||||
document.getElementById('cardURL_copy').selectionStart = 0;
|
||||
document.getElementById('cardURL_copy').selectionEnd = 999;
|
||||
document.execCommand('copy');
|
||||
if (window.getSelection) {
|
||||
if (window.getSelection().empty) {
|
||||
// Chrome
|
||||
window.getSelection().empty();
|
||||
} else if (window.getSelection().removeAllRanges) {
|
||||
// Firefox
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
} else if (document.selection) {
|
||||
// IE?
|
||||
document.selection.empty();
|
||||
}
|
||||
}
|
||||
},
|
||||
'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
|
||||
'submit .js-card-description'(event) {
|
||||
event.preventDefault();
|
||||
|
@ -291,6 +321,8 @@ BlazeComponent.extendComponent({
|
|||
.trim();
|
||||
if (title) {
|
||||
this.data().setTitle(title);
|
||||
} else {
|
||||
this.data().setTitle('');
|
||||
}
|
||||
},
|
||||
'submit .js-card-details-assigner'(event) {
|
||||
|
@ -300,6 +332,8 @@ BlazeComponent.extendComponent({
|
|||
.trim();
|
||||
if (assigner) {
|
||||
this.data().setAssignedBy(assigner);
|
||||
} else {
|
||||
this.data().setAssignedBy('');
|
||||
}
|
||||
},
|
||||
'submit .js-card-details-requester'(event) {
|
||||
|
@ -309,6 +343,8 @@ BlazeComponent.extendComponent({
|
|||
.trim();
|
||||
if (requester) {
|
||||
this.data().setRequestedBy(requester);
|
||||
} else {
|
||||
this.data().setRequestedBy('');
|
||||
}
|
||||
},
|
||||
'click .js-member': Popup.open('cardMember'),
|
||||
|
@ -364,6 +400,54 @@ Template.cardDetails.helpers({
|
|||
});
|
||||
},
|
||||
|
||||
receivedSelected() {
|
||||
if (this.getReceived().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
startSelected() {
|
||||
if (this.getStart().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
endSelected() {
|
||||
if (this.getEnd().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
dueSelected() {
|
||||
if (this.getDue().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
memberSelected() {
|
||||
if (this.getMembers().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
labelSelected() {
|
||||
if (this.getLabels().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
assigneeSelected() {
|
||||
if (this.getAssignees().length === 0) {
|
||||
return false;
|
||||
|
@ -372,6 +456,22 @@ Template.cardDetails.helpers({
|
|||
}
|
||||
},
|
||||
|
||||
requestBySelected() {
|
||||
if (this.getRequestBy().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
assigneeBySelected() {
|
||||
if (this.getAssigneeBy().length === 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
memberType() {
|
||||
const user = Users.findOne(this.userId);
|
||||
return user && user.isBoardAdmin() ? 'admin' : 'normal';
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
|
||||
avatar-radius = 50%
|
||||
|
||||
#cardURL_copy
|
||||
// Have clipboard text not visible by moving it to far left
|
||||
position: absolute
|
||||
left: -2000px
|
||||
top: 0px
|
||||
|
||||
.assignee
|
||||
border-radius: 3px
|
||||
display: block
|
||||
|
@ -107,7 +113,11 @@ avatar-radius = 50%
|
|||
border-bottom: 1px solid darken(white, 14%)
|
||||
|
||||
.close-card-details,
|
||||
.card-details-menu
|
||||
.card-details-menu,
|
||||
.card-copy-button,
|
||||
.card-copy-mobile-button,
|
||||
.close-card-details-mobile-web,
|
||||
.card-details-menu-mobile-web
|
||||
float: right
|
||||
|
||||
.close-card-details
|
||||
|
@ -115,10 +125,30 @@ avatar-radius = 50%
|
|||
padding: 5px
|
||||
margin-right: -8px
|
||||
|
||||
.close-card-details-mobile-web
|
||||
font-size: 24px
|
||||
padding: 5px
|
||||
margin-right: 40px
|
||||
|
||||
.card-copy-button
|
||||
font-size: 17px
|
||||
padding: 10px
|
||||
margin-right: 10px
|
||||
|
||||
.card-copy-mobile-button
|
||||
font-size: 17px
|
||||
padding: 10px
|
||||
margin-right: 10px
|
||||
|
||||
.card-details-menu
|
||||
font-size: 17px
|
||||
padding: 10px
|
||||
|
||||
.card-details-menu-mobile-web
|
||||
font-size: 17px
|
||||
padding: 10px
|
||||
margin-right: 30px
|
||||
|
||||
.card-details-watch
|
||||
font-size: 17px
|
||||
padding-left: 7px
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
template(name="checklists")
|
||||
h3 {{_ 'checklists'}}
|
||||
h3
|
||||
i.fa.fa-check
|
||||
| {{_ 'checklists'}}
|
||||
if toggleDeleteDialog.get
|
||||
.board-overlay#card-details-overlay
|
||||
+checklistDeleteDialog(checklist = checklistToDelete)
|
||||
|
|
|
@ -60,6 +60,9 @@ BlazeComponent.extendComponent({
|
|||
if ($itemsDom.data('sortable')) {
|
||||
$(self.itemsDom).sortable('option', 'disabled', !userIsMember());
|
||||
}
|
||||
if ($itemsDom.data('sortable')) {
|
||||
$(self.itemsDom).sortable('option', 'disabled', Utils.isMiniScreen());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -67,7 +70,8 @@ BlazeComponent.extendComponent({
|
|||
return (
|
||||
Meteor.user() &&
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly()
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
},
|
||||
}).register('checklistDetail');
|
||||
|
@ -120,7 +124,8 @@ BlazeComponent.extendComponent({
|
|||
return (
|
||||
Meteor.user() &&
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly()
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -228,7 +233,8 @@ Template.checklistItemDetail.helpers({
|
|||
return (
|
||||
Meteor.user() &&
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly()
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -4,8 +4,8 @@ template(name="minicard")
|
|||
class="{{#if isLinkedBoard}}linked-board{{/if}}"
|
||||
class="minicard-{{colorClass}}")
|
||||
if isMiniScreen
|
||||
.handle
|
||||
.fa.fa-arrows
|
||||
//.handle
|
||||
// .fa.fa-arrows
|
||||
unless isMiniScreen
|
||||
if showDesktopDragHandles
|
||||
.handle
|
||||
|
@ -67,14 +67,15 @@ template(name="minicard")
|
|||
.minicard-custom-fields
|
||||
each customFieldsWD
|
||||
if definition.showOnCard
|
||||
.minicard-custom-field
|
||||
if definition.showLabelOnMiniCard
|
||||
if trueValue
|
||||
.minicard-custom-field
|
||||
if definition.showLabelOnMiniCard
|
||||
.minicard-custom-field-item
|
||||
+viewer
|
||||
= definition.name
|
||||
.minicard-custom-field-item
|
||||
+viewer
|
||||
= definition.name
|
||||
.minicard-custom-field-item
|
||||
+viewer
|
||||
= trueValue
|
||||
= trueValue
|
||||
|
||||
if getAssignees
|
||||
.minicard-assignees.js-minicard-assignees
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Cookies } from 'meteor/ostrio:cookies';
|
||||
const cookies = new Cookies();
|
||||
// Template.cards.events({
|
||||
// 'click .member': Popup.open('cardMember')
|
||||
// });
|
||||
|
@ -18,7 +20,11 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
{
|
||||
'click .js-toggle-minicard-label-text'() {
|
||||
Meteor.call('toggleMinicardLabelText');
|
||||
if (cookies.has('hiddenMinicardLabelText')) {
|
||||
cookies.remove('hiddenMinicardLabelText'); //true
|
||||
} else {
|
||||
cookies.set('hiddenMinicardLabelText', 'true'); //true
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -27,10 +33,24 @@ BlazeComponent.extendComponent({
|
|||
|
||||
Template.minicard.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).showDesktopDragHandles;
|
||||
} else if (cookies.has('showDesktopDragHandles')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
hiddenMinicardLabelText() {
|
||||
return Meteor.user().hasHiddenMinicardLabelText();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).hiddenMinicardLabelText;
|
||||
} else if (cookies.has('hiddenMinicardLabelText')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
coverUrl() {
|
||||
return Attachments.findOne(this.coverId).link('original', '/');
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
template(name="subtasks")
|
||||
h3 {{_ 'subtasks'}}
|
||||
h3
|
||||
i.fa.fa-sitemap
|
||||
| {{_ 'subtasks'}}
|
||||
if toggleDeleteDialog.get
|
||||
.board-overlay#card-details-overlay
|
||||
+subtaskDeleteDialog(subtask = subtaskToDelete)
|
||||
|
|
|
@ -3,7 +3,8 @@ BlazeComponent.extendComponent({
|
|||
return (
|
||||
Meteor.user() &&
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly()
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
},
|
||||
}).register('subtaskDetail');
|
||||
|
@ -55,7 +56,8 @@ BlazeComponent.extendComponent({
|
|||
return (
|
||||
Meteor.user() &&
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly()
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -154,7 +156,8 @@ Template.subtaskItemDetail.helpers({
|
|||
return (
|
||||
Meteor.user() &&
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly()
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Cookies } from 'meteor/ostrio:cookies';
|
||||
const cookies = new Cookies();
|
||||
const { calculateIndex, enableClickOnTouch } = Utils;
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
|
@ -31,18 +33,6 @@ BlazeComponent.extendComponent({
|
|||
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
||||
const $cards = this.$('.js-minicards');
|
||||
|
||||
if (Utils.isMiniScreen) {
|
||||
$('.js-minicards').sortable({
|
||||
handle: '.handle',
|
||||
});
|
||||
}
|
||||
|
||||
if (!Utils.isMiniScreen && showDesktopDragHandles) {
|
||||
$('.js-minicards').sortable({
|
||||
handle: '.handle',
|
||||
});
|
||||
}
|
||||
|
||||
$cards.sortable({
|
||||
connectWith: '.js-minicards:not(.js-list-full)',
|
||||
tolerance: 'pointer',
|
||||
|
@ -85,16 +75,15 @@ BlazeComponent.extendComponent({
|
|||
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' ||
|
||||
Utils.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
|
||||
Utils.boardView() === 'board-view-lists' ||
|
||||
Utils.boardView() === 'board-view-cal' ||
|
||||
!Utils.boardView
|
||||
)
|
||||
swimlaneId = currentBoard.getDefaultSwimline()._id;
|
||||
|
||||
|
@ -128,9 +117,49 @@ BlazeComponent.extendComponent({
|
|||
// ugly touch event hotfix
|
||||
enableClickOnTouch(itemsSelector);
|
||||
|
||||
// Disable drag-dropping if the current user is not a board member or is comment only
|
||||
this.autorun(() => {
|
||||
$cards.sortable('option', 'disabled', !userIsMember());
|
||||
let showDesktopDragHandles = false;
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
showDesktopDragHandles = (currentUser.profile || {})
|
||||
.showDesktopDragHandles;
|
||||
} else if (cookies.has('showDesktopDragHandles')) {
|
||||
showDesktopDragHandles = true;
|
||||
} else {
|
||||
showDesktopDragHandles = false;
|
||||
}
|
||||
|
||||
if (!Utils.isMiniScreen() && showDesktopDragHandles) {
|
||||
$cards.sortable({
|
||||
handle: '.handle',
|
||||
});
|
||||
} else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
|
||||
$cards.sortable({
|
||||
handle: '.minicard',
|
||||
});
|
||||
}
|
||||
|
||||
if ($cards.data('sortable')) {
|
||||
$cards.sortable(
|
||||
'option',
|
||||
'disabled',
|
||||
// Disable drag-dropping when user is not member/is miniscreen
|
||||
!userIsMember(),
|
||||
// Not disable drag-dropping while in multi-selection mode
|
||||
// MultiSelection.isActive() || !userIsMember(),
|
||||
);
|
||||
}
|
||||
|
||||
if ($cards.data('sortable')) {
|
||||
$cards.sortable(
|
||||
'option',
|
||||
'disabled',
|
||||
// Disable drag-dropping when user is not member/is miniscreen
|
||||
Utils.isMiniScreen(),
|
||||
// Not disable drag-dropping while in multi-selection mode
|
||||
// MultiSelection.isActive() || !userIsMember(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// We want to re-run this function any time a card is added.
|
||||
|
@ -163,7 +192,14 @@ BlazeComponent.extendComponent({
|
|||
|
||||
Template.list.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).showDesktopDragHandles;
|
||||
} else if (cookies.has('showDesktopDragHandles')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -48,7 +48,6 @@ BlazeComponent.extendComponent({
|
|||
const board = this.data().board();
|
||||
let linkedId = '';
|
||||
let swimlaneId = '';
|
||||
const boardView = (Meteor.user().profile || {}).boardView;
|
||||
let cardType = 'cardType-card';
|
||||
if (title) {
|
||||
if (board.isTemplatesBoard()) {
|
||||
|
@ -71,14 +70,14 @@ BlazeComponent.extendComponent({
|
|||
});
|
||||
cardType = 'cardType-linkedBoard';
|
||||
}
|
||||
} else if (boardView === 'board-view-swimlanes')
|
||||
} else if (Utils.boardView() === 'board-view-swimlanes')
|
||||
swimlaneId = this.parentComponent()
|
||||
.parentComponent()
|
||||
.data()._id;
|
||||
else if (
|
||||
boardView === 'board-view-lists' ||
|
||||
boardView === 'board-view-cal' ||
|
||||
!boardView
|
||||
Utils.boardView() === 'board-view-lists' ||
|
||||
Utils.boardView() === 'board-view-cal' ||
|
||||
!Utils.boardView
|
||||
)
|
||||
swimlaneId = board.getDefaultSwimline()._id;
|
||||
|
||||
|
@ -157,9 +156,8 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
idOrNull(swimlaneId) {
|
||||
const currentUser = Meteor.user();
|
||||
if (
|
||||
(currentUser.profile || {}).boardView === 'board-view-swimlanes' ||
|
||||
Utils.boardView() === 'board-view-swimlanes' ||
|
||||
this.data()
|
||||
.board()
|
||||
.isTemplatesBoard()
|
||||
|
@ -191,7 +189,8 @@ BlazeComponent.extendComponent({
|
|||
!this.reachedWipLimit() &&
|
||||
Meteor.user() &&
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly()
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -397,10 +396,9 @@ BlazeComponent.extendComponent({
|
|||
'.js-swimlane',
|
||||
);
|
||||
this.swimlaneId = '';
|
||||
const boardView = (Meteor.user().profile || {}).boardView;
|
||||
if (boardView === 'board-view-swimlanes')
|
||||
if (Utils.boardView() === 'board-view-swimlanes')
|
||||
this.swimlaneId = Blaze.getData(swimlane[0])._id;
|
||||
else if (boardView === 'board-view-lists' || !boardView)
|
||||
else if (Utils.boardView() === 'board-view-lists' || !Utils.boardView)
|
||||
this.swimlaneId = Swimlanes.findOne({ boardId: this.boardId })._id;
|
||||
},
|
||||
|
||||
|
@ -580,7 +578,7 @@ BlazeComponent.extendComponent({
|
|||
const swimlane = $(Popup._getTopStack().openerElement).parents(
|
||||
'.js-swimlane',
|
||||
);
|
||||
if ((Meteor.user().profile || {}).boardView === 'board-view-swimlanes')
|
||||
if (Utils.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
|
||||
|
@ -709,22 +707,18 @@ BlazeComponent.extendComponent({
|
|||
if (isSandstorm) {
|
||||
const user = Meteor.user();
|
||||
if (user) {
|
||||
const boardView = (Meteor.user().profile || {}).boardView;
|
||||
if (boardView === 'board-view-swimlanes') {
|
||||
if (Utils.boardView() === 'board-view-swimlanes') {
|
||||
this.swimlaneId = this.parentComponent()
|
||||
.parentComponent()
|
||||
.parentComponent()
|
||||
.data()._id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const boardView = (Meteor.user().profile || {}).boardView;
|
||||
if (boardView === 'board-view-swimlanes') {
|
||||
this.swimlaneId = this.parentComponent()
|
||||
.parentComponent()
|
||||
.parentComponent()
|
||||
.data()._id;
|
||||
}
|
||||
} else if (Utils.boardView() === 'board-view-swimlanes') {
|
||||
this.swimlaneId = this.parentComponent()
|
||||
.parentComponent()
|
||||
.parentComponent()
|
||||
.data()._id;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ template(name="listHeader")
|
|||
a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
|
||||
h2.list-header-name(
|
||||
title="{{ moment modifiedAt 'LLL' }}"
|
||||
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}js-open-inlined-form is-editable{{/unless}}{{/if}}")
|
||||
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
|
||||
+viewer
|
||||
= title
|
||||
if wipLimit.enabled
|
||||
|
@ -30,17 +30,17 @@ template(name="listHeader")
|
|||
if canSeeAddCard
|
||||
a.js-add-card.fa.fa-plus.list-header-plus-icon
|
||||
a.fa.fa-navicon.js-open-list-menu
|
||||
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
|
||||
//a.list-header-handle.handle.fa.fa-arrows.js-list-handle
|
||||
else
|
||||
a.list-header-menu-icon.fa.fa-angle-right.js-select-list
|
||||
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
|
||||
//a.list-header-handle.handle.fa.fa-arrows.js-list-handle
|
||||
else if currentUser.isBoardMember
|
||||
if isWatching
|
||||
i.list-header-watch-icon.fa.fa-eye
|
||||
div.list-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
if isBoardAdmin
|
||||
a.fa.js-list-star.list-header-plus-icon(class="fa-star{{#unless starred}}-o{{/unless}}")
|
||||
//if isBoardAdmin
|
||||
// a.fa.js-list-star.list-header-plus-icon(class="fa-star{{#unless starred}}-o{{/unless}}")
|
||||
if canSeeAddCard
|
||||
a.js-add-card.fa.fa-plus.list-header-plus-icon
|
||||
a.fa.fa-navicon.js-open-list-menu
|
||||
|
@ -56,25 +56,47 @@ template(name="editListTitleForm")
|
|||
|
||||
template(name="listActionPopup")
|
||||
ul.pop-over-list
|
||||
li: a.js-toggle-watch-list {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
|
||||
li
|
||||
a.js-toggle-watch-list
|
||||
if isWatching
|
||||
i.fa.fa-eye
|
||||
| {{_ 'unwatch'}}
|
||||
else
|
||||
i.fa.fa-eye-slash
|
||||
| {{_ 'watch'}}
|
||||
unless currentUser.isCommentOnly
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-set-color-list {{_ 'set-color-list'}}
|
||||
hr
|
||||
unless currentUser.isWorker
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-set-color-list
|
||||
i.fa.fa-paint-brush
|
||||
| {{_ 'set-color-list'}}
|
||||
ul.pop-over-list
|
||||
if cards.count
|
||||
li: a.js-select-cards {{_ 'list-select-cards'}}
|
||||
hr
|
||||
li
|
||||
a.js-select-cards
|
||||
i.fa.fa-check-square
|
||||
| {{_ 'list-select-cards'}}
|
||||
if currentUser.isBoardAdmin
|
||||
ul.pop-over-list
|
||||
li: a.js-set-wip-limit {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}}
|
||||
li
|
||||
a.js-set-wip-limit
|
||||
i.fa.fa-ban
|
||||
| {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}}
|
||||
unless currentUser.isWorker
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-close-list {{_ 'archive-list'}}
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-close-list
|
||||
i.fa.fa-arrow-right
|
||||
i.fa.fa-archive
|
||||
| {{_ 'archive-list'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-more {{_ 'listMorePopup-title'}}
|
||||
li
|
||||
a.js-more
|
||||
i.fa.fa-link
|
||||
| {{_ 'listMorePopup-title'}}
|
||||
|
||||
template(name="boardLists")
|
||||
ul.pop-over-list
|
||||
|
@ -94,7 +116,8 @@ template(name="listMorePopup")
|
|||
input.inline-input(type="text" readonly value="{{ rootUrl }}")
|
||||
| {{_ 'added'}}
|
||||
span.date(title=list.createdAt) {{ moment createdAt 'LLL' }}
|
||||
a.js-delete {{_ 'delete'}}
|
||||
unless currentUser.isWorker
|
||||
a.js-delete {{_ 'delete'}}
|
||||
|
||||
template(name="listDeletePopup")
|
||||
p {{_ "list-delete-pop"}}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Cookies } from 'meteor/ostrio:cookies';
|
||||
const cookies = new Cookies();
|
||||
let listsColors;
|
||||
Meteor.startup(() => {
|
||||
listsColors = Lists.simpleSchema()._schema.color.allowedValues;
|
||||
|
@ -7,9 +9,10 @@ BlazeComponent.extendComponent({
|
|||
canSeeAddCard() {
|
||||
const list = Template.currentData();
|
||||
return (
|
||||
!list.getWipLimit('enabled') ||
|
||||
list.getWipLimit('soft') ||
|
||||
!this.reachedWipLimit()
|
||||
(!list.getWipLimit('enabled') ||
|
||||
list.getWipLimit('soft') ||
|
||||
!this.reachedWipLimit()) &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -44,14 +47,18 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
|
||||
limitToShowCardsCount() {
|
||||
return Meteor.user().getLimitToShowCardsCount();
|
||||
const currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return Meteor.user().getLimitToShowCardsCount();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
cardsCount() {
|
||||
const list = Template.currentData();
|
||||
let swimlaneId = '';
|
||||
const boardView = (Meteor.user().profile || {}).boardView;
|
||||
if (boardView === 'board-view-swimlanes')
|
||||
if (Utils.boardView() === 'board-view-swimlanes')
|
||||
swimlaneId = this.parentComponent()
|
||||
.parentComponent()
|
||||
.data()._id;
|
||||
|
@ -100,7 +107,14 @@ BlazeComponent.extendComponent({
|
|||
|
||||
Template.listHeader.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).showDesktopDragHandles;
|
||||
} else if (cookies.has('showDesktopDragHandles')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -218,6 +218,9 @@
|
|||
padding: 10px
|
||||
margin: -10px 0 -10px -10px
|
||||
|
||||
.announcement .viewer
|
||||
display: inline-block
|
||||
|
||||
.announcement,
|
||||
.offline-warning
|
||||
width: 100%
|
||||
|
|
|
@ -1,29 +1,42 @@
|
|||
template(name="boardActions")
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-move-card-to'}}
|
||||
div.trigger-dropdown
|
||||
select(id="move-gen-action")
|
||||
option(value="top") {{_'r-top-of'}}
|
||||
option(value="bottom") {{_'r-bottom-of'}}
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-its-list'}}
|
||||
div.trigger-button.js-add-gen-move-action.js-goto-rules
|
||||
i.fa.fa-plus
|
||||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-move-card-to'}}
|
||||
div.trigger-dropdown
|
||||
select(id="move-spec-action")
|
||||
option(value="top") {{_'r-top-of'}}
|
||||
option(value="bottom") {{_'r-bottom-of'}}
|
||||
div.trigger-text
|
||||
| {{_'r-list'}}
|
||||
div.trigger-text
|
||||
| {{_'r-the-board'}}
|
||||
div.trigger-dropdown
|
||||
select(id="board-id")
|
||||
each boards
|
||||
if $eq _id currentBoard._id
|
||||
option(value="{{_id}}" selected) {{_ 'current'}}
|
||||
else
|
||||
option(value="{{_id}}") {{title}}
|
||||
div.trigger-text
|
||||
| {{_'r-in-list'}}
|
||||
div.trigger-dropdown
|
||||
input(id="listName",type=text,placeholder="{{_'r-name'}}")
|
||||
div.trigger-text
|
||||
| {{_'r-in-swimlane'}}
|
||||
div.trigger-dropdown
|
||||
input(id="swimlaneName",type=text,placeholder="{{_'r-name'}}")
|
||||
div.trigger-button.js-add-spec-move-action.js-goto-rules
|
||||
i.fa.fa-plus
|
||||
|
||||
|
@ -33,14 +46,14 @@ template(name="boardActions")
|
|||
select(id="arch-action")
|
||||
option(value="archive") {{_'r-archive'}}
|
||||
option(value="unarchive") {{_'r-unarchive'}}
|
||||
div.trigger-text
|
||||
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
|
||||
div.trigger-text
|
||||
| {{_'r-add-swimlane'}}
|
||||
div.trigger-dropdown
|
||||
input(id="swimlane-name",type=text,placeholder="{{_'r-name'}}")
|
||||
|
@ -49,15 +62,15 @@ template(name="boardActions")
|
|||
|
||||
div.trigger-item
|
||||
div.trigger-content
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-create-card'}}
|
||||
div.trigger-dropdown
|
||||
input(id="card-name",type=text,placeholder="{{_'r-name'}}")
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-in-list'}}
|
||||
div.trigger-dropdown
|
||||
input(id="list-name",type=text,placeholder="{{_'r-name'}}")
|
||||
div.trigger-text
|
||||
div.trigger-text
|
||||
| {{_'r-in-swimlane'}}
|
||||
div.trigger-dropdown
|
||||
input(id="swimlane-name2",type=text,placeholder="{{_'r-name'}}")
|
||||
|
@ -65,8 +78,8 @@ template(name="boardActions")
|
|||
i.fa.fa-plus
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
BlazeComponent.extendComponent({
|
||||
onCreated() {},
|
||||
|
||||
boards() {
|
||||
const boards = Boards.find(
|
||||
{
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
_id: {
|
||||
$ne: Meteor.user().getTemplatesBoardId(),
|
||||
},
|
||||
},
|
||||
{
|
||||
sort: ['title'],
|
||||
},
|
||||
);
|
||||
return boards;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
|
@ -52,15 +68,18 @@ BlazeComponent.extendComponent({
|
|||
const ruleName = this.data().ruleName.get();
|
||||
const trigger = this.data().triggerVar.get();
|
||||
const actionSelected = this.find('#move-spec-action').value;
|
||||
const listTitle = this.find('#listName').value;
|
||||
const swimlaneName = this.find('#swimlaneName').value;
|
||||
const listName = this.find('#listName').value;
|
||||
const boardId = Session.get('currentBoard');
|
||||
const destBoardId = this.find('#board-id').value;
|
||||
const desc = Utils.getTriggerActionDesc(event, this);
|
||||
if (actionSelected === 'top') {
|
||||
const triggerId = Triggers.insert(trigger);
|
||||
const actionId = Actions.insert({
|
||||
actionType: 'moveCardToTop',
|
||||
listTitle,
|
||||
boardId,
|
||||
listName,
|
||||
swimlaneName,
|
||||
boardId: destBoardId,
|
||||
desc,
|
||||
});
|
||||
Rules.insert({
|
||||
|
@ -74,8 +93,9 @@ BlazeComponent.extendComponent({
|
|||
const triggerId = Triggers.insert(trigger);
|
||||
const actionId = Actions.insert({
|
||||
actionType: 'moveCardToBottom',
|
||||
listTitle,
|
||||
boardId,
|
||||
listName,
|
||||
swimlaneName,
|
||||
boardId: destBoardId,
|
||||
desc,
|
||||
});
|
||||
Rules.insert({
|
||||
|
|
|
@ -4,12 +4,16 @@ template(name='information')
|
|||
| {{_ 'error-notAuthorized'}}
|
||||
else
|
||||
.content-title
|
||||
span {{_ 'info'}}
|
||||
span
|
||||
i.fa.fa-info-circle
|
||||
| {{_ 'info'}}
|
||||
.content-body
|
||||
.side-menu
|
||||
ul
|
||||
li.active
|
||||
a.js-setting-menu(data-id="information-display") {{_ 'info'}}
|
||||
a.js-setting-menu(data-id="information-display")
|
||||
i.fa.fa-info-circle
|
||||
| {{_ 'info'}}
|
||||
.main-body
|
||||
+statistics
|
||||
|
||||
|
|
|
@ -5,16 +5,22 @@ template(name="people")
|
|||
else
|
||||
.content-title.ext-box
|
||||
.ext-box-left
|
||||
span {{_ 'people'}}
|
||||
span
|
||||
i.fa.fa-users
|
||||
| {{_ 'people'}}
|
||||
input#searchInput(placeholder="{{_ 'search'}}")
|
||||
button#searchButton {{_ 'search'}}
|
||||
button#searchButton
|
||||
i.fa.fa-search
|
||||
| {{_ 'search'}}
|
||||
.ext-box-right
|
||||
span {{_ 'people-number'}} #{peopleNumber}
|
||||
.content-body
|
||||
.side-menu
|
||||
ul
|
||||
li.active
|
||||
a.js-setting-menu(data-id="people-setting") {{_ 'people'}}
|
||||
a.js-setting-menu(data-id="people-setting")
|
||||
i.fa.fa-users
|
||||
| {{_ 'people'}}
|
||||
.main-body
|
||||
if loading.get
|
||||
+spinner
|
||||
|
@ -39,28 +45,58 @@ template(name="peopleGeneral")
|
|||
|
||||
template(name="peopleRow")
|
||||
tr
|
||||
td.username {{ userData.username }}
|
||||
td {{ userData.profile.fullname }}
|
||||
td
|
||||
if userData.isAdmin
|
||||
| {{_ 'yes'}}
|
||||
else
|
||||
| {{_ 'no'}}
|
||||
td {{ userData.emails.[0].address }}
|
||||
td
|
||||
if userData.emails.[0].verified
|
||||
| {{_ 'yes'}}
|
||||
else
|
||||
| {{_ 'no'}}
|
||||
td {{ moment userData.createdAt 'LLL' }}
|
||||
if userData.loginDisabled
|
||||
td.username <s>{{ userData.username }}</s>
|
||||
else
|
||||
td.username {{ userData.username }}
|
||||
if userData.loginDisabled
|
||||
td <s>{{ userData.profile.fullname }}</s>
|
||||
else
|
||||
td {{ userData.profile.fullname }}
|
||||
if userData.loginDisabled
|
||||
td
|
||||
if userData.isAdmin
|
||||
| <s>{{_ 'yes'}}</s>
|
||||
else
|
||||
| <s>{{_ 'no'}}</s>
|
||||
else
|
||||
td
|
||||
if userData.isAdmin
|
||||
| {{_ 'yes'}}
|
||||
else
|
||||
| {{_ 'no'}}
|
||||
if userData.loginDisabled
|
||||
td <s>{{ userData.emails.[0].address }}</s>
|
||||
else
|
||||
td {{ userData.emails.[0].address }}
|
||||
if userData.loginDisabled
|
||||
td
|
||||
if userData.emails.[0].verified
|
||||
| <s>{{_ 'yes'}}</s>
|
||||
else
|
||||
| <s>{{_ 'no'}}</s>
|
||||
else
|
||||
td
|
||||
if userData.emails.[0].verified
|
||||
| {{_ 'yes'}}
|
||||
else
|
||||
| {{_ 'no'}}
|
||||
if userData.loginDisabled
|
||||
td <s>{{ moment userData.createdAt 'LLL' }}</s>
|
||||
else
|
||||
td {{ moment userData.createdAt 'LLL' }}
|
||||
td
|
||||
if userData.loginDisabled
|
||||
| {{_ 'no'}}
|
||||
else
|
||||
| {{_ 'yes'}}
|
||||
td {{_ userData.authenticationMethod }}
|
||||
if userData.loginDisabled
|
||||
td <s>{{_ userData.authenticationMethod }}</s>
|
||||
else
|
||||
td {{_ userData.authenticationMethod }}
|
||||
td
|
||||
a.edit-user
|
||||
i.fa.fa-edit
|
||||
| {{_ 'edit'}}
|
||||
|
||||
template(name="editUserPopup")
|
||||
|
|
|
@ -33,7 +33,7 @@ table
|
|||
padding: 0;
|
||||
|
||||
button
|
||||
min-width: 60px;
|
||||
min-width: 90px;
|
||||
|
||||
.content-wrapper
|
||||
margin-top: 10px
|
||||
|
|
|
@ -4,22 +4,35 @@ template(name="setting")
|
|||
| {{_ 'error-notAuthorized'}}
|
||||
else
|
||||
.content-title
|
||||
i.fa.fa-cog
|
||||
span {{_ 'settings'}}
|
||||
.content-body
|
||||
.side-menu
|
||||
ul
|
||||
li.active
|
||||
a.js-setting-menu(data-id="registration-setting") {{_ 'registration'}}
|
||||
a.js-setting-menu(data-id="registration-setting")
|
||||
i.fa.fa-sign-in
|
||||
| {{_ 'registration'}}
|
||||
li
|
||||
a.js-setting-menu(data-id="email-setting") {{_ 'email'}}
|
||||
a.js-setting-menu(data-id="email-setting")
|
||||
i.fa.fa-envelope
|
||||
| {{_ 'email'}}
|
||||
li
|
||||
a.js-setting-menu(data-id="account-setting") {{_ 'accounts'}}
|
||||
a.js-setting-menu(data-id="account-setting")
|
||||
i.fa.fa-users
|
||||
| {{_ 'accounts'}}
|
||||
li
|
||||
a.js-setting-menu(data-id="announcement-setting") {{_ 'admin-announcement'}}
|
||||
a.js-setting-menu(data-id="announcement-setting")
|
||||
i.fa.fa-bullhorn
|
||||
| {{_ 'admin-announcement'}}
|
||||
li
|
||||
a.js-setting-menu(data-id="layout-setting") {{_ 'layout'}}
|
||||
a.js-setting-menu(data-id="layout-setting")
|
||||
i.fa.fa-object-group
|
||||
| {{_ 'layout'}}
|
||||
li
|
||||
a.js-setting-menu(data-id="webhook-setting") {{_ 'global-webhook'}}
|
||||
a.js-setting-menu(data-id="webhook-setting")
|
||||
i.fa.fa-globe
|
||||
| {{_ 'global-webhook'}}
|
||||
.main-body
|
||||
if loading.get
|
||||
+spinner
|
||||
|
@ -171,12 +184,6 @@ template(name='layoutSettings')
|
|||
.title {{_ 'custom-product-name'}}
|
||||
.form-group
|
||||
input.wekan-form-control#product-name(type="text", placeholder="" value="{{currentSetting.productName}}")
|
||||
li.layout-form
|
||||
.title {{_ 'add-custom-html-after-body-start'}}
|
||||
textarea#customHTMLafterBodyStart.wekan-form-control= currentSetting.customHTMLafterBodyStart
|
||||
li.layout-form
|
||||
.title {{_ 'add-custom-html-before-body-end'}}
|
||||
textarea#customHTMLbeforeBodyEnd.wekan-form-control= currentSetting.customHTMLbeforeBodyEnd
|
||||
li
|
||||
button.js-save-layout.primary {{_ 'save'}}
|
||||
|
||||
|
|
|
@ -171,20 +171,12 @@ BlazeComponent.extendComponent({
|
|||
const displayAuthenticationMethod =
|
||||
$('input[name=displayAuthenticationMethod]:checked').val() === 'true';
|
||||
const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
|
||||
const customHTMLafterBodyStart = $('#customHTMLafterBodyStart')
|
||||
.val()
|
||||
.trim();
|
||||
const customHTMLbeforeBodyEnd = $('#customHTMLbeforeBodyEnd')
|
||||
.val()
|
||||
.trim();
|
||||
|
||||
try {
|
||||
Settings.update(Settings.findOne()._id, {
|
||||
$set: {
|
||||
productName,
|
||||
hideLogo: hideLogoChange,
|
||||
customHTMLafterBodyStart,
|
||||
customHTMLbeforeBodyEnd,
|
||||
displayAuthenticationMethod,
|
||||
defaultAuthenticationMethod,
|
||||
},
|
||||
|
|
|
@ -41,15 +41,18 @@
|
|||
&:hover
|
||||
background #fff
|
||||
box-shadow 0 1px 2px rgba(0,0,0,0.15);
|
||||
|
||||
a
|
||||
@extends .flex
|
||||
padding: 1rem 0 1rem 1rem
|
||||
width: 100% - 5rem
|
||||
|
||||
|
||||
span
|
||||
font-size: 13px
|
||||
|
||||
i
|
||||
margin-right: 20px
|
||||
|
||||
.main-body
|
||||
padding: 0.1em 1em
|
||||
-webkit-user-select: text // Safari 3.1+
|
||||
|
|
|
@ -37,11 +37,12 @@ template(name='homeSidebar')
|
|||
template(name="membersWidget")
|
||||
.board-widget.board-widget-members
|
||||
h3
|
||||
i.fa.fa-user
|
||||
i.fa.fa-users
|
||||
| {{_ 'members'}}
|
||||
unless currentUser.isCommentOnly
|
||||
a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}").right
|
||||
i.board-header-btn-icon.fa.fa-cog
|
||||
unless currentUser.isWorker
|
||||
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
|
||||
|
@ -71,6 +72,108 @@ template(name="boardChangeColorPopup")
|
|||
if isSelected
|
||||
i.fa.fa-check
|
||||
|
||||
template(name="boardCardSettingsPopup")
|
||||
form.board-card-settings
|
||||
h3 {{_ 'show-on-card'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-receiveddate(class="{{#if allowsReceivedDate}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsReceivedDate}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-sign-out
|
||||
| {{_ 'card-received'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-startdate(class="{{#if allowsStartDate}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsStartDate}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-hourglass-start
|
||||
| {{_ 'card-start'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-duedate(class="{{#if allowsDueDate}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsDueDate}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-sign-in
|
||||
| {{_ 'card-due'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-enddate(class="{{#if allowsEndDate}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsEndDate}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-hourglass-end
|
||||
| {{_ 'card-end'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-members(class="{{#if allowsMembers}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsMembers}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-users
|
||||
| {{_ 'members'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-assignee(class="{{#if allowsAssignee}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsAssignee}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-user
|
||||
| {{_ 'assignee'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-assigned-by(class="{{#if allowsAssignedBy}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsAssignedBy}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-shopping-cart
|
||||
| {{_ 'assigned-by'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-requested-by(class="{{#if allowsRequestedBy}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsRequestedBy}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-user-plus
|
||||
| {{_ 'requested-by'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-labels(class="{{#if allowsLabels}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsLabels}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-tags
|
||||
| {{_ 'labels'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-description-title(class="{{#if allowsDescriptionTitle}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsDescriptionTitle}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-align-left
|
||||
| {{_ 'description'}}
|
||||
| {{_ 'title'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-description-text(class="{{#if allowsDescriptionText}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsDescriptionText}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-align-left
|
||||
| {{_ 'description'}}
|
||||
| {{_ 'custom-field-text'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-checklists(class="{{#if allowsChecklists}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsChecklists}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-check
|
||||
| {{_ 'checklists'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-subtasks(class="{{#if allowsSubtasks}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsSubtasks}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-sitemap
|
||||
| {{_ 'subtasks'}}
|
||||
div.check-div
|
||||
a.flex.js-field-has-attachments(class="{{#if allowsAttachments}}is-checked{{/if}}")
|
||||
.materialCheckBox(class="{{#if allowsAttachments}}is-checked{{/if}}")
|
||||
span
|
||||
i.fa.fa-paperclip
|
||||
| {{_ 'attachments'}}
|
||||
//div.check-div
|
||||
// a.flex.js-field-has-comments(class="{{#if allowsComments}}is-checked{{/if}}")
|
||||
// .materialCheckBox(class="{{#if allowsComments}}is-checked{{/if}}")
|
||||
// span
|
||||
// i.fa.fa-comment-o
|
||||
// | {{_ 'comment'}}
|
||||
//div.check-div
|
||||
// a.flex.js-field-has-activities(class="{{#if allowsActivities}}is-checked{{/if}}")
|
||||
// .materialCheckBox(class="{{#if allowsActivities}}is-checked{{/if}}")
|
||||
// span
|
||||
// i.fa.fa-history
|
||||
// | {{_ 'activities'}}
|
||||
|
||||
template(name="boardSubtaskSettingsPopup")
|
||||
form.board-subtask-settings
|
||||
h3 {{_ 'show-parent-in-minicard'}}
|
||||
|
@ -130,7 +233,9 @@ template(name="chooseBoardSource")
|
|||
|
||||
template(name="archiveBoardPopup")
|
||||
p {{_ 'close-board-pop'}}
|
||||
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
|
||||
button.js-confirm.negate.full(type="submit")
|
||||
i.fa.fa-archive
|
||||
| {{_ 'archive'}}
|
||||
|
||||
template(name="outgoingWebhooksPopup")
|
||||
each integrations
|
||||
|
@ -163,37 +268,88 @@ template(name="outgoingWebhooksPopup")
|
|||
template(name="boardMenuPopup")
|
||||
ul.pop-over-list
|
||||
li: a.js-custom-fields {{_ 'custom-fields'}}
|
||||
li: a.js-open-archives {{_ 'archived-items'}}
|
||||
li
|
||||
a.js-open-archives
|
||||
i.fa.fa-archive
|
||||
| {{_ 'archived-items'}}
|
||||
if currentUser.isBoardAdmin
|
||||
li: a.js-change-board-color {{_ 'board-change-color'}}
|
||||
li
|
||||
a.js-change-board-color
|
||||
i.fa.fa-paint-brush
|
||||
| {{_ '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'}}
|
||||
li
|
||||
a.js-change-language
|
||||
i.fa.fa-flag
|
||||
| {{_ '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'}}
|
||||
li
|
||||
a(href="{{exportUrl}}", download="{{exportFilename}}")
|
||||
i.fa.fa-share-alt
|
||||
| {{_ 'export-board'}}
|
||||
li
|
||||
a.js-outgoing-webhooks
|
||||
i.fa.fa-globe
|
||||
| {{_ 'outgoing-webhooks'}}
|
||||
li
|
||||
a.js-card-settings
|
||||
i.fa.fa-id-card-o
|
||||
| {{_ 'card-settings'}}
|
||||
li
|
||||
a.js-subtask-settings
|
||||
i.fa.fa-sitemap
|
||||
| {{_ 'subtask-settings'}}
|
||||
unless currentBoard.isTemplatesBoard
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-archive-board
|
||||
i.fa.fa-arrow-right
|
||||
i.fa.fa-archive
|
||||
| {{_ 'archive-board'}}
|
||||
|
||||
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'}}
|
||||
li
|
||||
a(href="{{exportUrl}}", download="{{exportFilename}}")
|
||||
i.fa.fa-share-alt
|
||||
i.fa.fa-sign-out
|
||||
| {{_ 'export-board'}}
|
||||
li
|
||||
a.js-import-board
|
||||
i.fa.fa-share-alt
|
||||
i.fa.fa-sign-in
|
||||
| {{_ 'import-board-c'}}
|
||||
li
|
||||
a.js-archive-board
|
||||
i.fa.fa-arrow-right
|
||||
i.fa.fa-archive
|
||||
| {{_ 'archive-board'}}
|
||||
li
|
||||
a.js-outgoing-webhooks
|
||||
i.fa.fa-globe
|
||||
| {{_ 'outgoing-webhooks'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-subtask-settings {{_ 'subtask-settings'}}
|
||||
li
|
||||
a.js-card-settings
|
||||
i.fa.fa-id-card-o
|
||||
| {{_ 'card-settings'}}
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-subtask-settings
|
||||
i.fa.fa-sitemap
|
||||
| {{_ 'subtask-settings'}}
|
||||
|
||||
template(name="labelsWidget")
|
||||
.board-widget.board-widget-labels
|
||||
|
@ -203,7 +359,7 @@ template(name="labelsWidget")
|
|||
.board-widget-content
|
||||
each currentBoard.labels
|
||||
a.card-label(class="card-label-{{color}}"
|
||||
class="{{#if currentUser.isNotCommentOnly}}js-label{{/if}}")
|
||||
class="{{#if currentUser.isNotCommentOnly}}{{#if currentUser.isNotWorker}}js-label{{/if}}{{/if}}")
|
||||
span.card-label-name
|
||||
+viewer
|
||||
= name
|
||||
|
@ -232,12 +388,12 @@ template(name="memberPopup")
|
|||
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'}}
|
||||
|
||||
unless currentUser.isWorker
|
||||
li
|
||||
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")
|
||||
p {{_ 'remove-member-pop' name=user.profile.fullname username=user.username boardTitle=board.title}}
|
||||
|
@ -301,6 +457,12 @@ template(name="changePermissionsPopup")
|
|||
if isCommentOnly
|
||||
i.fa.fa-check
|
||||
span.sub-name {{_ 'comment-only-desc'}}
|
||||
li
|
||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-worker{{/if}}")
|
||||
| {{_ 'worker'}}
|
||||
if isWorker
|
||||
i.fa.fa-check
|
||||
span.sub-name {{_ 'worker-desc'}}
|
||||
if isLastAdmin
|
||||
hr
|
||||
p.quiet.bottom {{_ 'last-admin-desc'}}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Cookies } from 'meteor/ostrio:cookies';
|
||||
const cookies = new Cookies();
|
||||
Sidebar = null;
|
||||
|
||||
const defaultView = 'home';
|
||||
|
@ -107,7 +109,14 @@ BlazeComponent.extendComponent({
|
|||
'click .js-toggle-sidebar': this.toggle,
|
||||
'click .js-back-home': this.setView,
|
||||
'click .js-toggle-minicard-label-text'() {
|
||||
Meteor.call('toggleMinicardLabelText');
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
Meteor.call('toggleMinicardLabelText');
|
||||
} else if (cookies.has('hiddenMinicardLabelText')) {
|
||||
cookies.remove('hiddenMinicardLabelText');
|
||||
} else {
|
||||
cookies.set('hiddenMinicardLabelText', 'true');
|
||||
}
|
||||
},
|
||||
'click .js-shortcuts'() {
|
||||
FlowRouter.go('shortcuts');
|
||||
|
@ -121,7 +130,14 @@ Blaze.registerHelper('Sidebar', () => Sidebar);
|
|||
|
||||
Template.homeSidebar.helpers({
|
||||
hiddenMinicardLabelText() {
|
||||
return Meteor.user().hasHiddenMinicardLabelText();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).hiddenMinicardLabelText;
|
||||
} else if (cookies.has('hiddenMinicardLabelText')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -145,10 +161,13 @@ Template.memberPopup.helpers({
|
|||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
const commentOnly = currentBoard.hasCommentOnly(this.userId);
|
||||
const noComments = currentBoard.hasNoComments(this.userId);
|
||||
const worker = currentBoard.hasWorker(this.userId);
|
||||
if (commentOnly) {
|
||||
return TAPi18n.__('comment-only').toLowerCase();
|
||||
} else if (noComments) {
|
||||
return TAPi18n.__('no-comments').toLowerCase();
|
||||
} else if (worker) {
|
||||
return TAPi18n.__('worker').toLowerCase();
|
||||
} else {
|
||||
return TAPi18n.__(type).toLowerCase();
|
||||
}
|
||||
|
@ -189,6 +208,7 @@ Template.boardMenuPopup.events({
|
|||
'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
|
||||
'click .js-import-board': Popup.open('chooseBoardSource'),
|
||||
'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
|
||||
'click .js-card-settings': Popup.open('boardCardSettings'),
|
||||
});
|
||||
|
||||
Template.boardMenuPopup.helpers({
|
||||
|
@ -251,6 +271,14 @@ Template.membersWidget.helpers({
|
|||
const user = Meteor.user();
|
||||
return user && user.isInvitedTo(Session.get('currentBoard'));
|
||||
},
|
||||
isWorker() {
|
||||
const user = Meteor.user();
|
||||
if (user) {
|
||||
return Meteor.call(Boards.hasWorker(user.memberId));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Template.membersWidget.events({
|
||||
|
@ -445,6 +473,10 @@ BlazeComponent.extendComponent({
|
|||
return this.currentBoard.allowsSubtasks;
|
||||
},
|
||||
|
||||
allowsReceivedDate() {
|
||||
return this.currentBoard.allowsReceivedDate;
|
||||
},
|
||||
|
||||
isBoardSelected() {
|
||||
return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
|
||||
},
|
||||
|
@ -558,6 +590,359 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
}).register('boardSubtaskSettingsPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
},
|
||||
|
||||
allowsReceivedDate() {
|
||||
return this.currentBoard.allowsReceivedDate;
|
||||
},
|
||||
|
||||
allowsStartDate() {
|
||||
return this.currentBoard.allowsStartDate;
|
||||
},
|
||||
|
||||
allowsDueDate() {
|
||||
return this.currentBoard.allowsDueDate;
|
||||
},
|
||||
|
||||
allowsEndDate() {
|
||||
return this.currentBoard.allowsEndDate;
|
||||
},
|
||||
|
||||
allowsSubtasks() {
|
||||
return this.currentBoard.allowsSubtasks;
|
||||
},
|
||||
|
||||
allowsMembers() {
|
||||
return this.currentBoard.allowsMembers;
|
||||
},
|
||||
|
||||
allowsAssignee() {
|
||||
return this.currentBoard.allowsAssignee;
|
||||
},
|
||||
|
||||
allowsAssignedBy() {
|
||||
return this.currentBoard.allowsAssignedBy;
|
||||
},
|
||||
|
||||
allowsRequestedBy() {
|
||||
return this.currentBoard.allowsRequestedBy;
|
||||
},
|
||||
|
||||
allowsLabels() {
|
||||
return this.currentBoard.allowsLabels;
|
||||
},
|
||||
|
||||
allowsChecklists() {
|
||||
return this.currentBoard.allowsChecklists;
|
||||
},
|
||||
|
||||
allowsAttachments() {
|
||||
return this.currentBoard.allowsAttachments;
|
||||
},
|
||||
|
||||
allowsComments() {
|
||||
return this.currentBoard.allowsComments;
|
||||
},
|
||||
|
||||
allowsDescriptionTitle() {
|
||||
return this.currentBoard.allowsDescriptionTitle;
|
||||
},
|
||||
|
||||
allowsDescriptionText() {
|
||||
return this.currentBoard.allowsDescriptionText;
|
||||
},
|
||||
|
||||
isBoardSelected() {
|
||||
return this.currentBoard.dateSettingsDefaultBoardID;
|
||||
},
|
||||
|
||||
isNullBoardSelected() {
|
||||
return (
|
||||
this.currentBoard.dateSettingsDefaultBoardId === null ||
|
||||
this.currentBoard.dateSettingsDefaultBoardId === 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.dateSettingsDefaultBoardId === this.currentData()._id
|
||||
);
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-field-has-receiveddate'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsReceivedDate = !this.currentBoard
|
||||
.allowsReceivedDate;
|
||||
this.currentBoard.setAllowsReceivedDate(
|
||||
this.currentBoard.allowsReceivedDate,
|
||||
);
|
||||
$(`.js-field-has-receiveddate ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsReceivedDate,
|
||||
);
|
||||
$('.js-field-has-receiveddate').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsReceivedDate,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-startdate'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsStartDate = !this.currentBoard
|
||||
.allowsStartDate;
|
||||
this.currentBoard.setAllowsStartDate(
|
||||
this.currentBoard.allowsStartDate,
|
||||
);
|
||||
$(`.js-field-has-startdate ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsStartDate,
|
||||
);
|
||||
$('.js-field-has-startdate').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsStartDate,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-enddate'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsEndDate = !this.currentBoard.allowsEndDate;
|
||||
this.currentBoard.setAllowsEndDate(this.currentBoard.allowsEndDate);
|
||||
$(`.js-field-has-enddate ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsEndDate,
|
||||
);
|
||||
$('.js-field-has-enddate').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsEndDate,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-duedate'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsDueDate = !this.currentBoard.allowsDueDate;
|
||||
this.currentBoard.setAllowsDueDate(this.currentBoard.allowsDueDate);
|
||||
$(`.js-field-has-duedate ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDueDate,
|
||||
);
|
||||
$('.js-field-has-duedate').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDueDate,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-subtasks'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks;
|
||||
this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks);
|
||||
$(`.js-field-has-subtasks ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsSubtasks,
|
||||
);
|
||||
$('.js-field-has-subtasks').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsSubtasks,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-members'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsMembers = !this.currentBoard.allowsMembers;
|
||||
this.currentBoard.setAllowsMembers(this.currentBoard.allowsMembers);
|
||||
$(`.js-field-has-members ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsMembers,
|
||||
);
|
||||
$('.js-field-has-members').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsMembers,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-assignee'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsAssignee = !this.currentBoard.allowsAssignee;
|
||||
this.currentBoard.setAllowsAssignee(this.currentBoard.allowsAssignee);
|
||||
$(`.js-field-has-assignee ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsAssignee,
|
||||
);
|
||||
$('.js-field-has-assignee').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsAssignee,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-assigned-by'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsAssignedBy = !this.currentBoard
|
||||
.allowsAssignedBy;
|
||||
this.currentBoard.setAllowsAssignedBy(
|
||||
this.currentBoard.allowsAssignedBy,
|
||||
);
|
||||
$(`.js-field-has-assigned-by ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsAssignedBy,
|
||||
);
|
||||
$('.js-field-has-assigned-by').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsAssignedBy,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-requested-by'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsRequestedBy = !this.currentBoard
|
||||
.allowsRequestedBy;
|
||||
this.currentBoard.setAllowsRequestedBy(
|
||||
this.currentBoard.allowsRequestedBy,
|
||||
);
|
||||
$(`.js-field-has-requested-by ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsRequestedBy,
|
||||
);
|
||||
$('.js-field-has-requested-by').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsRequestedBy,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-labels'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsLabels = !this.currentBoard.allowsLabels;
|
||||
this.currentBoard.setAllowsLabels(this.currentBoard.allowsLabels);
|
||||
$(`.js-field-has-labels ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsAssignee,
|
||||
);
|
||||
$('.js-field-has-labels').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsLabels,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-description-title'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsDescriptionTitle = !this.currentBoard
|
||||
.allowsDescriptionTitle;
|
||||
this.currentBoard.setAllowsDescriptionTitle(
|
||||
this.currentBoard.allowsDescriptionTitle,
|
||||
);
|
||||
$(`.js-field-has-description-title ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDescriptionTitle,
|
||||
);
|
||||
$('.js-field-has-description-title').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDescriptionTitle,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-description-text'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsDescriptionText = !this.currentBoard
|
||||
.allowsDescriptionText;
|
||||
this.currentBoard.setAllowsDescriptionText(
|
||||
this.currentBoard.allowsDescriptionText,
|
||||
);
|
||||
$(`.js-field-has-description-text ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDescriptionText,
|
||||
);
|
||||
$('.js-field-has-description-text').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsDescriptionText,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-checklists'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsChecklists = !this.currentBoard
|
||||
.allowsChecklists;
|
||||
this.currentBoard.setAllowsChecklists(
|
||||
this.currentBoard.allowsChecklists,
|
||||
);
|
||||
$(`.js-field-has-checklists ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsChecklists,
|
||||
);
|
||||
$('.js-field-has-checklists').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsChecklists,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-attachments'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsAttachments = !this.currentBoard
|
||||
.allowsAttachments;
|
||||
this.currentBoard.setAllowsAttachments(
|
||||
this.currentBoard.allowsAttachments,
|
||||
);
|
||||
$(`.js-field-has-attachments ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsAttachments,
|
||||
);
|
||||
$('.js-field-has-attachments').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsAttachments,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-comments'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsComments = !this.currentBoard.allowsComments;
|
||||
this.currentBoard.setAllowsComments(this.currentBoard.allowsComments);
|
||||
$(`.js-field-has-comments ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsComments,
|
||||
);
|
||||
$('.js-field-has-comments').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsComments,
|
||||
);
|
||||
},
|
||||
'click .js-field-has-activities'(evt) {
|
||||
evt.preventDefault();
|
||||
this.currentBoard.allowsActivities = !this.currentBoard
|
||||
.allowsActivities;
|
||||
this.currentBoard.setAllowsActivities(
|
||||
this.currentBoard.allowsActivities,
|
||||
);
|
||||
$(`.js-field-has-activities ${MCB}`).toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsActivities,
|
||||
);
|
||||
$('.js-field-has-activities').toggleClass(
|
||||
CKCLS,
|
||||
this.currentBoard.allowsActivities,
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('boardCardSettingsPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.error = new ReactiveVar('');
|
||||
|
@ -628,7 +1013,7 @@ BlazeComponent.extendComponent({
|
|||
}).register('addMemberPopup');
|
||||
|
||||
Template.changePermissionsPopup.events({
|
||||
'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only'(
|
||||
'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only, click .js-set-worker'(
|
||||
event,
|
||||
) {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
|
@ -638,11 +1023,13 @@ Template.changePermissionsPopup.events({
|
|||
'js-set-comment-only',
|
||||
);
|
||||
const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
|
||||
const isWorker = $(event.currentTarget).hasClass('js-set-worker');
|
||||
currentBoard.setMemberPermission(
|
||||
memberId,
|
||||
isAdmin,
|
||||
isNoComments,
|
||||
isCommentOnly,
|
||||
isWorker,
|
||||
);
|
||||
Popup.back(1);
|
||||
},
|
||||
|
@ -659,7 +1046,8 @@ Template.changePermissionsPopup.helpers({
|
|||
return (
|
||||
!currentBoard.hasAdmin(this.userId) &&
|
||||
!currentBoard.hasNoComments(this.userId) &&
|
||||
!currentBoard.hasCommentOnly(this.userId)
|
||||
!currentBoard.hasCommentOnly(this.userId) &&
|
||||
!currentBoard.hasWorker(this.userId)
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -679,6 +1067,13 @@ Template.changePermissionsPopup.helpers({
|
|||
);
|
||||
},
|
||||
|
||||
isWorker() {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
return (
|
||||
!currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
|
||||
);
|
||||
},
|
||||
|
||||
isLastAdmin() {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
return (
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
color: darken(white, 40%)
|
||||
|
||||
.board-sidebar
|
||||
width: 248px
|
||||
width: 548px
|
||||
right: -@width
|
||||
transition: top .1s, right .1s, width .1s
|
||||
|
||||
|
|
|
@ -2,54 +2,60 @@ template(name="archivesSidebar")
|
|||
if isArchiveReady.get
|
||||
+basicTabs(tabs=tabs)
|
||||
+tabContent(slug="cards")
|
||||
p.quiet
|
||||
a.js-restore-all-cards {{_ 'restore-all'}}
|
||||
| -
|
||||
a.js-delete-all-cards {{_ 'delete-all'}}
|
||||
unless isWorker
|
||||
p.quiet
|
||||
a.js-restore-all-cards {{_ 'restore-all'}}
|
||||
| -
|
||||
a.js-delete-all-cards {{_ 'delete-all'}}
|
||||
each archivedCards
|
||||
.minicard-wrapper.js-minicard
|
||||
+minicard(this)
|
||||
if currentUser.isBoardMember
|
||||
p.quiet
|
||||
a.js-restore-card {{_ 'restore'}}
|
||||
| -
|
||||
a.js-delete-card {{_ 'delete'}}
|
||||
unless isWorker
|
||||
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'}}
|
||||
|
||||
+tabContent(slug="lists")
|
||||
p.quiet
|
||||
a.js-restore-all-lists {{_ 'restore-all'}}
|
||||
| -
|
||||
a.js-delete-all-lists {{_ 'delete-all'}}
|
||||
unless isWorker
|
||||
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'}}
|
||||
unless isWorker
|
||||
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'}}
|
||||
unless isWorker
|
||||
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'}}
|
||||
unless isWorker
|
||||
p.quiet
|
||||
a.js-restore-swimlane {{_ 'restore'}}
|
||||
| -
|
||||
a.js-delete-swimlane {{_ 'delete'}}
|
||||
else
|
||||
li.no-items-message {{_ 'no-archived-swimlanes'}}
|
||||
else
|
||||
|
|
|
@ -139,3 +139,12 @@ BlazeComponent.extendComponent({
|
|||
];
|
||||
},
|
||||
}).register('archivesSidebar');
|
||||
|
||||
Template.archivesSidebar.helpers({
|
||||
isWorker() {
|
||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||
return (
|
||||
!currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -117,13 +117,14 @@ template(name="multiselectionSidebar")
|
|||
i.fa.fa-check
|
||||
else if someSelectedElementHave 'member' _id
|
||||
i.fa.fa-ellipsis-h
|
||||
hr
|
||||
a.sidebar-btn.js-move-selection
|
||||
i.fa.fa-share
|
||||
span {{_ 'move-selection'}}
|
||||
a.sidebar-btn.js-archive-selection
|
||||
i.fa.fa-archive
|
||||
span {{_ 'archive-selection'}}
|
||||
unless currentUser.isWorker
|
||||
hr
|
||||
a.sidebar-btn.js-move-selection
|
||||
i.fa.fa-share
|
||||
span {{_ 'move-selection'}}
|
||||
a.sidebar-btn.js-archive-selection
|
||||
i.fa.fa-archive
|
||||
span {{_ 'archive-selection'}}
|
||||
|
||||
template(name="disambiguateMultiLabelPopup")
|
||||
p {{_ 'what-to-do'}}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Cookies } from 'meteor/ostrio:cookies';
|
||||
const cookies = new Cookies();
|
||||
const { calculateIndexData } = Utils;
|
||||
|
||||
let swimlaneColors;
|
||||
|
@ -30,7 +32,14 @@ BlazeComponent.extendComponent({
|
|||
|
||||
Template.swimlaneHeader.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).showDesktopDragHandles;
|
||||
} else if (cookies.has('showDesktopDragHandles')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
template(name="swimlane")
|
||||
.swimlane
|
||||
+swimlaneHeader
|
||||
.swimlane.js-lists.js-swimlane
|
||||
if isMiniScreen
|
||||
if currentListIsInThisSwimlane _id
|
||||
+list(currentList)
|
||||
unless currentList
|
||||
unless collapseSwimlane
|
||||
.swimlane.js-lists.js-swimlane
|
||||
if isMiniScreen
|
||||
if currentListIsInThisSwimlane _id
|
||||
+list(currentList)
|
||||
unless currentList
|
||||
each lists
|
||||
+miniList(this)
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
else
|
||||
each lists
|
||||
+miniList(this)
|
||||
+list(this)
|
||||
if currentCardIsInThisList _id ../_id
|
||||
+cardDetails(currentCard)
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
else
|
||||
each lists
|
||||
+list(this)
|
||||
if currentCardIsInThisList _id ../_id
|
||||
+cardDetails(currentCard)
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isCommentOnly
|
||||
+addListForm
|
||||
|
||||
template(name="listsGroup")
|
||||
.swimlane.list-group.js-lists
|
||||
|
@ -42,19 +43,20 @@ template(name="listsGroup")
|
|||
+addListForm
|
||||
|
||||
template(name="addListForm")
|
||||
.list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
|
||||
.list-header-add
|
||||
+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'}}
|
||||
else
|
||||
a.open-list-composer.js-open-inlined-form
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-list'}}
|
||||
unless currentUser.isWorker
|
||||
.list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
|
||||
.list-header-add
|
||||
+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'}}
|
||||
else
|
||||
a.open-list-composer.js-open-inlined-form
|
||||
i.fa.fa-plus
|
||||
| {{_ 'add-list'}}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Cookies } from 'meteor/ostrio:cookies';
|
||||
const cookies = new Cookies();
|
||||
const { calculateIndex, enableClickOnTouch } = Utils;
|
||||
|
||||
function currentListIsInThisSwimlane(swimlaneId) {
|
||||
|
@ -14,7 +16,7 @@ function currentCardIsInThisList(listId, swimlaneId) {
|
|||
if (
|
||||
currentUser &&
|
||||
currentUser.profile &&
|
||||
currentUser.profile.boardView === 'board-view-swimlanes'
|
||||
Utils.boardView() === 'board-view-swimlanes'
|
||||
)
|
||||
return (
|
||||
currentCard &&
|
||||
|
@ -53,18 +55,6 @@ function initSortable(boardComponent, $listsDom) {
|
|||
},
|
||||
};
|
||||
|
||||
if (Utils.isMiniScreen) {
|
||||
$listsDom.sortable({
|
||||
handle: '.js-list-handle',
|
||||
});
|
||||
}
|
||||
|
||||
if (!Utils.isMiniScreen && showDesktopDragHandles) {
|
||||
$listsDom.sortable({
|
||||
handle: '.js-list-header',
|
||||
});
|
||||
}
|
||||
|
||||
$listsDom.sortable({
|
||||
tolerance: 'pointer',
|
||||
helper: 'clone',
|
||||
|
@ -104,19 +94,64 @@ function initSortable(boardComponent, $listsDom) {
|
|||
return (
|
||||
Meteor.user() &&
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly()
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
}
|
||||
|
||||
// Disable drag-dropping while in multi-selection mode, or if the current user
|
||||
// is not a board member
|
||||
boardComponent.autorun(() => {
|
||||
let showDesktopDragHandles = false;
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
showDesktopDragHandles = (currentUser.profile || {})
|
||||
.showDesktopDragHandles;
|
||||
} else if (cookies.has('showDesktopDragHandles')) {
|
||||
showDesktopDragHandles = true;
|
||||
} else {
|
||||
showDesktopDragHandles = false;
|
||||
}
|
||||
|
||||
if (!Utils.isMiniScreen() && showDesktopDragHandles) {
|
||||
$listsDom.sortable({
|
||||
handle: '.js-list-handle',
|
||||
});
|
||||
} else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
|
||||
$listsDom.sortable({
|
||||
handle: '.js-list-header',
|
||||
});
|
||||
}
|
||||
|
||||
const $listDom = $listsDom;
|
||||
if ($listDom.data('sortable')) {
|
||||
$listsDom.sortable(
|
||||
'option',
|
||||
'disabled',
|
||||
MultiSelection.isActive() || !userIsMember(),
|
||||
// Disable drag-dropping when user is not member/is worker/is miniscreen
|
||||
!userIsMember(),
|
||||
// Not disable drag-dropping while in multi-selection mode
|
||||
// MultiSelection.isActive() || !userIsMember(),
|
||||
);
|
||||
}
|
||||
|
||||
if ($listDom.data('sortable')) {
|
||||
$listsDom.sortable(
|
||||
'option',
|
||||
'disabled',
|
||||
// Disable drag-dropping when user is not member/is worker/is miniscreen
|
||||
Meteor.user().isWorker(),
|
||||
// Not disable drag-dropping while in multi-selection mode
|
||||
// MultiSelection.isActive() || !userIsMember(),
|
||||
);
|
||||
}
|
||||
|
||||
if ($listDom.data('sortable')) {
|
||||
$listsDom.sortable(
|
||||
'option',
|
||||
'disabled',
|
||||
// Disable drag-dropping when user is not member/is worker/is miniscreen
|
||||
Utils.isMiniScreen(),
|
||||
// Not disable drag-dropping while in multi-selection mode
|
||||
// MultiSelection.isActive() || !userIsMember(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -163,8 +198,20 @@ BlazeComponent.extendComponent({
|
|||
// the user will legitimately expect to be able to select some text with
|
||||
// his mouse.
|
||||
|
||||
let showDesktopDragHandles = false;
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
showDesktopDragHandles = (currentUser.profile || {})
|
||||
.showDesktopDragHandles;
|
||||
} else if (cookies.has('showDesktopDragHandles')) {
|
||||
showDesktopDragHandles = true;
|
||||
} else {
|
||||
showDesktopDragHandles = false;
|
||||
}
|
||||
|
||||
const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
|
||||
Util.isMiniScreen || (!Util.isMiniScreen && showDesktopDragHandles)
|
||||
Utils.isMiniScreen() ||
|
||||
(!Utils.isMiniScreen() && showDesktopDragHandles)
|
||||
? ['.js-list-handle', '.js-swimlane-header-handle']
|
||||
: ['.js-list-header'],
|
||||
);
|
||||
|
@ -245,13 +292,21 @@ BlazeComponent.extendComponent({
|
|||
|
||||
Template.swimlane.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).showDesktopDragHandles;
|
||||
} else if (cookies.has('showDesktopDragHandles')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
canSeeAddList() {
|
||||
return (
|
||||
Meteor.user() &&
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly()
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,41 @@
|
|||
@import 'nib'
|
||||
|
||||
/*
|
||||
// Minimize swimlanes start https://www.w3schools.com/howto/howto_js_accordion.asp
|
||||
|
||||
.accordion
|
||||
cursor: pointer
|
||||
width: 30px
|
||||
height: 20px
|
||||
border: none
|
||||
outline: none
|
||||
font-size: 18px
|
||||
transition: 0.4s
|
||||
padding-top: 0px
|
||||
margin-top: 0px
|
||||
|
||||
.accordion:after
|
||||
// Unicode triagle right:
|
||||
content: '\25B6'
|
||||
color: #777
|
||||
font-weight: bold
|
||||
float: left
|
||||
|
||||
.active:after
|
||||
// Unicode triangle down:
|
||||
content: '\25BC'
|
||||
|
||||
.panel
|
||||
width: 100%
|
||||
max-height: 0
|
||||
overflow: hidden
|
||||
transition: max-height 0.2s ease-out
|
||||
margin: 0px
|
||||
padding: 0px
|
||||
|
||||
// Minimize swimlanes end https://www.w3schools.com/howto/howto_js_accordion.asp
|
||||
*/
|
||||
|
||||
.swimlane
|
||||
// Even if this background color is the same as the body we can't leave it
|
||||
// transparent, because that won't work during a swimlane drag.
|
||||
|
@ -25,22 +61,22 @@
|
|||
cursor: grabbing
|
||||
|
||||
.swimlane-header-wrap
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1 0 100%;
|
||||
background-color: #ccc;
|
||||
display: flex
|
||||
flex-direction: row
|
||||
flex: 1 0 100%
|
||||
background-color: #ccc
|
||||
|
||||
.swimlane-header
|
||||
font-size: 14px;
|
||||
font-size: 14px
|
||||
padding: 5px 5px
|
||||
font-weight: bold;
|
||||
min-height: 9px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
-o-text-overflow: ellipsis;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
text-align: center;
|
||||
font-weight: bold
|
||||
min-height: 9px
|
||||
width: 100%
|
||||
overflow: hidden
|
||||
-o-text-overflow: ellipsis
|
||||
text-overflow: ellipsis
|
||||
word-wrap: break-word
|
||||
text-align: center
|
||||
|
||||
.swimlane-header-menu
|
||||
position: absolute
|
||||
|
|
|
@ -73,6 +73,7 @@ template(name="cardMemberPopup")
|
|||
p.quiet @{{ user.username }}
|
||||
ul.pop-over-list
|
||||
if currentUser.isNotCommentOnly
|
||||
if currentUser.isNotWorker
|
||||
li: a.js-remove-member {{_ 'remove-member-from-card'}}
|
||||
|
||||
if $eq currentUser._id user._id
|
||||
|
|
|
@ -13,21 +13,46 @@ template(name="headerUserBar")
|
|||
template(name="memberMenuPopup")
|
||||
ul.pop-over-list
|
||||
with currentUser
|
||||
li: a.js-edit-profile {{_ 'edit-profile'}}
|
||||
li: a.js-change-settings {{_ 'change-settings'}}
|
||||
li: a.js-change-avatar {{_ 'edit-avatar'}}
|
||||
li
|
||||
a.js-edit-profile
|
||||
i.fa.fa-user
|
||||
| {{_ 'edit-profile'}}
|
||||
li
|
||||
a.js-change-settings
|
||||
i.fa.fa-cog
|
||||
| {{_ 'change-settings'}}
|
||||
li
|
||||
a.js-change-avatar
|
||||
i.fa.fa-picture-o
|
||||
| {{_ 'edit-avatar'}}
|
||||
unless isSandstorm
|
||||
li: a.js-change-password {{_ 'changePasswordPopup-title'}}
|
||||
li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
|
||||
li
|
||||
a.js-change-password
|
||||
i.fa.fa-key
|
||||
| {{_ 'changePasswordPopup-title'}}
|
||||
li
|
||||
a.js-change-language
|
||||
i.fa.fa-flag
|
||||
| {{_ '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'}}
|
||||
li
|
||||
a.js-go-setting(href="{{pathFor 'setting'}}")
|
||||
i.fa.fa-lock
|
||||
| {{_ 'admin-panel'}}
|
||||
unless currentUser.isWorker
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li
|
||||
a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
|
||||
i.fa.fa-clone
|
||||
| {{_ 'templates'}}
|
||||
unless isSandstorm
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li: a.js-logout {{_ 'log-out'}}
|
||||
li
|
||||
a.js-logout
|
||||
i.fa.fa-sign-out
|
||||
| {{_ 'log-out'}}
|
||||
|
||||
template(name="editProfilePopup")
|
||||
form
|
||||
|
@ -75,21 +100,25 @@ template(name="changeSettingsPopup")
|
|||
ul.pop-over-list
|
||||
li
|
||||
a.js-toggle-system-messages
|
||||
i.fa.fa-comments-o
|
||||
| {{_ 'hide-system-messages'}}
|
||||
if hiddenSystemMessages
|
||||
i.fa.fa-check
|
||||
li
|
||||
a.js-toggle-desktop-drag-handles
|
||||
i.fa.fa-arrows
|
||||
| {{_ 'show-desktop-drag-handles'}}
|
||||
if showDesktopDragHandles
|
||||
i.fa.fa-check
|
||||
li
|
||||
label.bold
|
||||
| {{_ 'show-cards-minimum-count'}}
|
||||
input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="0" max="99" onkeydown="return false")
|
||||
input.js-apply-show-cards-at.left(type="submit" value="{{_ 'apply'}}")
|
||||
|
||||
unless currentUser.isWorker
|
||||
li
|
||||
label.bold
|
||||
i.fa.fa-sort-numeric-asc
|
||||
| {{_ 'show-cards-minimum-count'}}
|
||||
input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="0" max="99" onkeydown="return false")
|
||||
input.js-apply-show-cards-at.left(type="submit" value="{{_ 'apply'}}")
|
||||
|
||||
template(name="userDeletePopup")
|
||||
p {{_ 'delete-user-confirm-popup'}}
|
||||
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
|
||||
unless currentUser.isWorker
|
||||
p {{_ 'delete-user-confirm-popup'}}
|
||||
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import { Cookies } from 'meteor/ostrio:cookies';
|
||||
const cookies = new Cookies();
|
||||
|
||||
Template.headerUserBar.events({
|
||||
'click .js-open-header-member-menu': Popup.open('memberMenu'),
|
||||
'click .js-change-avatar': Popup.open('changeAvatar'),
|
||||
|
@ -5,10 +8,22 @@ Template.headerUserBar.events({
|
|||
|
||||
Template.memberMenuPopup.helpers({
|
||||
templatesBoardId() {
|
||||
return Meteor.user().getTemplatesBoardId();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return Meteor.user().getTemplatesBoardId();
|
||||
} else {
|
||||
// No need to getTemplatesBoardId on public board
|
||||
return false;
|
||||
}
|
||||
},
|
||||
templatesBoardSlug() {
|
||||
return Meteor.user().getTemplatesBoardSlug();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return Meteor.user().getTemplatesBoardSlug();
|
||||
} else {
|
||||
// No need to getTemplatesBoardSlug() on public board
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -30,13 +45,31 @@ Template.memberMenuPopup.events({
|
|||
|
||||
Template.editProfilePopup.helpers({
|
||||
allowEmailChange() {
|
||||
return AccountSettings.findOne('accounts-allowEmailChange').booleanValue;
|
||||
Meteor.call('AccountSettings.allowEmailChange', (_, result) => {
|
||||
if (result) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
allowUserNameChange() {
|
||||
return AccountSettings.findOne('accounts-allowUserNameChange').booleanValue;
|
||||
Meteor.call('AccountSettings.allowUserNameChange', (_, result) => {
|
||||
if (result) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
allowUserDelete() {
|
||||
return AccountSettings.findOne('accounts-allowUserDelete').booleanValue;
|
||||
Meteor.call('AccountSettings.allowUserDelete', (_, result) => {
|
||||
if (result) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -162,22 +195,55 @@ Template.changeLanguagePopup.events({
|
|||
|
||||
Template.changeSettingsPopup.helpers({
|
||||
showDesktopDragHandles() {
|
||||
return Meteor.user().hasShowDesktopDragHandles();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).showDesktopDragHandles;
|
||||
} else if (cookies.has('showDesktopDragHandles')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
hiddenSystemMessages() {
|
||||
return Meteor.user().hasHiddenSystemMessages();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).hasHiddenSystemMessages;
|
||||
} else if (cookies.has('hasHiddenSystemMessages')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
showCardsCountAt() {
|
||||
return Meteor.user().getLimitToShowCardsCount();
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return Meteor.user().getLimitToShowCardsCount();
|
||||
} else {
|
||||
return cookies.get('limitToShowCardsCount');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Template.changeSettingsPopup.events({
|
||||
'click .js-toggle-desktop-drag-handles'() {
|
||||
Meteor.call('toggleDesktopDragHandles');
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
Meteor.call('toggleDesktopDragHandles');
|
||||
} else if (cookies.has('showDesktopDragHandles')) {
|
||||
cookies.remove('showDesktopDragHandles');
|
||||
} else {
|
||||
cookies.set('showDesktopDragHandles', 'true');
|
||||
}
|
||||
},
|
||||
'click .js-toggle-system-messages'() {
|
||||
Meteor.call('toggleSystemMessages');
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
Meteor.call('toggleSystemMessages');
|
||||
} else if (cookies.has('hasHiddenSystemMessages')) {
|
||||
cookies.remove('hasHiddenSystemMessages');
|
||||
} else {
|
||||
cookies.set('hasHiddenSystemMessages', 'true');
|
||||
}
|
||||
},
|
||||
'click .js-apply-show-cards-at'(event, templateInstance) {
|
||||
event.preventDefault();
|
||||
|
@ -186,7 +252,12 @@ Template.changeSettingsPopup.events({
|
|||
10,
|
||||
);
|
||||
if (!isNaN(minLimit)) {
|
||||
Meteor.call('changeLimitToShowCardsCount', minLimit);
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
Meteor.call('changeLimitToShowCardsCount', minLimit);
|
||||
} else {
|
||||
cookies.set('limitToShowCardsCount', minLimit);
|
||||
}
|
||||
Popup.back();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -70,6 +70,30 @@ Mousetrap.bind('space', evt => {
|
|||
}
|
||||
});
|
||||
|
||||
// XXX This shortcut should also work when hovering over a card in board view
|
||||
Mousetrap.bind('c', evt => {
|
||||
if (!Session.get('currentCard')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentUserId = Meteor.userId();
|
||||
if (currentUserId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
Meteor.user().isBoardMember() &&
|
||||
!Meteor.user().isCommentOnly() &&
|
||||
!Meteor.user().isWorker()
|
||||
) {
|
||||
const card = Cards.findOne(Session.get('currentCard'));
|
||||
card.archive();
|
||||
// We should prevent scrolling in card when spacebar is clicked
|
||||
// This should do it according to Mousetrap docs, but it doesn't
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
Template.keyboardShortcuts.helpers({
|
||||
mapping: [
|
||||
{
|
||||
|
@ -104,5 +128,9 @@ Template.keyboardShortcuts.helpers({
|
|||
keys: ['SPACE'],
|
||||
action: 'shortcut-assign-self',
|
||||
},
|
||||
{
|
||||
keys: ['C'],
|
||||
action: 'archive-card',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -48,6 +48,11 @@ $.fn.escapeableTextComplete = function(strategies, options, ...otherArgs) {
|
|||
return this;
|
||||
};
|
||||
|
||||
EscapeActions.register('textcomplete', () => {}, () => dropdownMenuIsOpened, {
|
||||
noClickEscapeOn: '.textcomplete-dropdown',
|
||||
});
|
||||
EscapeActions.register(
|
||||
'textcomplete',
|
||||
() => {},
|
||||
() => dropdownMenuIsOpened,
|
||||
{
|
||||
noClickEscapeOn: '.textcomplete-dropdown',
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,4 +1,40 @@
|
|||
import { Cookies } from 'meteor/ostrio:cookies';
|
||||
const cookies = new Cookies();
|
||||
|
||||
Utils = {
|
||||
setBoardView(view) {
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
Meteor.user().setBoardView(view);
|
||||
} else if (view === 'board-view-lists') {
|
||||
cookies.set('boardView', 'board-view-lists'); //true
|
||||
} else if (view === 'board-view-swimlanes') {
|
||||
cookies.set('boardView', 'board-view-swimlanes'); //true
|
||||
} else if (view === 'board-view-cal') {
|
||||
cookies.set('boardView', 'board-view-cal'); //true
|
||||
}
|
||||
},
|
||||
|
||||
unsetBoardView() {
|
||||
cookies.remove('boardView');
|
||||
cookies.remove('collapseSwimlane');
|
||||
},
|
||||
|
||||
boardView() {
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).boardView;
|
||||
} else if (cookies.get('boardView') === 'board-view-lists') {
|
||||
return 'board-view-lists';
|
||||
} else if (cookies.get('boardView') === 'board-view-swimlanes') {
|
||||
return 'board-view-swimlanes';
|
||||
} else if (cookies.get('boardView') === 'board-view-cal') {
|
||||
return 'board-view-cal';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// XXX We should remove these two methods
|
||||
goBoardId(_id) {
|
||||
const board = Boards.findOne(_id);
|
||||
|
@ -117,8 +153,38 @@ Utils = {
|
|||
// in a small window (even on desktop), Wekan run in compact mode.
|
||||
// we can easily debug with a small window of desktop browser. :-)
|
||||
isMiniScreen() {
|
||||
// OLD WINDOW WIDTH DETECTION:
|
||||
this.windowResizeDep.depend();
|
||||
return $(window).width() <= 800;
|
||||
|
||||
// NEW TOUCH DEVICE DETECTION:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
|
||||
|
||||
/*
|
||||
var hasTouchScreen = false;
|
||||
if ("maxTouchPoints" in navigator) {
|
||||
hasTouchScreen = navigator.maxTouchPoints > 0;
|
||||
} else if ("msMaxTouchPoints" in navigator) {
|
||||
hasTouchScreen = navigator.msMaxTouchPoints > 0;
|
||||
} else {
|
||||
var mQ = window.matchMedia && matchMedia("(pointer:coarse)");
|
||||
if (mQ && mQ.media === "(pointer:coarse)") {
|
||||
hasTouchScreen = !!mQ.matches;
|
||||
} else if ('orientation' in window) {
|
||||
hasTouchScreen = true; // deprecated, but good fallback
|
||||
} else {
|
||||
// Only as a last resort, fall back to user agent sniffing
|
||||
var UA = navigator.userAgent;
|
||||
hasTouchScreen = (
|
||||
/\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
|
||||
/\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA)
|
||||
);
|
||||
}
|
||||
}
|
||||
*/
|
||||
//if (hasTouchScreen)
|
||||
// document.getElementById("exampleButton").style.padding="1em";
|
||||
//return false;
|
||||
},
|
||||
|
||||
calculateIndexData(prevData, nextData, nItems = 1) {
|
||||
|
|
|
@ -38,9 +38,13 @@ AccountsTemplates.configure({
|
|||
},
|
||||
});
|
||||
|
||||
['signIn', 'signUp', 'resetPwd', 'forgotPwd', 'enrollAccount'].forEach(
|
||||
routeName => AccountsTemplates.configureRoute(routeName),
|
||||
);
|
||||
[
|
||||
'signIn',
|
||||
'signUp',
|
||||
'resetPwd',
|
||||
'forgotPwd',
|
||||
'enrollAccount',
|
||||
].forEach(routeName => AccountsTemplates.configureRoute(routeName));
|
||||
|
||||
// We display the form to change the password in a popup window that already
|
||||
// have a title, so we unset the title automatically displayed by useraccounts.
|
||||
|
|
|
@ -93,14 +93,14 @@ services:
|
|||
#-------------------------------------------------------------------------------------
|
||||
# ==== MONGODB AND METEOR VERSION ====
|
||||
# a) For Wekan Meteor 1.8.x version at master branch, use mongo 4.x
|
||||
image: mongo:4.0.12
|
||||
image: mongo:4.2.2
|
||||
# b) For Wekan Meteor 1.6.x version at devel branch.
|
||||
# Only for Snap and Sandstorm while they are not upgraded yet to Meteor 1.8.x
|
||||
#image: mongo:3.2.21
|
||||
#-------------------------------------------------------------------------------------
|
||||
container_name: wekan-db
|
||||
restart: always
|
||||
command: mongod --smallfiles --oplogSize 128
|
||||
command: mongod --oplogSize 128
|
||||
networks:
|
||||
- wekan-tier
|
||||
expose:
|
||||
|
@ -238,7 +238,7 @@ services:
|
|||
#---------------------------------------------------------------
|
||||
# ==== RICH TEXT EDITOR IN CARD COMMENTS ====
|
||||
# https://github.com/wekan/wekan/pull/2560
|
||||
- RICHER_CARD_COMMENT_EDITOR=true
|
||||
- RICHER_CARD_COMMENT_EDITOR=false
|
||||
#---------------------------------------------------------------
|
||||
# ==== CARD OPENED, SEND WEBHOOK MESSAGE ====
|
||||
# https://github.com/wekan/wekan/issues/2518
|
||||
|
@ -342,6 +342,31 @@ services:
|
|||
# Tthe claim name you want to map to the email field:
|
||||
#- OAUTH2_EMAIL_MAP=email
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 Nextcloud ====
|
||||
# 1) Register the application with Nextcloud: https://your.nextcloud/settings/admin/security
|
||||
# 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://your-nextcloud.tld
|
||||
#- OAUTH2_AUTH_ENDPOINT=/index.php/apps/oauth2/authorize
|
||||
#- OAUTH2_USERINFO_ENDPOINT=/ocs/v2.php/cloud/user?format=json
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/index.php/apps/oauth2/api/v1/token
|
||||
# The claim name you want to map to the unique ID field:
|
||||
#- OAUTH2_ID_MAP=id
|
||||
# The claim name you want to map to the username field:
|
||||
#- OAUTH2_USERNAME_MAP=id
|
||||
# The claim name you want to map to the full name field:
|
||||
#- OAUTH2_FULLNAME_MAP=display-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
|
||||
|
@ -440,10 +465,10 @@ services:
|
|||
# If the sync of the users should be done in the background
|
||||
#- LDAP_BACKGROUND_SYNC=false
|
||||
#
|
||||
# At which interval does the background task sync in milliseconds.
|
||||
# Leave this unset, so it uses default, and does not crash.
|
||||
# https://github.com/wekan/wekan/issues/2354#issuecomment-515305722
|
||||
- LDAP_BACKGROUND_SYNC_INTERVAL=''
|
||||
# At which interval does the background task sync.
|
||||
# The format must be as specified in:
|
||||
# https://bunkat.github.io/later/parsers.html#text
|
||||
#- LDAP_BACKGROUND_SYNC_INTERVAL='every 1 hour'
|
||||
#
|
||||
#- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false
|
||||
#
|
||||
|
@ -559,7 +584,6 @@ services:
|
|||
# example : LOGOUT_ON_MINUTES=55
|
||||
#- LOGOUT_ON_MINUTES=
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
depends_on:
|
||||
- wekandb
|
||||
|
||||
|
|
|
@ -451,14 +451,14 @@ FS.HTTP.Handlers.Get = function (ref) {
|
|||
if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('trident') >= 0 || userAgent.indexOf('chrome') >= 0) {
|
||||
ref.filename = encodeURIComponent(ref.filename);
|
||||
} else if(userAgent.indexOf('firefox') >= 0) {
|
||||
ref.filename = new Buffer(ref.filename).toString('binary');
|
||||
ref.filename = Buffer.from(ref.filename).toString('binary');
|
||||
} else {
|
||||
/* safari*/
|
||||
ref.filename = new Buffer(ref.filename).toString('binary');
|
||||
}
|
||||
ref.filename = Buffer.from(ref.filename).toString('binary');
|
||||
}
|
||||
} catch (ex){
|
||||
ref.filename = 'tempfix';
|
||||
}
|
||||
}
|
||||
return originalHandler.call(this, ref);
|
||||
};
|
||||
// 221
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
dependencies:
|
||||
- name: mongodb-replicaset
|
||||
version: 3.6.x
|
||||
version: 3.11.x
|
||||
repository: "https://kubernetes-charts.storage.googleapis.com/"
|
||||
condition: mongodb-replicaset.enabled
|
||||
|
|
|
@ -75,7 +75,7 @@ else use user-provided URL.
|
|||
{{- if (index .Values "mongodb-replicaset" "enabled") -}}
|
||||
{{- $count := (int (index .Values "mongodb-replicaset" "replicas")) -}}
|
||||
{{- $release := .Release.Name -}}
|
||||
mongodb://{{- range $v := until $count }}{{ $release }}-mongodb-replicaset-{{ $v }}.{{ $release }}-mongodb-replicaset:27017{{ if ne $v (sub $count 1) }},{{- end -}}{{- end -}}?replicaSet={{ index .Values "mongodb-replicaset" "replicaSetName" }}
|
||||
mongodb://{{ $release }}-mongodb-replicaset:27017/admin?replicaSet={{ index .Values "mongodb-replicaset" "replicaSetName" }}
|
||||
{{- else -}}
|
||||
{{- index .Values "mongodb-replicaset" "url" -}}
|
||||
{{- end -}}
|
||||
|
|
|
@ -35,6 +35,6 @@ spec:
|
|||
- path: {{ $ingressPath }}
|
||||
backend:
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: http
|
||||
servicePort: 80
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
|
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "تعديل وضوح الرؤية",
|
||||
"boardChangeWatchPopup-title": "تغيير المتابعة",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boardChangeViewPopup-title": "عرض اللوحات",
|
||||
"boards": "لوحات",
|
||||
"board-view": "عرض اللوحات",
|
||||
"board-view-cal": "التقويم",
|
||||
"board-view-swimlanes": "خطوط السباحة",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "القائمات",
|
||||
"bucket-example": "مثل « todo list » على سبيل المثال",
|
||||
"cancel": "إلغاء",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "يمكن التعليق على بطاقات فقط.",
|
||||
"no-comments": "No comments",
|
||||
"no-comments-desc": "Can not see comments and activities.",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "حاسوب",
|
||||
"confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
|
||||
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Default",
|
||||
"queue": "Queue",
|
||||
"subtask-settings": "Subtasks Settings",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
|
||||
"show-subtasks-field": "Cards can have subtasks",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Deposit subtasks to this board:",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Show parent in minicard:",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Change Visibility",
|
||||
"boardChangeWatchPopup-title": "Промени наблюдаването",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boardChangeViewPopup-title": "Board View",
|
||||
"boards": "Табла",
|
||||
"board-view": "Board View",
|
||||
"board-view-cal": "Календар",
|
||||
"board-view-swimlanes": "Коридори",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "Списъци",
|
||||
"bucket-example": "Like “Bucket List” for example",
|
||||
"cancel": "Cancel",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Може да коментира само в карти.",
|
||||
"no-comments": "Няма коментари",
|
||||
"no-comments-desc": "Can not see comments and activities.",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "Компютър",
|
||||
"confirm-subtask-delete-dialog": "Сигурен ли сте, че искате да изтриете подзадачата?",
|
||||
"confirm-checklist-delete-dialog": "Сигурни ли сте, че искате да изтриете този чеклист?",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "по подразбиране",
|
||||
"queue": "Опашка",
|
||||
"subtask-settings": "Настройки на Подзадачите",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Настройки за Подзадачите за това Табло",
|
||||
"show-subtasks-field": "Картата може да има подзадачи",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Deposit subtasks to this board:",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Show parent in minicard:",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Change Visibility",
|
||||
"boardChangeWatchPopup-title": "Change Watch",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boardChangeViewPopup-title": "Board View",
|
||||
"boards": "Boards",
|
||||
"board-view": "Board View",
|
||||
"board-view-cal": "Calendar",
|
||||
"board-view-swimlanes": "Swimlanes",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "Lists",
|
||||
"bucket-example": "Like “Bucket List” for example",
|
||||
"cancel": "Cancel",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Can comment on cards only.",
|
||||
"no-comments": "No comments",
|
||||
"no-comments-desc": "Can not see comments and activities.",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "Computer",
|
||||
"confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
|
||||
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Default",
|
||||
"queue": "Queue",
|
||||
"subtask-settings": "Subtasks Settings",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
|
||||
"show-subtasks-field": "Cards can have subtasks",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Deposit subtasks to this board:",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Show parent in minicard:",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
"add-attachment": "Afegeix adjunt",
|
||||
"add-board": "Afegeix Tauler",
|
||||
"add-card": "Afegeix Fitxa",
|
||||
"add-swimlane": "Afegix Carril de Natació",
|
||||
"add-swimlane": "Afegeix carril de natació",
|
||||
"add-subtask": "Afegir Subtasca",
|
||||
"add-checklist": "Afegeix checklist",
|
||||
"add-checklist-item": "Afegeix un ítem al checklist",
|
||||
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Canvia visibilitat",
|
||||
"boardChangeWatchPopup-title": "Canvia seguiment",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boardChangeViewPopup-title": "Visió del tauler",
|
||||
"boards": "Taulers",
|
||||
"board-view": "Visió del tauler",
|
||||
"board-view-cal": "Calendari",
|
||||
"board-view-swimlanes": "Carrils de Natació",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "Llistes",
|
||||
"bucket-example": "Igual que “Bucket List”, per exemple",
|
||||
"cancel": "Cancel·la",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Només pots fer comentaris a les fitxes",
|
||||
"no-comments": "Sense comentaris",
|
||||
"no-comments-desc": "Can not see comments and activities.",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "Ordinador",
|
||||
"confirm-subtask-delete-dialog": "Esteu segur que voleu eliminar la subtasca?",
|
||||
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Default",
|
||||
"queue": "Queue",
|
||||
"subtask-settings": "Subtasks Settings",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
|
||||
"show-subtasks-field": "Cards can have subtasks",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Deposit subtasks to this board:",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Show parent in minicard:",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
"activity-checklist-completed-card": "dokončil(a) zaškrtávací seznam __checklist__ na kartě __card__ ve sloupci __list__ ve swimlane __swimlane__ na tablu __board__",
|
||||
"activity-checklist-uncompleted-card": "nedokončený seznam %s",
|
||||
"activity-editComment": "edited comment %s",
|
||||
"activity-deleteComment": "deleted comment %s",
|
||||
"activity-deleteComment": "smazat komentář %s",
|
||||
"add-attachment": "Přidat přílohu",
|
||||
"add-board": "Přidat tablo",
|
||||
"add-card": "Přidat kartu",
|
||||
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Upravit viditelnost",
|
||||
"boardChangeWatchPopup-title": "Změnit sledování",
|
||||
"boardMenuPopup-title": "Nastavení Tabla",
|
||||
"boardChangeViewPopup-title": "Náhled tabla",
|
||||
"boards": "Tabla",
|
||||
"board-view": "Náhled tabla",
|
||||
"board-view-cal": "Kalendář",
|
||||
"board-view-swimlanes": "Swimlanes",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "Sloupce",
|
||||
"bucket-example": "Například \"O čem sním\"",
|
||||
"cancel": "Zrušit",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Může přidávat komentáře pouze do karet.",
|
||||
"no-comments": "Žádné komentáře",
|
||||
"no-comments-desc": "Nemůže vidět komentáře a aktivity",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "Počítač",
|
||||
"confirm-subtask-delete-dialog": "Opravdu chcete smazat tento podúkol?",
|
||||
"confirm-checklist-delete-dialog": "Opravdu chcete smazat tento checklist?",
|
||||
|
@ -300,9 +304,9 @@
|
|||
"error-username-taken": "Toto uživatelské jméno již existuje",
|
||||
"error-email-taken": "Tento email byl již použit",
|
||||
"export-board": "Exportovat tablo",
|
||||
"sort": "Sort",
|
||||
"sort": "řadit",
|
||||
"sort-desc": "Click to Sort List",
|
||||
"list-sort-by": "Sort the List By:",
|
||||
"list-sort-by": "řadit seznam podle",
|
||||
"list-label-modifiedAt": "Last Access Time",
|
||||
"list-label-title": "Name of the List",
|
||||
"list-label-sort": "Your Manual Order",
|
||||
|
@ -532,7 +536,7 @@
|
|||
"no-name": "(Neznámé)",
|
||||
"Node_version": "Node verze",
|
||||
"Meteor_version": "Meteor version",
|
||||
"MongoDB_version": "MongoDB version",
|
||||
"MongoDB_version": "MongoDB verze",
|
||||
"MongoDB_storage_engine": "MongoDB storage engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
|
||||
"OS_Arch": "OS Architektura",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Výchozí",
|
||||
"queue": "Fronta",
|
||||
"subtask-settings": "Nastavení podúkolů",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Nastavení podúkolů tabla",
|
||||
"show-subtasks-field": "Karty mohou mít podúkoly",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Vložit podúkoly do tohoto tabla",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Ukázat předka na minikartě",
|
||||
|
@ -699,9 +704,9 @@
|
|||
"r-set": "Set",
|
||||
"r-update": "Update",
|
||||
"r-datefield": "date field",
|
||||
"r-df-start-at": "start",
|
||||
"r-df-start-at": "začátek",
|
||||
"r-df-due-at": "due",
|
||||
"r-df-end-at": "end",
|
||||
"r-df-end-at": "konec",
|
||||
"r-df-received-at": "received",
|
||||
"r-to-current-datetime": "to current date/time",
|
||||
"r-remove-value-from": "Remove value from",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Change Visibility",
|
||||
"boardChangeWatchPopup-title": "Change Watch",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boardChangeViewPopup-title": "Board View",
|
||||
"boards": "Boards",
|
||||
"board-view": "Board View",
|
||||
"board-view-cal": "Calendar",
|
||||
"board-view-swimlanes": "Swimlanes",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "Lists",
|
||||
"bucket-example": "Like “Bucket List” for example",
|
||||
"cancel": "Cancel",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Can comment on cards only.",
|
||||
"no-comments": "No comments",
|
||||
"no-comments-desc": "Can not see comments and activities.",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "Computer",
|
||||
"confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
|
||||
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Default",
|
||||
"queue": "Queue",
|
||||
"subtask-settings": "Subtasks Settings",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
|
||||
"show-subtasks-field": "Cards can have subtasks",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Deposit subtasks to this board:",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Show parent in minicard:",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Sichtbarkeit ändern",
|
||||
"boardChangeWatchPopup-title": "Beobachtung ändern",
|
||||
"boardMenuPopup-title": "Boardeinstellungen",
|
||||
"boardChangeViewPopup-title": "Boardansicht",
|
||||
"boards": "Boards",
|
||||
"board-view": "Boardansicht",
|
||||
"board-view-cal": "Kalender",
|
||||
"board-view-swimlanes": "Swimlanes",
|
||||
"board-view-collapse": "Einklappen",
|
||||
"board-view-lists": "Listen",
|
||||
"bucket-example": "z.B. \"Löffelliste\"",
|
||||
"cancel": "Abbrechen",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Kann Karten nur kommentieren.",
|
||||
"no-comments": "Keine Kommentare",
|
||||
"no-comments-desc": "Kann keine Kommentare und Aktivitäten sehen.",
|
||||
"worker": "Arbeiter",
|
||||
"worker-desc": "Kann Karten nur verschieben, sich selbst zuweisen und kommentieren.",
|
||||
"computer": "Computer",
|
||||
"confirm-subtask-delete-dialog": "Wollen Sie die Teilaufgabe wirklich löschen?",
|
||||
"confirm-checklist-delete-dialog": "Wollen Sie die Checkliste wirklich löschen?",
|
||||
|
@ -301,11 +305,11 @@
|
|||
"error-email-taken": "E-Mail wird schon verwendet",
|
||||
"export-board": "Board exportieren",
|
||||
"sort": "Sortieren",
|
||||
"sort-desc": "Zum sortieren der Liste, klicken",
|
||||
"sort-desc": "Zum Sortieren der Liste klicken",
|
||||
"list-sort-by": "Sortieren der Liste nach:",
|
||||
"list-label-modifiedAt": "Letzte Zugriffszeit",
|
||||
"list-label-title": "Name der Liste",
|
||||
"list-label-sort": "Deine manuelle Sortierung",
|
||||
"list-label-sort": "Ihre manuelle Sortierung",
|
||||
"list-label-short-modifiedAt": "(Z)",
|
||||
"list-label-short-title": "(N)",
|
||||
"list-label-short-sort": "(M)",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Standard",
|
||||
"queue": "Warteschlange",
|
||||
"subtask-settings": "Einstellungen für Teilaufgaben",
|
||||
"card-settings": "Karten-Einstellungen",
|
||||
"boardSubtaskSettingsPopup-title": "Boardeinstellungen für Teilaufgaben",
|
||||
"show-subtasks-field": "Karten können Teilaufgaben haben",
|
||||
"boardCardSettingsPopup-title": "Karten-Einstellungen",
|
||||
"deposit-subtasks-board": "Teilaufgaben in diesem Board ablegen:",
|
||||
"deposit-subtasks-list": "Zielliste für hier abgelegte Teilaufgaben:",
|
||||
"show-parent-in-minicard": "Übergeordnetes Element auf Minikarte anzeigen:",
|
||||
|
@ -738,16 +743,18 @@
|
|||
"almostdue": "aktuelles Fälligkeitsdatum %s bevorstehend",
|
||||
"pastdue": "aktuelles Fälligkeitsdatum %s überschritten",
|
||||
"duenow": "aktuelles Fälligkeitsdatum %s heute",
|
||||
"act-newDue": "__list__/__card__ hat seine 1. fällig Erinnerung [__board__]",
|
||||
"act-newDue": "__list__/__card__ hat seine 1. fällige Erinnerung [__board__]",
|
||||
"act-withDue": "Erinnerung an Fällikgeit von __card__ [__board__]",
|
||||
"act-almostdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist bevorstehend",
|
||||
"act-pastdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist vorbei",
|
||||
"act-duenow": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist jetzt",
|
||||
"act-atUserComment": "Du wurdest in [__board__] __list__/__card__ erwähnt",
|
||||
"act-atUserComment": "Sie wurden in [__board__] __list__/__card__ erwähnt",
|
||||
"delete-user-confirm-popup": "Sind Sie sicher, dass Sie diesen Account löschen wollen? Die Aktion kann nicht rückgängig gemacht werden.",
|
||||
"accounts-allowUserDelete": "Erlaube Benutzern ihren eigenen Account zu löschen",
|
||||
"hide-minicard-label-text": "Labeltext auf Minikarte ausblenden",
|
||||
"show-desktop-drag-handles": "Desktop-Ziehpunkte anzeigen",
|
||||
"assignee": "Zugewiesen",
|
||||
"cardAssigneesPopup-title": "Zugewiesen"
|
||||
"cardAssigneesPopup-title": "Zugewiesen",
|
||||
"addmore-detail": "Eine detailliertere Beschreibung hinzufügen",
|
||||
"show-on-card": "Zeige auf Karte"
|
||||
}
|
||||
|
|
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Αλλαγή Ορατότητας",
|
||||
"boardChangeWatchPopup-title": "Change Watch",
|
||||
"boardMenuPopup-title": "Ρυθμίσεις Πίνακα",
|
||||
"boardChangeViewPopup-title": "Board View",
|
||||
"boards": "Πίνακες",
|
||||
"board-view": "Board View",
|
||||
"board-view-cal": "Ημερολόγιο",
|
||||
"board-view-swimlanes": "Swimlanes",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "Λίστες",
|
||||
"bucket-example": "Like “Bucket List” for example",
|
||||
"cancel": "Ακύρωση",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Can comment on cards only.",
|
||||
"no-comments": "Χωρίς σχόλια",
|
||||
"no-comments-desc": "Can not see comments and activities.",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "Υπολογιστής",
|
||||
"confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
|
||||
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Default",
|
||||
"queue": "Queue",
|
||||
"subtask-settings": "Subtasks Settings",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
|
||||
"show-subtasks-field": "Cards can have subtasks",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Deposit subtasks to this board:",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Show parent in minicard:",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Change Visibility",
|
||||
"boardChangeWatchPopup-title": "Change Watch",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boardChangeViewPopup-title": "Board View",
|
||||
"boards": "Boards",
|
||||
"board-view": "Board View",
|
||||
"board-view-cal": "Calendar",
|
||||
"board-view-swimlanes": "Swimlanes",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "Lists",
|
||||
"bucket-example": "Like “Bucket List” for example",
|
||||
"cancel": "Cancel",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Can comment on cards only.",
|
||||
"no-comments": "No comments",
|
||||
"no-comments-desc": "Can not see comments and activities.",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "Computer",
|
||||
"confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
|
||||
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Default",
|
||||
"queue": "Queue",
|
||||
"subtask-settings": "Subtasks Settings",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
|
||||
"show-subtasks-field": "Cards can have subtasks",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Deposit subtasks to this board:",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Show parent in minicard:",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Change Visibility",
|
||||
"boardChangeWatchPopup-title": "Change Watch",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boardChangeViewPopup-title": "Board View",
|
||||
"boards": "Boards",
|
||||
"board-view": "Board View",
|
||||
"board-view-cal": "Calendar",
|
||||
"board-view-swimlanes": "Swimlanes",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "Lists",
|
||||
"bucket-example": "Like “Bucket List” for example",
|
||||
"cancel": "Cancel",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Can comment on cards only.",
|
||||
"no-comments": "No comments",
|
||||
"no-comments-desc": "Can not see comments and activities.",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "Computer",
|
||||
"confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
|
||||
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Default",
|
||||
"queue": "Queue",
|
||||
"subtask-settings": "Subtasks Settings",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
|
||||
"show-subtasks-field": "Cards can have subtasks",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Deposit subtasks to this board:",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Show parent in minicard:",
|
||||
|
@ -752,5 +757,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -128,14 +128,16 @@
|
|||
"board-private-info": "This board will be <strong>private</strong>.",
|
||||
"board-public-info": "This board will be <strong>public</strong>.",
|
||||
"boardChangeColorPopup-title": "Change Board Background",
|
||||
"boardChangeTitlePopup-title": "Rename Board",
|
||||
"boardChangeTitlePopup-title": "Renomi tavolon",
|
||||
"boardChangeVisibilityPopup-title": "Change Visibility",
|
||||
"boardChangeWatchPopup-title": "Change Watch",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boardChangeViewPopup-title": "Board View",
|
||||
"boards": "Boards",
|
||||
"board-view": "Board View",
|
||||
"board-view-cal": "Calendar",
|
||||
"board-view-swimlanes": "Swimlanes",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "Listoj",
|
||||
"bucket-example": "Like “Bucket List” for example",
|
||||
"cancel": "Cancel",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Can comment on cards only.",
|
||||
"no-comments": "No comments",
|
||||
"no-comments-desc": "Can not see comments and activities.",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "Komputilo",
|
||||
"confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
|
||||
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
|
||||
|
@ -246,8 +250,8 @@
|
|||
"custom-field-dropdown-options": "List Options",
|
||||
"custom-field-dropdown-options-placeholder": "Press enter to add more options",
|
||||
"custom-field-dropdown-unknown": "(unknown)",
|
||||
"custom-field-number": "Number",
|
||||
"custom-field-text": "Text",
|
||||
"custom-field-number": "Nombro",
|
||||
"custom-field-text": "Teksto",
|
||||
"custom-fields": "Custom Fields",
|
||||
"date": "Dato",
|
||||
"decline": "Decline",
|
||||
|
@ -323,13 +327,13 @@
|
|||
"filter-to-selection": "Filter to selection",
|
||||
"advanced-filter-label": "Advanced Filter",
|
||||
"advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i",
|
||||
"fullname": "Full Name",
|
||||
"fullname": "Plena nomo",
|
||||
"header-logo-title": "Go back to your boards page.",
|
||||
"hide-system-messages": "Hide system messages",
|
||||
"headerBarCreateBoardPopup-title": "Krei tavolon",
|
||||
"home": "Hejmo",
|
||||
"import": "Importi",
|
||||
"link": "Link",
|
||||
"link": "Ligilo",
|
||||
"import-board": "import board",
|
||||
"import-board-c": "Import board",
|
||||
"import-board-title-trello": "Import board from Trello",
|
||||
|
@ -431,11 +435,11 @@
|
|||
"remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.",
|
||||
"removeMemberPopup-title": "Remove Member?",
|
||||
"rename": "Renomi",
|
||||
"rename-board": "Rename Board",
|
||||
"rename-board": "Renomi tavolon",
|
||||
"restore": "Forigi",
|
||||
"save": "Savi",
|
||||
"search": "Serĉi",
|
||||
"rules": "Rules",
|
||||
"rules": "Reguloj",
|
||||
"search-cards": "Search from card/list titles and descriptions on this board",
|
||||
"search-example": "Text to search for?",
|
||||
"select-color": "Select Color",
|
||||
|
@ -470,7 +474,7 @@
|
|||
"title": "Titolo",
|
||||
"tracking": "Tracking",
|
||||
"tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.",
|
||||
"type": "Type",
|
||||
"type": "Tipo",
|
||||
"unassign-member": "Unassign member",
|
||||
"unsaved-description": "You have an unsaved description.",
|
||||
"unwatch": "Unwatch",
|
||||
|
@ -551,8 +555,8 @@
|
|||
"show-field-on-card": "Show this field on card",
|
||||
"automatically-field-on-card": "Auto create field to all cards",
|
||||
"showLabel-field-on-card": "Show field label on minicard",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"yes": "Jes",
|
||||
"no": "Ne",
|
||||
"accounts": "Accounts",
|
||||
"accounts-allowEmailChange": "Allow Email Change",
|
||||
"accounts-allowUserNameChange": "Allow Username Change",
|
||||
|
@ -576,11 +580,12 @@
|
|||
"boardDeletePopup-title": "Delete Board?",
|
||||
"delete-board": "Delete Board",
|
||||
"default-subtasks-board": "Subtasks for __board__ board",
|
||||
"default": "Default",
|
||||
"default": "Defaŭlto",
|
||||
"queue": "Queue",
|
||||
"subtask-settings": "Subtasks Settings",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
|
||||
"show-subtasks-field": "Cards can have subtasks",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Deposit subtasks to this board:",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Show parent in minicard:",
|
||||
|
@ -600,13 +605,13 @@
|
|||
"activity-delete-attach-card": "deleted an attachment",
|
||||
"activity-set-customfield": "set custom field '%s' to '%s' in %s",
|
||||
"activity-unset-customfield": "unset custom field '%s' in %s",
|
||||
"r-rule": "Rule",
|
||||
"r-rule": "Regulo",
|
||||
"r-add-trigger": "Add trigger",
|
||||
"r-add-action": "Add action",
|
||||
"r-board-rules": "Board rules",
|
||||
"r-add-rule": "Add rule",
|
||||
"r-add-rule": "Aldoni regulon",
|
||||
"r-view-rule": "View rule",
|
||||
"r-delete-rule": "Delete rule",
|
||||
"r-delete-rule": "Forigi regulon",
|
||||
"r-new-rule-name": "New rule title",
|
||||
"r-no-rules": "No rules",
|
||||
"r-when-a-card": "When a card",
|
||||
|
@ -615,7 +620,7 @@
|
|||
"r-added-to": "added to",
|
||||
"r-removed-from": "Removed from",
|
||||
"r-the-board": "the board",
|
||||
"r-list": "list",
|
||||
"r-list": "listo",
|
||||
"set-filter": "Set Filter",
|
||||
"r-moved-to": "Moved to",
|
||||
"r-moved-from": "Moved from",
|
||||
|
@ -627,7 +632,7 @@
|
|||
"r-list-name": "list name",
|
||||
"r-when-a-member": "When a member is",
|
||||
"r-when-the-member": "When the member",
|
||||
"r-name": "name",
|
||||
"r-name": "nomo",
|
||||
"r-when-a-attach": "When an attachment",
|
||||
"r-when-a-checklist": "When a checklist is",
|
||||
"r-when-the-checklist": "When the checklist",
|
||||
|
@ -645,12 +650,12 @@
|
|||
"r-unarchive": "Restore from Archive",
|
||||
"r-card": "card",
|
||||
"r-add": "Aldoni",
|
||||
"r-remove": "Remove",
|
||||
"r-remove": "Forigi",
|
||||
"r-label": "label",
|
||||
"r-member": "member",
|
||||
"r-member": "membro",
|
||||
"r-remove-all": "Remove all members from the card",
|
||||
"r-set-color": "Set color to",
|
||||
"r-checklist": "checklist",
|
||||
"r-checklist": "kontrololisto",
|
||||
"r-check-all": "Check all",
|
||||
"r-uncheck-all": "Uncheck all",
|
||||
"r-items-check": "items of checklist",
|
||||
|
@ -660,7 +665,7 @@
|
|||
"r-of-checklist": "of checklist",
|
||||
"r-send-email": "Send an email",
|
||||
"r-to": "to",
|
||||
"r-subject": "subject",
|
||||
"r-subject": "temo",
|
||||
"r-rule-details": "Rule details",
|
||||
"r-d-move-to-top-gen": "Move card to top of its list",
|
||||
"r-d-move-to-top-spec": "Move card to top of list",
|
||||
|
@ -668,8 +673,8 @@
|
|||
"r-d-move-to-bottom-spec": "Move card to bottom of list",
|
||||
"r-d-send-email": "Send email",
|
||||
"r-d-send-email-to": "to",
|
||||
"r-d-send-email-subject": "subject",
|
||||
"r-d-send-email-message": "message",
|
||||
"r-d-send-email-subject": "temo",
|
||||
"r-d-send-email-message": "mesaĝo",
|
||||
"r-d-archive": "Move card to Archive",
|
||||
"r-d-unarchive": "Restore card from Archive",
|
||||
"r-d-add-label": "Add label",
|
||||
|
@ -677,18 +682,18 @@
|
|||
"r-create-card": "Create new card",
|
||||
"r-in-list": "in list",
|
||||
"r-in-swimlane": "in swimlane",
|
||||
"r-d-add-member": "Add member",
|
||||
"r-d-remove-member": "Remove member",
|
||||
"r-d-remove-all-member": "Remove all member",
|
||||
"r-d-add-member": "Aldoni membron",
|
||||
"r-d-remove-member": "Forigi membron",
|
||||
"r-d-remove-all-member": "Forigi ĉiujn membrojn",
|
||||
"r-d-check-all": "Check all items of a list",
|
||||
"r-d-uncheck-all": "Uncheck all items of a list",
|
||||
"r-d-check-one": "Check item",
|
||||
"r-d-uncheck-one": "Uncheck item",
|
||||
"r-d-check-of-list": "of checklist",
|
||||
"r-d-add-checklist": "Add checklist",
|
||||
"r-d-remove-checklist": "Remove checklist",
|
||||
"r-d-add-checklist": "Aldoni kontrololiston",
|
||||
"r-d-remove-checklist": "Forigi kontrololiston",
|
||||
"r-by": "by",
|
||||
"r-add-checklist": "Add checklist",
|
||||
"r-add-checklist": "Aldoni kontrololiston",
|
||||
"r-with-items": "with items",
|
||||
"r-items-list": "item1,item2,item3",
|
||||
"r-add-swimlane": "Add swimlane",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Cambiar Visibilidad",
|
||||
"boardChangeWatchPopup-title": "Alternar Seguimiento",
|
||||
"boardMenuPopup-title": "Opciones del Tablero",
|
||||
"boardChangeViewPopup-title": "Vista de Tablero",
|
||||
"boards": "Tableros",
|
||||
"board-view": "Vista de Tablero",
|
||||
"board-view-cal": "Calendario",
|
||||
"board-view-swimlanes": "Calles",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "Listas",
|
||||
"bucket-example": "Como \"Lista de Contenedores\" por ejemplo",
|
||||
"cancel": "Cancelar",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Puede comentar en tarjetas solamente.",
|
||||
"no-comments": "Sin comentarios",
|
||||
"no-comments-desc": "Can not see comments and activities.",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "Computadora",
|
||||
"confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
|
||||
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Default",
|
||||
"queue": "Queue",
|
||||
"subtask-settings": "Subtasks Settings",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
|
||||
"show-subtasks-field": "Cards can have subtasks",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Deposit subtasks to this board:",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Show parent in minicard:",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Cambiar visibilidad",
|
||||
"boardChangeWatchPopup-title": "Cambiar vigilancia",
|
||||
"boardMenuPopup-title": "Preferencias del tablero",
|
||||
"boardChangeViewPopup-title": "Vista del tablero",
|
||||
"boards": "Tableros",
|
||||
"board-view": "Vista del tablero",
|
||||
"board-view-cal": "Calendario",
|
||||
"board-view-swimlanes": "Carriles",
|
||||
"board-view-collapse": "Contraer",
|
||||
"board-view-lists": "Listas",
|
||||
"bucket-example": "Como “Cosas por hacer” por ejemplo",
|
||||
"cancel": "Cancelar",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Solo puedes comentar en las tarjetas.",
|
||||
"no-comments": "No hay comentarios",
|
||||
"no-comments-desc": "No se pueden mostrar comentarios ni actividades.",
|
||||
"worker": "Trabajador",
|
||||
"worker-desc": "Solo puede mover tarjetas, asignarse a la tarjeta y comentar.",
|
||||
"computer": "el ordenador",
|
||||
"confirm-subtask-delete-dialog": "¿Seguro que quieres eliminar la subtarea?",
|
||||
"confirm-checklist-delete-dialog": "¿Seguro que quieres eliminar la lista de verificación?",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Por defecto",
|
||||
"queue": "Cola",
|
||||
"subtask-settings": "Preferencias de las subtareas",
|
||||
"card-settings": "Preferencias de la tarjeta",
|
||||
"boardSubtaskSettingsPopup-title": "Preferencias de las subtareas del tablero",
|
||||
"show-subtasks-field": "Las tarjetas pueden tener subtareas",
|
||||
"boardCardSettingsPopup-title": "Preferencias de la tarjeta",
|
||||
"deposit-subtasks-board": "Depositar subtareas en este tablero:",
|
||||
"deposit-subtasks-list": "Lista de destino para subtareas depositadas aquí:",
|
||||
"show-parent-in-minicard": "Mostrar el padre en una minitarjeta:",
|
||||
|
@ -748,6 +753,8 @@
|
|||
"accounts-allowUserDelete": "Permitir a los usuarios eliminar su cuenta",
|
||||
"hide-minicard-label-text": "Ocultar el texto de la etiqueta de la minitarjeta",
|
||||
"show-desktop-drag-handles": "Mostrar los controles de arrastre del escritorio",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"assignee": "Asignado",
|
||||
"cardAssigneesPopup-title": "Asignado",
|
||||
"addmore-detail": "Añadir una descripción detallada",
|
||||
"show-on-card": "Mostrar en la tarjeta"
|
||||
}
|
||||
|
|
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Aldatu ikusgaitasuna",
|
||||
"boardChangeWatchPopup-title": "Aldatu ikuskatzea",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boardChangeViewPopup-title": "Board View",
|
||||
"boards": "Arbelak",
|
||||
"board-view": "Board View",
|
||||
"board-view-cal": "Calendar",
|
||||
"board-view-swimlanes": "Swimlanes",
|
||||
"board-view-collapse": "Collapse",
|
||||
"board-view-lists": "Zerrendak",
|
||||
"bucket-example": "Esaterako \"Pertz zerrenda\"",
|
||||
"cancel": "Utzi",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Iruzkinak txarteletan soilik egin ditzake",
|
||||
"no-comments": "No comments",
|
||||
"no-comments-desc": "Can not see comments and activities.",
|
||||
"worker": "Worker",
|
||||
"worker-desc": "Can only move cards, assign itself to card and comment.",
|
||||
"computer": "Ordenagailua",
|
||||
"confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
|
||||
"confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
|
||||
|
@ -579,8 +583,9 @@
|
|||
"default": "Default",
|
||||
"queue": "Queue",
|
||||
"subtask-settings": "Subtasks Settings",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "Board Subtasks Settings",
|
||||
"show-subtasks-field": "Cards can have subtasks",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "Deposit subtasks to this board:",
|
||||
"deposit-subtasks-list": "Landing list for subtasks deposited here:",
|
||||
"show-parent-in-minicard": "Show parent in minicard:",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -1,45 +1,45 @@
|
|||
{
|
||||
"accept": "پذیرش",
|
||||
"act-activity-notify": "اعلان فعالیت",
|
||||
"act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-createBoard": "created board __board__",
|
||||
"act-createSwimlane": "created swimlane __swimlane__ to board __board__",
|
||||
"act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-createCustomField": "created custom field __customField__ at board __board__",
|
||||
"act-deleteCustomField": "deleted custom field __customField__ at board __board__",
|
||||
"act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-createList": "added list __list__ to board __board__",
|
||||
"act-addBoardMember": "added member __member__ to board __board__",
|
||||
"act-archivedBoard": "Board __board__ moved to Archive",
|
||||
"act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive",
|
||||
"act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive",
|
||||
"act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive",
|
||||
"act-importBoard": "imported board __board__",
|
||||
"act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__",
|
||||
"act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__",
|
||||
"act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-removeBoardMember": "removed member __member__ from board __board__",
|
||||
"act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"act-addAttachment": "ضمیمه __attachment__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
|
||||
"act-deleteAttachment": "ضمیمه __attachment__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
|
||||
"act-addSubtask": "زیر وظیفه __subtask__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
|
||||
"act-addLabel": "برچسب __label__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
|
||||
"act-addedLabel": "برچسب __label__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
|
||||
"act-removeLabel": "برچسب __label__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
|
||||
"act-removedLabel": "برچسب __label__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
|
||||
"act-addChecklist": "سیاهه __checklist__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
|
||||
"act-addChecklistItem": "چک لیست __checklistItem__ را به سیاهه __checklist__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
|
||||
"act-removeChecklist": "سیاهه __checklist__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
|
||||
"act-removeChecklistItem": "چک لیست __checklistItem__ را از سیاهه __checkList__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
|
||||
"act-checkedItem": "چک لیست __checklistItem__ را از سیاهه __checklist__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ تیک زد",
|
||||
"act-uncheckedItem": "چک لیست __checklistItem__ را از سیاهه __checklist__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ بدونِ تیک کرد",
|
||||
"act-completeChecklist": "سیاهه __checklist__ را در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ کامل کرد",
|
||||
"act-uncompleteChecklist": "سیاهه __checklist__ را در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ ناقص کرد",
|
||||
"act-addComment": "روی کارت __card__ نظر داد: __comment__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__",
|
||||
"act-editComment": "نظر روی کارت __card__ را ویرایش کرد: __comment__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__",
|
||||
"act-deleteComment": "نظر روی کارت __card__ را حذف کرد: __comment__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__",
|
||||
"act-createBoard": "برد __board__ را ایجاد کرد",
|
||||
"act-createSwimlane": "مسیر شناور __swimlane__ را در برد __board__ ایجاد کرد",
|
||||
"act-createCard": "کارت __card__ را در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ ایجاد کرد",
|
||||
"act-createCustomField": "فیلد شخصی __customField__ را در برد __board__ ایجاد کرد",
|
||||
"act-deleteCustomField": "فیلد شخصی __customField__ را در برد __board__ حذف کرد",
|
||||
"act-setCustomField": "فیلد شخصی __customField__ را ویرایش کرد: __customFieldValue__ در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__",
|
||||
"act-createList": "لیست __list__ را به برد __board__ اضافه کرد",
|
||||
"act-addBoardMember": "عضو __member__ را به برد __board__ اضافه کرد",
|
||||
"act-archivedBoard": "برد __board__ را بایگانی کرد",
|
||||
"act-archivedCard": "کارت __card__ را در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ بایگانی کرد",
|
||||
"act-archivedList": "لیست __list__ را در مسیر شناور __swimlane__ در برد __board__ بایگانی کرد",
|
||||
"act-archivedSwimlane": "مسیر شناور __swimlane__ را در برد __board__ بایگانی کرد",
|
||||
"act-importBoard": "برد __board__ را وارد کرد",
|
||||
"act-importCard": "کارت __card__ را به لیست __list__ در مسیر شناور __swimlane__ در برد __board__ وارد کرد",
|
||||
"act-importList": "لیست __list__ را به مسیر شناور __swimlane__ در برد __board__ وارد کرد",
|
||||
"act-joinMember": "عضو __member__ را به کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ اضافه کرد",
|
||||
"act-moveCard": "کارت __card__ را در برد __board__ از لیست __oldList__ در مسیر شناور __oldSwimlane__ به لیست __list__ در مسیر شناور __swimlane__ منتقل کرد",
|
||||
"act-moveCardToOtherBoard": "کارت __card__ را از لیست __oldList__ در مسیر شناور __oldSwimlane__ در برد __oldBoard__ به لیست __list__ در مسیر شناور __swimlane__ در برد __board__ منتقل کرد",
|
||||
"act-removeBoardMember": "عضو __member__ را از برد __board__ حذف کرد",
|
||||
"act-restoredCard": "کارت __card__ را به لیست __list__ در مسیر شناور __swimlane__ در برد __board__ بازگرداند",
|
||||
"act-unjoinMember": "عضو __member__ را از کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ حذف کرد",
|
||||
"act-withBoardTitle": "__board__",
|
||||
"act-withCardTitle": "[__board__] __card__",
|
||||
"actions": "اعمال",
|
||||
|
@ -64,21 +64,21 @@
|
|||
"activity-unchecked-item": "چک نشده %s در چک لیست %s از %s",
|
||||
"activity-checklist-added": "سیاهه به %s اضافه شد",
|
||||
"activity-checklist-removed": "از چک لیست حذف گردید",
|
||||
"activity-checklist-completed": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"activity-checklist-completed": "سیاهه __checklist__ را در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ کامل کرد",
|
||||
"activity-checklist-uncompleted": "تمام نشده ها در چک لیست %s از %s",
|
||||
"activity-checklist-item-added": "added checklist item to '%s' in %s",
|
||||
"activity-checklist-item-removed": "حذف شده از چک لیست '%s' در %s",
|
||||
"add": "افزودن",
|
||||
"activity-checked-item-card": "چک شده %s در چک لیست %s",
|
||||
"activity-unchecked-item-card": "چک نشده %s در چک لیست %s",
|
||||
"activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
||||
"activity-checklist-completed-card": "سیاهه __checklist__ را در کارت __card__ در لیست __list__ در مسیر شناور __swimlane__ در برد __board__ کامل کرد",
|
||||
"activity-checklist-uncompleted-card": "چک لیست تمام نشده %s",
|
||||
"activity-editComment": "edited comment %s",
|
||||
"activity-deleteComment": "deleted comment %s",
|
||||
"activity-editComment": "نظر ویرایش شد %s",
|
||||
"activity-deleteComment": "نظر حذف شد %s",
|
||||
"add-attachment": "افزودن ضمیمه",
|
||||
"add-board": "افزودن برد",
|
||||
"add-card": "افزودن کارت",
|
||||
"add-swimlane": "Add Swimlane",
|
||||
"add-swimlane": "اضافه کردن مسیر شناور",
|
||||
"add-subtask": "افزودن زیر وظیفه",
|
||||
"add-checklist": "افزودن چک لیست",
|
||||
"add-checklist-item": "افزودن مورد به سیاهه",
|
||||
|
@ -89,11 +89,11 @@
|
|||
"added": "اضافه گردید",
|
||||
"addMemberPopup-title": "اعضا",
|
||||
"admin": "مدیر",
|
||||
"admin-desc": "امکان دیدن و ویرایش کارتها،پاک کردن کاربران و تغییر تنظیمات برای تخته",
|
||||
"admin-desc": "امکان دیدن و ویرایش کارتها، پاک کردن کاربران و تغییر تنظیمات برای برد.",
|
||||
"admin-announcement": "اعلان",
|
||||
"admin-announcement-active": "اعلان سراسری فعال",
|
||||
"admin-announcement-title": "اعلان از سوی مدیر",
|
||||
"all-boards": "تمام تختهها",
|
||||
"all-boards": "تمام بردها",
|
||||
"and-n-other-card": "و __count__ کارت دیگر",
|
||||
"and-n-other-card_plural": "و __count__ کارت دیگر",
|
||||
"apply": "اعمال",
|
||||
|
@ -108,7 +108,7 @@
|
|||
"archiveBoardPopup-title": "انتقال برد به آرشیو؟",
|
||||
"archived-items": "بایگانی",
|
||||
"archived-boards": "برد های داخل آرشیو",
|
||||
"restore-board": "بازیابی تخته",
|
||||
"restore-board": "بازیابی برد",
|
||||
"no-archived-boards": "هیچ بردی داخل آرشیو نیست",
|
||||
"archives": "بایگانی",
|
||||
"template": "Template",
|
||||
|
@ -119,23 +119,25 @@
|
|||
"attachment-delete-pop": "حذف پیوست دایمی و بی بازگشت خواهد بود.",
|
||||
"attachmentDeletePopup-title": "آیا می خواهید ضمیمه را حذف کنید؟",
|
||||
"attachments": "ضمائم",
|
||||
"auto-watch": "اضافه شدن خودکار دیده بانی تخته زمانی که ایجاد می شوند",
|
||||
"auto-watch": "اضافه شدن خودکار دیدهبانی بردها زمانی که ایجاد میشوند",
|
||||
"avatar-too-big": "تصویر کاربر بسیار بزرگ است ـ حداکثر۷۰ کیلوبایت ـ",
|
||||
"back": "بازگشت",
|
||||
"board-change-color": "تغییر رنگ",
|
||||
"board-nb-stars": "%s ستاره",
|
||||
"board-not-found": "تخته مورد نظر پیدا نشد",
|
||||
"board-private-info": "این تخته <strong>خصوصی</strong> خواهد بود.",
|
||||
"board-public-info": "این تخته <strong>عمومی</strong> خواهد بود.",
|
||||
"boardChangeColorPopup-title": "تغییر پس زمینه تخته",
|
||||
"boardChangeTitlePopup-title": "تغییر نام تخته",
|
||||
"board-not-found": "برد مورد نظر پیدا نشد",
|
||||
"board-private-info": "این برد <strong>خصوصی</strong> خواهد بود.",
|
||||
"board-public-info": "این برد <strong>عمومی</strong> خواهد بود.",
|
||||
"boardChangeColorPopup-title": "تغییر پس زمینه برد",
|
||||
"boardChangeTitlePopup-title": "تغییر نام برد",
|
||||
"boardChangeVisibilityPopup-title": "تغییر وضعیت نمایش",
|
||||
"boardChangeWatchPopup-title": "تغییر دیده بانی",
|
||||
"boardMenuPopup-title": "Board Settings",
|
||||
"boards": "تختهها",
|
||||
"board-view": "نمایش تخته",
|
||||
"boardChangeViewPopup-title": "نمایش برد",
|
||||
"boards": "بردها",
|
||||
"board-view": "نمایش برد",
|
||||
"board-view-cal": "تقویم",
|
||||
"board-view-swimlanes": "Swimlanes",
|
||||
"board-view-collapse": "بستن",
|
||||
"board-view-lists": "فهرستها",
|
||||
"bucket-example": "برای مثال چیزی شبیه \"لیست سبدها\"",
|
||||
"cancel": "انصراف",
|
||||
|
@ -170,7 +172,7 @@
|
|||
"casSignIn": "ورود با استفاده از CAS",
|
||||
"cardType-card": "کارت",
|
||||
"cardType-linkedCard": "کارتهای مرتبط",
|
||||
"cardType-linkedBoard": "تختههای مرتبط",
|
||||
"cardType-linkedBoard": "برد مرتبط",
|
||||
"change": "تغییر",
|
||||
"change-avatar": "تغییر تصویر",
|
||||
"change-password": "تغییر کلمه عبور",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "فقط میتواند روی کارتها نظر دهد.",
|
||||
"no-comments": "هیچ کامنتی موجود نیست",
|
||||
"no-comments-desc": "نظرات و فعالیت ها را نمی توان دید.",
|
||||
"worker": "کارگر",
|
||||
"worker-desc": "تنها میتوانید کارت ها را جابجا کنید، این را به یک کارت اضافه کنید.",
|
||||
"computer": "رایانه",
|
||||
"confirm-subtask-delete-dialog": "از حذف این زیر وظیفه اطمینان دارید؟",
|
||||
"confirm-checklist-delete-dialog": "مطمئنا چک لیست پاک شود؟",
|
||||
|
@ -232,8 +236,8 @@
|
|||
"copyChecklistToManyCardsPopup-instructions": "عنوان و توضیحات کارت مقصد در این قالب JSON",
|
||||
"copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]",
|
||||
"create": "ایجاد",
|
||||
"createBoardPopup-title": "ایجاد تخته",
|
||||
"chooseBoardSourcePopup-title": "بارگذاری تخته",
|
||||
"createBoardPopup-title": "ایجاد برد",
|
||||
"chooseBoardSourcePopup-title": "بارگذاری برد",
|
||||
"createLabelPopup-title": "ایجاد برچسب",
|
||||
"createCustomField": "ایجاد فیلد",
|
||||
"createCustomFieldPopup-title": "ایجاد فیلد",
|
||||
|
@ -281,16 +285,16 @@
|
|||
"email-invalid": "رایانامه نادرست",
|
||||
"email-invite": "دعوت از طریق رایانامه",
|
||||
"email-invite-subject": "__inviter__ برای شما دعوت نامه ارسال کرده است",
|
||||
"email-invite-text": "__User__ عزیز\n __inviter__ شما را به عضویت تخته \"__board__\" برای همکاری دعوت کرده است.\nلطفا لینک زیر را دنبال کنید، باتشکر:\n__url__",
|
||||
"email-invite-text": "__User__ عزیز\n __inviter__ شما را به عضویت برد \"__board__\" برای همکاری دعوت کرده است.\nلطفا لینک زیر را دنبال کنید، باتشکر:\n__url__",
|
||||
"email-resetPassword-subject": "تنظیم مجدد کلمه عبور در __siteName__",
|
||||
"email-resetPassword-text": "سلام __user__\nجهت تنظیم مجدد کلمه عبور آدرس زیر را دنبال نمایید، باتشکر:\n__url__",
|
||||
"email-sent": "نامه الکترونیکی فرستاده شد",
|
||||
"email-verifyEmail-subject": "تایید آدرس الکترونیکی شما در __siteName__",
|
||||
"email-verifyEmail-text": "سلام __user__\nبه منظور تایید آدرس الکترونیکی حساب خود، آدرس زیر را دنبال نمایید، باتشکر:\n__url__.",
|
||||
"enable-wip-limit": "Enable WIP Limit",
|
||||
"error-board-doesNotExist": "تخته مورد نظر وجود ندارد",
|
||||
"error-board-notAdmin": "شما جهت انجام آن باید مدیر تخته باشید",
|
||||
"error-board-notAMember": "شما انجام آن ،اید عضو این تخته باشید.",
|
||||
"error-board-doesNotExist": "برد مورد نظر وجود ندارد",
|
||||
"error-board-notAdmin": "شما جهت انجام آن باید مدیر برد باشید",
|
||||
"error-board-notAMember": "شما برای انجام آن، باید عضو این برد باشید",
|
||||
"error-json-malformed": "متن درغالب صحیح Json نمی باشد.",
|
||||
"error-json-schema": "داده های Json شما، شامل اطلاعات صحیح در غالب درستی نمی باشد.",
|
||||
"error-list-doesNotExist": "این لیست موجود نیست",
|
||||
|
@ -299,40 +303,40 @@
|
|||
"error-user-notCreated": "این کاربر ایجاد نشده است",
|
||||
"error-username-taken": "این نام کاربری استفاده شده است",
|
||||
"error-email-taken": "رایانامه توسط گیرنده دریافت شده است",
|
||||
"export-board": "انتقال به بیرون تخته",
|
||||
"sort": "Sort",
|
||||
"sort-desc": "Click to Sort List",
|
||||
"list-sort-by": "Sort the List By:",
|
||||
"list-label-modifiedAt": "Last Access Time",
|
||||
"list-label-title": "Name of the List",
|
||||
"list-label-sort": "Your Manual Order",
|
||||
"export-board": "انتقال به بیرون برد",
|
||||
"sort": "مرتب سازی",
|
||||
"sort-desc": "برای مرتب سازی لیست کلیک کنید",
|
||||
"list-sort-by": "مرتب سازی لیست بر اساس:",
|
||||
"list-label-modifiedAt": "زمان دسترسی قبلی",
|
||||
"list-label-title": "نام لیست",
|
||||
"list-label-sort": "دلخواه شما",
|
||||
"list-label-short-modifiedAt": "(L)",
|
||||
"list-label-short-title": "(N)",
|
||||
"list-label-short-sort": "(M)",
|
||||
"filter": "صافی ـFilterـ",
|
||||
"filter-cards": "Filter Cards or Lists",
|
||||
"list-filter-label": "Filter List by Title",
|
||||
"filter-clear": "حذف صافی ـFilterـ",
|
||||
"filter": "صافی ـ فیلتر ـ",
|
||||
"filter-cards": "فیلتر کارتها یا لیستها",
|
||||
"list-filter-label": "فیلتر لیست بر اساس عنوان",
|
||||
"filter-clear": "حذف صافی ـ فیلتر ـ",
|
||||
"filter-no-label": "بدون برچسب",
|
||||
"filter-no-member": "بدون عضو",
|
||||
"filter-no-custom-fields": "هیچ فیلدشخصی ای وجود ندارد",
|
||||
"filter-show-archive": "Show archived lists",
|
||||
"filter-hide-empty": "Hide empty lists",
|
||||
"filter-on": "صافی ـFilterـ فعال است",
|
||||
"filter-on-desc": "شما صافی ـFilterـ برای کارتهای تخته را روشن کرده اید. جهت ویرایش کلیک نمایید.",
|
||||
"filter-to-selection": "صافی ـFilterـ برای موارد انتخابی",
|
||||
"advanced-filter-label": "صافی پیشرفته",
|
||||
"filter-show-archive": "نمایش لیستهای آرشیو شده",
|
||||
"filter-hide-empty": "مخفی کردن لیستهای خالی",
|
||||
"filter-on": "صافی ـ فیلتر ـ فعال است",
|
||||
"filter-on-desc": "شما درحال صافی ـ فیلتر ـ کارتهای این برد هستید. برای ویرایش فیلتر کلیک نمایید.",
|
||||
"filter-to-selection": "صافی ـ فیلتر ـ برای موارد انتخابی",
|
||||
"advanced-filter-label": "صافی ـ فیلتر ـ پیشرفته",
|
||||
"advanced-filter-description": "فیلتر پیشرفته اجازه می دهد تا برای نوشتن رشته حاوی اپراتورهای زیر: ==! = <=> = && || () یک فضای به عنوان یک جداساز بین اپراتورها استفاده می شود. با تایپ کردن نام ها و مقادیر آنها می توانید برای تمام زمینه های سفارشی فیلتر کنید. به عنوان مثال: Field1 == Value1. نکته: اگر فیلدها یا مقادیر حاوی فضاها باشند، شما باید آنها را به یک نقل قول کپسول کنید. برای مثال: 'فیلد 1' == 'مقدار 1'. برای تک تک کاراکترهای کنترل (\\\\) که می توانید از آنها استفاده کنید، می توانید از \\ استفاده کنید. به عنوان مثال: Field1 == I \\ 'm. همچنین شما می توانید شرایط مختلف را ترکیب کنید. برای مثال: F1 == V1 || F1 == V2. به طور معمول همه اپراتورها از چپ به راست تفسیر می شوند. شما می توانید سفارش را با قرار دادن براکت تغییر دهید. برای مثال: F1 == V1 && (F2 == V2 || F2 == V3). همچنین می توانید فیلدهای متنی را با استفاده از regex جستجو کنید: F1 == /Tes.*/i",
|
||||
"fullname": "نام و نام خانوادگی",
|
||||
"header-logo-title": "بازگشت به صفحه تخته.",
|
||||
"header-logo-title": "بازگشت به صفحه بردها.",
|
||||
"hide-system-messages": "عدم نمایش پیامهای سیستمی",
|
||||
"headerBarCreateBoardPopup-title": "ایجاد تخته",
|
||||
"headerBarCreateBoardPopup-title": "ایجاد برد",
|
||||
"home": "خانه",
|
||||
"import": "وارد کردن",
|
||||
"link": "ارتباط",
|
||||
"import-board": "وارد کردن تخته",
|
||||
"import-board-c": "وارد کردن تخته",
|
||||
"import-board-title-trello": "وارد کردن تخته از Trello",
|
||||
"import-board": "وارد کردن برد",
|
||||
"import-board-c": "وارد کردن برد",
|
||||
"import-board-title-trello": "وارد کردن برد از Trello",
|
||||
"import-board-title-wekan": "بارگذاری برد ها از آخرین خروجی",
|
||||
"import-sandstorm-backup-warning": "قبل از بررسی این داده ها را از صفحه اصلی صادر شده یا Trello وارد نمیکنید این دانه دوباره باز می شود و یا دوباره باز می شود، یا برد را پیدا نمی کنید، این بدان معنی است که از دست دادن اطلاعات.",
|
||||
"import-sandstorm-warning": "Imported board will delete all existing data on board and replace it with imported board.",
|
||||
|
@ -353,7 +357,7 @@
|
|||
"invalid-time": "زمان نامعتبر",
|
||||
"invalid-user": "کاربر نامعتیر",
|
||||
"joined": "متصل",
|
||||
"just-invited": "هم اکنون، شما به این تخته دعوت شده اید.",
|
||||
"just-invited": "هم اکنون، شما به این برد دعوت شدهاید.",
|
||||
"keyboard-shortcuts": "میانبر کلیدها",
|
||||
"label-create": "ایجاد برچسب",
|
||||
"label-default": "%s برچسب(پیش فرض)",
|
||||
|
@ -361,7 +365,7 @@
|
|||
"labels": "برچسب ها",
|
||||
"language": "زبان",
|
||||
"last-admin-desc": "شما نمی توانید نقش ـroleـ را تغییر دهید چراکه باید حداقل یک مدیری وجود داشته باشد.",
|
||||
"leave-board": "خروج از تخته",
|
||||
"leave-board": "خروج از برد",
|
||||
"leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.",
|
||||
"leaveBoardPopup-title": "Leave Board ?",
|
||||
"link-card": "ارجاع به این کارت",
|
||||
|
@ -394,8 +398,8 @@
|
|||
"multi-selection": "امکان چند انتخابی",
|
||||
"multi-selection-on": "حالت چند انتخابی روشن است",
|
||||
"muted": "بی صدا",
|
||||
"muted-info": "شما هیچگاه از تغییرات این تخته مطلع نخواهید شد",
|
||||
"my-boards": "تختههای من",
|
||||
"muted-info": "شما هیچگاه از تغییرات این برد مطلع نخواهید شد",
|
||||
"my-boards": "بردهای من",
|
||||
"name": "نام",
|
||||
"no-archived-cards": "هیچ کارتی در آرشیو موجود نمی باشد",
|
||||
"no-archived-lists": "هیچ لیستی در آرشیو موجود نمی باشد",
|
||||
|
@ -405,7 +409,7 @@
|
|||
"normal-desc": "امکان نمایش و تنظیم کارت بدون امکان تغییر تنظیمات",
|
||||
"not-accepted-yet": "دعوت نامه هنوز پذیرفته نشده است",
|
||||
"notify-participate": "اطلاع رسانی از هرگونه تغییر در کارتهایی که ایجاد کرده اید ویا عضو آن هستید",
|
||||
"notify-watch": "اطلاع رسانی از هرگونه تغییر در تخته، لیست یا کارتهایی که از آنها دیده بانی میکنید",
|
||||
"notify-watch": "اطلاع رسانی از هرگونه تغییر در بردها، لیستها یا کارتهایی که از آنها دیدهبانی میکنید",
|
||||
"optional": "انتخابی",
|
||||
"or": "یا",
|
||||
"page-maybe-private": "این صفحه ممکن است خصوصی باشد. شما با<a href='%s'>ورود</a> میتوانید آن را ببینید.",
|
||||
|
@ -417,26 +421,26 @@
|
|||
"previewAttachedImagePopup-title": "پیشنمایش",
|
||||
"previewClipboardImagePopup-title": "پیشنمایش",
|
||||
"private": "خصوصی",
|
||||
"private-desc": "این تخته خصوصی است. فقط تنها افراد اضافه شده به آن می توانند مشاهده و ویرایش کنند.",
|
||||
"private-desc": "این برد خصوصی است. فقط افراد اضافه شده به برد میتوانند مشاهده و ویرایش کنند.",
|
||||
"profile": "حساب کاربری",
|
||||
"public": "عمومی",
|
||||
"public-desc": "این تخته عمومی است. برای هر کسی با آدرس ویا جستجو درموتورها مانند گوگل قابل مشاهده است . فقط افرادی که به آن اضافه شده اند امکان ویرایش دارند.",
|
||||
"quick-access-description": "جهت افزودن یک تخته به اینجا،آنرا ستاره دار نمایید.",
|
||||
"public-desc": "این برد عمومی است. برای هر کسی با آدرس و یا جستجو در موتورها مانند گوگل قابل مشاهده است. فقط افرادی که به برد اضافه شدهاند امکان ویرایش دارند.",
|
||||
"quick-access-description": "جهت افزودن یک برد به اینجا، آن را ستاره دار نمایید.",
|
||||
"remove-cover": "حذف کاور",
|
||||
"remove-from-board": "حذف از تخته",
|
||||
"remove-from-board": "حذف از برد",
|
||||
"remove-label": "حذف برچسب",
|
||||
"listDeletePopup-title": "حذف فهرست؟",
|
||||
"remove-member": "حذف عضو",
|
||||
"remove-member-from-card": "حذف از کارت",
|
||||
"remove-member-pop": "آیا __name__ (__username__) را از __boardTitle__ حذف می کنید? کاربر از تمام کارت ها در این تخته حذف خواهد شد و آنها ازین اقدام مطلع خواهند شد.",
|
||||
"remove-member-pop": "آیا __name__ (__username__) را از __boardTitle__ حذف میکنید؟ کاربر از تمام کارتها در این برد حذف خواهد شد. آنها از این اقدام مطلع خواهند شد.",
|
||||
"removeMemberPopup-title": "آیا می خواهید کاربر را حذف کنید؟",
|
||||
"rename": "تغیر نام",
|
||||
"rename-board": "تغییر نام تخته",
|
||||
"rename-board": "تغییر نام برد",
|
||||
"restore": "بازیابی",
|
||||
"save": "ذخیره",
|
||||
"search": "جستجو",
|
||||
"rules": "قوانین",
|
||||
"search-cards": "Search from card/list titles and descriptions on this board",
|
||||
"search-cards": "جتستجو از عنوان لیست/کارت ها و توضیحات در این برد",
|
||||
"search-example": "متن مورد جستجو؟",
|
||||
"select-color": "انتخاب رنگ",
|
||||
"set-wip-limit-value": "تعیین بیشینه تعداد وظایف در این فهرست",
|
||||
|
@ -444,22 +448,22 @@
|
|||
"shortcut-assign-self": "اختصاص خود به کارت فعلی",
|
||||
"shortcut-autocomplete-emoji": "تکمیل خودکار شکلکها",
|
||||
"shortcut-autocomplete-members": "تکمیل خودکار کاربرها",
|
||||
"shortcut-clear-filters": "حذف تمامی صافی ـfilterـ",
|
||||
"shortcut-clear-filters": "حذف تمامی صافیها ـ فیلترها ـ",
|
||||
"shortcut-close-dialog": "بستن محاوره",
|
||||
"shortcut-filter-my-cards": "کارت های من",
|
||||
"shortcut-show-shortcuts": "بالا آوردن میانبر این لیست",
|
||||
"shortcut-toggle-filterbar": "ضامن نوار جداکننده صافی ـfilterـ",
|
||||
"shortcut-toggle-sidebar": "ضامن نوار جداکننده تخته",
|
||||
"shortcut-toggle-filterbar": "ضامن نوار جداکننده صافی ـ فیلتر ـ",
|
||||
"shortcut-toggle-sidebar": "ضامن نوار جداکننده برد",
|
||||
"show-cards-minimum-count": "نمایش تعداد کارتها اگر لیست شامل بیشتراز",
|
||||
"sidebar-open": "بازکردن جداکننده",
|
||||
"sidebar-close": "بستن جداکننده",
|
||||
"signupPopup-title": "ایجاد یک کاربر",
|
||||
"star-board-title": "برای ستاره دادن، کلیک کنید.این در بالای لیست تخته های شما نمایش داده خواهد شد.",
|
||||
"starred-boards": "تخته های ستاره دار",
|
||||
"starred-boards-description": "تخته های ستاره دار در بالای لیست تخته ها نمایش داده می شود.",
|
||||
"star-board-title": "برای ستاره دار کردن این برد کلیک کنید. این در بالای لیست بردهای شما نمایش داده خواهد شد.",
|
||||
"starred-boards": "بردهای ستاره دار",
|
||||
"starred-boards-description": "بردهای ستاره دار در بالای لیست بردها نمایش داده میشود.",
|
||||
"subscribe": "عضوشدن",
|
||||
"team": "تیم",
|
||||
"this-board": "این تخته",
|
||||
"this-board": "این برد",
|
||||
"this-card": "این کارت",
|
||||
"spent-time-hours": "زمان صرف شده (ساعت)",
|
||||
"overtime-hours": "Overtime (hours)",
|
||||
|
@ -482,8 +486,8 @@
|
|||
"warn-list-archived": "اخطار:این کارت در یک لیست در آرشیو موجود می باشد",
|
||||
"watch": "دیده بانی",
|
||||
"watching": "درحال دیده بانی",
|
||||
"watching-info": "شما از هر تغییری دراین تخته آگاه خواهید شد",
|
||||
"welcome-board": "به این تخته خوش آمدید",
|
||||
"watching-info": "شما از هر تغییری در این برد آگاه خواهید شد",
|
||||
"welcome-board": "به این برد خوش آمدید",
|
||||
"welcome-swimlane": "Milestone 1",
|
||||
"welcome-list1": "پایه ای ها",
|
||||
"welcome-list2": "پیشرفته",
|
||||
|
@ -501,7 +505,7 @@
|
|||
"disable-self-registration": "غیرفعالسازی خودثبتنامی",
|
||||
"invite": "دعوت",
|
||||
"invite-people": "دعوت از افراد",
|
||||
"to-boards": "به تخته(ها)",
|
||||
"to-boards": "به برد(ها)",
|
||||
"email-addresses": "نشانی رایانامه",
|
||||
"smtp-host-description": "آدرس سرور SMTP ای که پست الکترونیکی شما برروی آن است",
|
||||
"smtp-port-description": "شماره درگاه ـPortـ ای که سرور SMTP شما جهت ارسال از آن استفاده می کند",
|
||||
|
@ -520,21 +524,21 @@
|
|||
"email-smtp-test-text": "با موفقیت، یک رایانامه را فرستادید",
|
||||
"error-invitation-code-not-exist": "چنین کد دعوتی یافت نشد",
|
||||
"error-notAuthorized": "شما مجاز به دیدن این صفحه نیستید.",
|
||||
"webhook-title": "Webhook Name",
|
||||
"webhook-token": "Token (Optional for Authentication)",
|
||||
"webhook-title": "نام وبهوک",
|
||||
"webhook-token": "توکن",
|
||||
"outgoing-webhooks": "Outgoing Webhooks",
|
||||
"bidirectional-webhooks": "Two-Way Webhooks",
|
||||
"bidirectional-webhooks": "وبهوک two-way",
|
||||
"outgoingWebhooksPopup-title": "Outgoing Webhooks",
|
||||
"boardCardTitlePopup-title": "فیلتر موضوع کارت",
|
||||
"disable-webhook": "Disable This Webhook",
|
||||
"global-webhook": "Global Webhooks",
|
||||
"disable-webhook": "حذف این وبهوک",
|
||||
"global-webhook": "وبهوکهای سراسری",
|
||||
"new-outgoing-webhook": "New Outgoing Webhook",
|
||||
"no-name": "(ناشناخته)",
|
||||
"Node_version": "نسخه Node",
|
||||
"Meteor_version": "Meteor version",
|
||||
"MongoDB_version": "MongoDB version",
|
||||
"MongoDB_storage_engine": "MongoDB storage engine",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
|
||||
"MongoDB_version": "ورژن MongoDB",
|
||||
"MongoDB_storage_engine": "موتور ذخیره سازی MongoDB",
|
||||
"MongoDB_Oplog_enabled": "MongoDB Oplog فعال",
|
||||
"OS_Arch": "OS Arch",
|
||||
"OS_Cpus": "OS CPU Count",
|
||||
"OS_Freemem": "OS Free Memory",
|
||||
|
@ -573,14 +577,15 @@
|
|||
"requested-by": "تقاضا شده توسط",
|
||||
"board-delete-notice": "حذف دائمی است شما تمام لیست ها، کارت ها و اقدامات مرتبط با این برد را از دست خواهید داد.",
|
||||
"delete-board-confirm-popup": "تمام لیست ها، کارت ها، برچسب ها و فعالیت ها حذف خواهند شد و شما نمی توانید محتوای برد را بازیابی کنید. هیچ واکنشی وجود ندارد",
|
||||
"boardDeletePopup-title": "حذف تخته؟",
|
||||
"delete-board": "حذف تخته",
|
||||
"boardDeletePopup-title": "حذف برد؟",
|
||||
"delete-board": "حذف برد",
|
||||
"default-subtasks-board": "ریزکار برای __board__ برد",
|
||||
"default": "پیشفرض",
|
||||
"queue": "صف",
|
||||
"subtask-settings": "تنظیمات ریزکارها",
|
||||
"card-settings": "Card Settings",
|
||||
"boardSubtaskSettingsPopup-title": "تنظیمات ریزکار های برد",
|
||||
"show-subtasks-field": "کارت می تواند ریزکار داشته باشد",
|
||||
"boardCardSettingsPopup-title": "Card Settings",
|
||||
"deposit-subtasks-board": "افزودن ریزکار به برد:",
|
||||
"deposit-subtasks-list": "لیست برای ریزکار های افزوده شده",
|
||||
"show-parent-in-minicard": "نمایش خانواده در ریز کارت",
|
||||
|
@ -695,10 +700,10 @@
|
|||
"r-swimlane-name": "نام مسیر شناور",
|
||||
"r-board-note": "نکته: برای نمایش موارد ممکن کادر را خالی بگذارید.",
|
||||
"r-checklist-note": "نکته: چکلیستها باید توسط کاما از یکدیگر جدا شوند.",
|
||||
"r-when-a-card-is-moved": "دمانی که یک کارت به لیست دیگری منتقل شد",
|
||||
"r-when-a-card-is-moved": "زمانی که یک کارت به لیست دیگری منتقل شد",
|
||||
"r-set": "Set",
|
||||
"r-update": "Update",
|
||||
"r-datefield": "date field",
|
||||
"r-update": "به روز رسانی",
|
||||
"r-datefield": "تاریخ",
|
||||
"r-df-start-at": "شروع",
|
||||
"r-df-due-at": "ناشی از",
|
||||
"r-df-end-at": "پایان",
|
||||
|
@ -749,5 +754,7 @@
|
|||
"hide-minicard-label-text": "Hide minicard label text",
|
||||
"show-desktop-drag-handles": "Show desktop drag handles",
|
||||
"assignee": "Assignee",
|
||||
"cardAssigneesPopup-title": "Assignee"
|
||||
"cardAssigneesPopup-title": "Assignee",
|
||||
"addmore-detail": "Add a more detailed description",
|
||||
"show-on-card": "Show on Card"
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
"act-addAttachment": "lisätty liite __attachment__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-deleteAttachment": "poistettu liite __attachment__ kortilla __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-addSubtask": "lisätty alitehtävä __subtask__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-addLabel": "Lisätty tunniste __label__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-addedLabel": "Lisätty tunniste __label__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-removeLabel": "Poistettu tunniste __label__ kortilta __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-removedLabel": "Poistettu tunniste __label__ kortilta __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-addLabel": "Lisätty nimilappu __label__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-addedLabel": "Lisätty nimilappu __label__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-removeLabel": "Poistettu nimilappu __label__ kortilta __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-removedLabel": "Poistettu nimilappu __label__ kortilta __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-addChecklist": "lisätty tarkistuslista __checklist__ kortille __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-addChecklistItem": "lisätty tarkistuslistan kohta __checklistItem__ tarkistuslistalle __checklist__ kortilla __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
"act-removeChecklist": "poistettu tarkistuslista __checklist__ kortilta __card__ listalla __list__ swimlanella __swimlane__ taululla __board__",
|
||||
|
@ -83,7 +83,7 @@
|
|||
"add-checklist": "Lisää tarkistuslista",
|
||||
"add-checklist-item": "Lisää kohta tarkistuslistaan",
|
||||
"add-cover": "Lisää kansi",
|
||||
"add-label": "Lisää tunniste",
|
||||
"add-label": "Lisää nimilappu",
|
||||
"add-list": "Lisää lista",
|
||||
"add-members": "Lisää jäseniä",
|
||||
"added": "Lisätty",
|
||||
|
@ -132,10 +132,12 @@
|
|||
"boardChangeVisibilityPopup-title": "Muokkaa näkyvyyttä",
|
||||
"boardChangeWatchPopup-title": "Muokkaa seuraamista",
|
||||
"boardMenuPopup-title": "Tauluasetukset",
|
||||
"boardChangeViewPopup-title": "Taulunäkymä",
|
||||
"boards": "Taulut",
|
||||
"board-view": "Taulunäkymä",
|
||||
"board-view-cal": "Kalenteri",
|
||||
"board-view-swimlanes": "Swimlanet",
|
||||
"board-view-collapse": "Pienennä",
|
||||
"board-view-lists": "Listat",
|
||||
"bucket-example": "Kuten “Laatikko lista” esimerkiksi",
|
||||
"cancel": "Peruuta",
|
||||
|
@ -150,9 +152,9 @@
|
|||
"card-spent": "Käytetty aika",
|
||||
"card-edit-attachments": "Muokkaa liitetiedostoja",
|
||||
"card-edit-custom-fields": "Muokkaa mukautettuja kenttiä",
|
||||
"card-edit-labels": "Muokkaa tunnisteita",
|
||||
"card-edit-labels": "Muokkaa nimilappuja",
|
||||
"card-edit-members": "Muokkaa jäseniä",
|
||||
"card-labels-title": "Muokkaa kortin tunnisteita.",
|
||||
"card-labels-title": "Muokkaa kortin nimilappuja.",
|
||||
"card-members-title": "Lisää tai poista taulun jäseniä tältä kortilta.",
|
||||
"card-start": "Alkaa",
|
||||
"card-start-on": "Alkaa",
|
||||
|
@ -161,7 +163,7 @@
|
|||
"cardCustomFieldsPopup-title": "Muokkaa mukautettuja kenttiä",
|
||||
"cardDeletePopup-title": "Poista kortti?",
|
||||
"cardDetailsActionsPopup-title": "Korttitoimet",
|
||||
"cardLabelsPopup-title": "Tunnisteet",
|
||||
"cardLabelsPopup-title": "Nimilaput",
|
||||
"cardMembersPopup-title": "Jäsenet",
|
||||
"cardMorePopup-title": "Lisää",
|
||||
"cardTemplatePopup-title": "Luo malli",
|
||||
|
@ -221,6 +223,8 @@
|
|||
"comment-only-desc": "Voi vain kommentoida kortteja",
|
||||
"no-comments": "Ei kommentteja",
|
||||
"no-comments-desc": "Ei voi nähdä kommentteja ja toimintaa.",
|
||||
"worker": "Työntekijä",
|
||||
"worker-desc": "Voi vain siirtää kortteja, ilmoittautua kortin käsittelijäksi ja kommentoida.",
|
||||
"computer": "Tietokone",
|
||||
"confirm-subtask-delete-dialog": "Haluatko varmasti poistaa alitehtävän?",
|
||||
"confirm-checklist-delete-dialog": "Haluatko varmasti poistaa tarkistuslistan?",
|
||||
|
@ -234,7 +238,7 @@
|
|||
"create": "Luo",
|
||||
"createBoardPopup-title": "Luo taulu",
|
||||
"chooseBoardSourcePopup-title": "Tuo taulu",
|
||||
"createLabelPopup-title": "Luo tunniste",
|
||||
"createLabelPopup-title": "Luo nimilappu",
|
||||
"createCustomField": "Luo kenttä",
|
||||
"createCustomFieldPopup-title": "Luo kenttä",
|
||||
"current": "nykyinen",
|
||||
|
@ -254,9 +258,9 @@
|
|||
"default-avatar": "Oletusprofiilikuva",
|
||||
"delete": "Poista",
|
||||
"deleteCustomFieldPopup-title": "Poista mukautettu kenttä?",
|
||||
"deleteLabelPopup-title": "Poista tunniste?",
|
||||
"deleteLabelPopup-title": "Poista nimilappu?",
|
||||
"description": "Kuvaus",
|
||||
"disambiguateMultiLabelPopup-title": "Yksikäsitteistä tunnistetoiminta",
|
||||
"disambiguateMultiLabelPopup-title": "Yksikäsitteistä nimilapputoiminta",
|
||||
"disambiguateMultiMemberPopup-title": "Yksikäsitteistä jäsentoiminta",
|
||||
"discard": "Hylkää",
|
||||
"done": "Valmis",
|
||||
|
@ -270,7 +274,7 @@
|
|||
"editCardDueDatePopup-title": "Muokkaa eräpäivää",
|
||||
"editCustomFieldPopup-title": "Muokkaa kenttää",
|
||||
"editCardSpentTimePopup-title": "Muuta käytettyä aikaa",
|
||||
"editLabelPopup-title": "Muokkaa tunnistetta",
|
||||
"editLabelPopup-title": "Muokkaa nimilappua",
|
||||
"editNotificationPopup-title": "Muokkaa ilmoituksia",
|
||||
"editProfilePopup-title": "Muokkaa profiilia",
|
||||
"email": "Sähköposti",
|
||||
|
@ -313,7 +317,7 @@
|
|||
"filter-cards": "Suodata kortit tai listat",
|
||||
"list-filter-label": "Suodata listat otsikon mukaan",
|
||||
"filter-clear": "Poista suodatin",
|
||||
"filter-no-label": "Ei tunnistetta",
|
||||
"filter-no-label": "Ei nimilappua",
|
||||
"filter-no-member": "Ei jäseniä",
|
||||
"filter-no-custom-fields": "Ei mukautettuja kenttiä",
|
||||
"filter-show-archive": "Näytä arkistoidut listat",
|
||||
|
@ -355,10 +359,10 @@
|
|||
"joined": "liittyi",
|
||||
"just-invited": "Sinut on juuri kutsuttu tälle taululle",
|
||||
"keyboard-shortcuts": "Pikanäppäimet",
|
||||
"label-create": "Luo tunniste",
|
||||
"label-default": "%s tunniste (oletus)",
|
||||
"label-delete-pop": "Tätä ei voi peruuttaa. Tämä poistaa tämän tunnisteen kaikista korteista ja tuhoaa sen historian.",
|
||||
"labels": "Tunnisteet",
|
||||
"label-create": "Luo nimilappu",
|
||||
"label-default": "%s nimilappu (oletus)",
|
||||
"label-delete-pop": "Tätä ei voi peruuttaa. Tämä poistaa tämän nimilapun kaikista korteista ja tuhoaa sen historian.",
|
||||
"labels": "Nimilaput",
|
||||
"language": "Kieli",
|
||||
"last-admin-desc": "Et voi vaihtaa rooleja koska täytyy olla olemassa ainakin yksi ylläpitäjä.",
|
||||
"leave-board": "Jää pois taululta",
|
||||
|
@ -424,7 +428,7 @@
|
|||
"quick-access-description": "Merkkaa taulu tähdellä lisätäksesi pikavalinta tähän palkkiin.",
|
||||
"remove-cover": "Poista kansi",
|
||||
"remove-from-board": "Poista taululta",
|
||||
"remove-label": "Poista tunniste",
|
||||
"remove-label": "Poista nimilappu",
|
||||
"listDeletePopup-title": "Poista lista?",
|
||||
"remove-member": "Poista jäsen",
|
||||
"remove-member-from-card": "Poista kortilta",
|
||||
|
@ -550,7 +554,7 @@
|
|||
"seconds": "sekuntia",
|
||||
"show-field-on-card": "Näytä tämä kenttä kortilla",
|
||||
"automatically-field-on-card": "Luo kenttä automaattisesti kaikille korteille",
|
||||
"showLabel-field-on-card": "Näytä kentän tunniste minikortilla",
|
||||
"showLabel-field-on-card": "Näytä kentän nimilappu minikortilla",
|
||||
"yes": "Kyllä",
|
||||
"no": "Ei",
|
||||
"accounts": "Tilit",
|
||||
|
@ -572,15 +576,16 @@
|
|||
"assigned-by": "Tehtävänantaja",
|
||||
"requested-by": "Pyytäjä",
|
||||
"board-delete-notice": "Poistaminen on lopullista. Menetät kaikki listat, kortit ja toimet tällä taululla.",
|
||||
"delete-board-confirm-popup": "Kaikki listat, kortit, tunnisteet ja toimet poistetaan ja et pysty palauttamaan taulun sisältöä. Tätä ei voi peruuttaa.",
|
||||
"delete-board-confirm-popup": "Kaikki listat, kortit, nimilaput ja toimet poistetaan ja et pysty palauttamaan taulun sisältöä. Tätä ei voi peruuttaa.",
|
||||
"boardDeletePopup-title": "Poista taulu?",
|
||||
"delete-board": "Poista taulu",
|
||||
"default-subtasks-board": "Alitehtävät taululle __board__",
|
||||
"default": "Oletus",
|
||||
"queue": "Jono",
|
||||
"subtask-settings": "Alitehtävä-asetukset",
|
||||
"card-settings": "Kortin asetukset",
|
||||
"boardSubtaskSettingsPopup-title": "Taulualitehtävien asetukset",
|
||||
"show-subtasks-field": "Korteilla voi olla alitehtäviä",
|
||||
"boardCardSettingsPopup-title": "Kortin asetukset",
|
||||
"deposit-subtasks-board": "Talleta alitehtävät tälle taululle:",
|
||||
"deposit-subtasks-list": "Laskeutumislista alatehtäville tallennettu tänne:",
|
||||
"show-parent-in-minicard": "Näytä ylätehtävä minikortilla:",
|
||||
|
@ -592,11 +597,11 @@
|
|||
"parent-card": "Ylätehtäväkortti",
|
||||
"source-board": "Lähdetaulu",
|
||||
"no-parent": "Älä näytä ylätehtävää",
|
||||
"activity-added-label": "lisätty tunniste '%s' kohteeseen %s",
|
||||
"activity-removed-label": "poistettu tunniste '%s' kohteesta %s",
|
||||
"activity-added-label": "lisätty nimilappu '%s' kohteeseen %s",
|
||||
"activity-removed-label": "poistettu nimilappu '%s' kohteesta %s",
|
||||
"activity-delete-attach": "poistettu liitetiedosto kohteesta %s",
|
||||
"activity-added-label-card": "lisätty tunniste '%s'",
|
||||
"activity-removed-label-card": "poistettu tunniste '%s'",
|
||||
"activity-added-label-card": "lisätty nimilappu '%s'",
|
||||
"activity-removed-label-card": "poistettu nimilappu '%s'",
|
||||
"activity-delete-attach-card": "poistettu liitetiedosto",
|
||||
"activity-set-customfield": "asetettu mukautettu kentän '%s' sisällöksi '%s' kortilla %s",
|
||||
"activity-unset-customfield": "poistettu mukautettu kenttä '%s' kortilla %s",
|
||||
|
@ -622,8 +627,8 @@
|
|||
"r-archived": "Siirretty Arkistoon",
|
||||
"r-unarchived": "Palautettu Arkistosta",
|
||||
"r-a-card": "kortti",
|
||||
"r-when-a-label-is": "Kun tunniste on",
|
||||
"r-when-the-label": "Kun tunniste on",
|
||||
"r-when-a-label-is": "Kun nimilappu on",
|
||||
"r-when-the-label": "Kun nimilappu on",
|
||||
"r-list-name": "listan nimi",
|
||||
"r-when-a-member": "Kun jäsen on",
|
||||
"r-when-the-member": "Kun käyttäjä",
|
||||
|
@ -646,7 +651,7 @@
|
|||
"r-card": "kortti",
|
||||
"r-add": "Lisää",
|
||||
"r-remove": "Poista",
|
||||
"r-label": "tunniste",
|
||||
"r-label": "nimilappu",
|
||||
"r-member": "jäsen",
|
||||
"r-remove-all": "Poista kaikki jäsenet kortilta",
|
||||
"r-set-color": "Aseta väriksi",
|
||||
|
@ -672,8 +677,8 @@
|
|||
"r-d-send-email-message": "viesti",
|
||||
"r-d-archive": "Siirrä kortti Arkistoon",
|
||||
"r-d-unarchive": "Palauta kortti Arkistosta",
|
||||
"r-d-add-label": "Lisää tunniste",
|
||||
"r-d-remove-label": "Poista tunniste",
|
||||
"r-d-add-label": "Lisää nimilappu",
|
||||
"r-d-remove-label": "Poista nimilappu",
|
||||
"r-create-card": "Luo uusi kortti",
|
||||
"r-in-list": "listassa",
|
||||
"r-in-swimlane": "swimlanessa",
|
||||
|
@ -746,8 +751,10 @@
|
|||
"act-atUserComment": "Sinut mainittiin [__board__] __list__/__card__",
|
||||
"delete-user-confirm-popup": "Haluatko varmasti poistaa tämän käyttäjätilin? Tätä ei voi peruuttaa.",
|
||||
"accounts-allowUserDelete": "Salli käyttäjien poistaa tilinsä itse",
|
||||
"hide-minicard-label-text": "Piilota minikortin tunniste teksti",
|
||||
"hide-minicard-label-text": "Piilota minikortin nimilappu teksti",
|
||||
"show-desktop-drag-handles": "Näytä työpöydän vedon kahvat",
|
||||
"assignee": "Valtuutettu",
|
||||
"cardAssigneesPopup-title": "Valtuutettu"
|
||||
"assignee": "Käsittelijä",
|
||||
"cardAssigneesPopup-title": "Käsittelijä",
|
||||
"addmore-detail": "Lisää tarkempi kuvaus",
|
||||
"show-on-card": "Näytä kortilla"
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue