Compare commits

..
18 Commits
Author SHA1 Message Date
Lauri Ojansivu 94611c4f38 v2.95 2019-07-01 05:51:41 +03:00
Lauri Ojansivu 0e6035f846 Update translations. 2019-07-01 05:46:05 +03:00
Lauri Ojansivu a8faeb2e5f Update changelog. 2019-07-01 05:44:21 +03:00
Lauri Ojansivu 118c9613dc Update changelog. 2019-07-01 05:29:02 +03:00
Lauri Ojansivu b42ecb7948 In RELAX MODE and DARK MODE, hide card fields: received, start, due, end, members, requested, assigned.
Thanks to xet7 !
2019-07-01 05:24:29 +03:00
Lauri Ojansivu e0398f01c8 Update changelog. 2019-07-01 05:13:35 +03:00
Lauri Ojansivu b260d05a8b Make list header add card + button more black, to make it more visible.
Thanks to xet7 !
2019-07-01 05:03:44 +03:00
Lauri Ojansivu bbc68309af On minicard, make comment icon and number of comments have red color
on white rounded background, so it is more visible when there is comments on card.

Thanks to xet7 !
2019-07-01 05:02:48 +03:00
Lauri Ojansivu 48ebc5f117 Make card description text more visible with black borders and more white text area.
Thanks to xet7 !
2019-07-01 05:01:52 +03:00
Lauri Ojansivu c04292e988 Added themes to board background colors:
- RELAX MODE, so when green background selected, list background is light green.
- [DARK MODE](https://github.com/wekan/wekan/issues/1149), when dark backgroud
  color selected. Please test and send color visibility fixes as pull requests.

Thanks to xet7 !

Related #1149
2019-07-01 05:00:18 +03:00
Lauri Ojansivu 2203d46493 Merge branch 'devel' into edge 2019-06-28 09:15:38 +03:00
Lauri Ojansivu dae8033d61 v2.93 2019-06-28 09:14:34 +03:00
Lauri Ojansivu b5c566615b Fix lint errors. 2019-06-28 09:07:30 +03:00
Lauri Ojansivu d792033ffa Update translations. 2019-06-28 08:55:58 +03:00
Lauri Ojansivu 63fa75819c Update changelog. 2019-06-28 08:54:19 +03:00
Lauri Ojansivu f25e54a9f1 Merge pull request #2519 from Worteks/devel
Add test if email attribute is an Array
2019-06-28 08:50:29 +03:00
Thibaut Démaret 621efd59db Add test if email attribute is an Array 2019-06-28 06:59:51 +02:00
Lauri Ojansivu 5e36c188f9 Update translations. 2019-06-27 20:31:41 +03:00
1006 changed files with 42223 additions and 148245 deletions

No files matched your search

-12
View File
@@ -1,12 +0,0 @@
{
"presets": [
"@babel/preset-stage-3"
],
"env": {
"COVERAGE": {
"plugins": [
"istanbul"
]
}
}
}
-233
View File
@@ -1,233 +0,0 @@
FROM quay.io/wekan/ubuntu:groovy-20210115
LABEL maintainer="sgr"
ENV BUILD_DEPS="gnupg gosu libarchive-tools wget curl bzip2 g++ build-essential python git ca-certificates iproute2"
ENV DEBIAN_FRONTEND=noninteractive
ENV \
DEBUG=false \
NODE_VERSION=v12.22.3 \
METEOR_RELEASE=1.10.2 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
NPM_VERSION=latest \
FIBERS_VERSION=4.0.1 \
ARCHITECTURE=linux-x64 \
SRC_PATH=./ \
WITH_API=true \
RESULTS_PER_PAGE="" \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \
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=false \
CARD_OPENED_WEBHOOK_ENABLED=false \
ATTACHMENTS_STORE_PATH="" \
MAX_IMAGE_PIXEL="" \
IMAGE_COMPRESS_RATIO="" \
NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="" \
BIGEVENTS_PATTERN=NONE \
NOTIFY_DUE_DAYS_BEFORE_AND_AFTER="" \
NOTIFY_DUE_AT_HOUR_OF_DAY="" \
EMAIL_NOTIFICATION_TIMEOUT=30000 \
MATOMO_ADDRESS="" \
MATOMO_SITE_ID="" \
MATOMO_DO_NOT_TRACK=true \
MATOMO_WITH_USERNAME=false \
BROWSER_POLICY_ENABLED=true \
TRUSTED_URL="" \
WEBHOOKS_ATTRIBUTES="" \
OAUTH2_ENABLED=false \
OAUTH2_CA_CERT="" \
OAUTH2_ADFS_ENABLED=false \
OAUTH2_LOGIN_STYLE=redirect \
OAUTH2_CLIENT_ID="" \
OAUTH2_SECRET="" \
OAUTH2_SERVER_URL="" \
OAUTH2_AUTH_ENDPOINT="" \
OAUTH2_USERINFO_ENDPOINT="" \
OAUTH2_TOKEN_ENDPOINT="" \
OAUTH2_ID_MAP="" \
OAUTH2_USERNAME_MAP="" \
OAUTH2_FULLNAME_MAP="" \
OAUTH2_ID_TOKEN_WHITELIST_FIELDS="" \
OAUTH2_REQUEST_PERMISSIONS='openid profile email' \
OAUTH2_EMAIL_MAP="" \
LDAP_ENABLE=false \
LDAP_PORT=389 \
LDAP_HOST="" \
LDAP_BASEDN="" \
LDAP_LOGIN_FALLBACK=false \
LDAP_RECONNECT=true \
LDAP_TIMEOUT=10000 \
LDAP_IDLE_TIMEOUT=10000 \
LDAP_CONNECT_TIMEOUT=10000 \
LDAP_AUTHENTIFICATION=false \
LDAP_AUTHENTIFICATION_USERDN="" \
LDAP_AUTHENTIFICATION_PASSWORD="" \
LDAP_LOG_ENABLED=false \
LDAP_BACKGROUND_SYNC=false \
LDAP_BACKGROUND_SYNC_INTERVAL="" \
LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false \
LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false \
LDAP_ENCRYPTION=false \
LDAP_CA_CERT="" \
LDAP_REJECT_UNAUTHORIZED=false \
LDAP_USER_AUTHENTICATION=false \
LDAP_USER_AUTHENTICATION_FIELD=uid \
LDAP_USER_SEARCH_FILTER="" \
LDAP_USER_SEARCH_SCOPE="" \
LDAP_USER_SEARCH_FIELD="" \
LDAP_SEARCH_PAGE_SIZE=0 \
LDAP_SEARCH_SIZE_LIMIT=0 \
LDAP_GROUP_FILTER_ENABLE=false \
LDAP_GROUP_FILTER_OBJECTCLASS="" \
LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE="" \
LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE="" \
LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT="" \
LDAP_GROUP_FILTER_GROUP_NAME="" \
LDAP_UNIQUE_IDENTIFIER_FIELD="" \
LDAP_UTF8_NAMES_SLUGIFY=true \
LDAP_USERNAME_FIELD="" \
LDAP_FULLNAME_FIELD="" \
LDAP_MERGE_EXISTING_USERS=false \
LDAP_EMAIL_FIELD="" \
LDAP_EMAIL_MATCH_ENABLE=false \
LDAP_EMAIL_MATCH_REQUIRE=false \
LDAP_EMAIL_MATCH_VERIFIED=false \
LDAP_SYNC_USER_DATA=false \
LDAP_SYNC_USER_DATA_FIELDMAP="" \
LDAP_SYNC_GROUP_ROLES="" \
LDAP_DEFAULT_DOMAIN="" \
LDAP_SYNC_ADMIN_STATUS="" \
LDAP_SYNC_ADMIN_GROUPS="" \
HEADER_LOGIN_ID="" \
HEADER_LOGIN_FIRSTNAME="" \
HEADER_LOGIN_LASTNAME="" \
HEADER_LOGIN_EMAIL="" \
LOGOUT_WITH_TIMER=false \
LOGOUT_IN="" \
LOGOUT_ON_HOURS="" \
LOGOUT_ON_MINUTES="" \
CORS="" \
CORS_ALLOW_HEADERS="" \
CORS_EXPOSE_HEADERS="" \
DEFAULT_AUTHENTICATION_METHOD="" \
PASSWORD_LOGIN_ENABLED=true \
CAS_ENABLED=false \
CAS_BASE_URL="" \
CAS_LOGIN_URL="" \
CAS_VALIDATE_URL="" \
SAML_ENABLED=false \
SAML_PROVIDER="" \
SAML_ENTRYPOINT="" \
SAML_ISSUER="" \
SAML_CERT="" \
SAML_IDPSLO_REDIRECTURL="" \
SAML_PRIVATE_KEYFILE="" \
SAML_PUBLIC_CERTFILE="" \
SAML_IDENTIFIER_FORMAT="" \
SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE="" \
SAML_ATTRIBUTES="" \
DEFAULT_WAIT_SPINNER=""
# Install OS
RUN set -o xtrace \
&& useradd --user-group -m --system --home-dir /home/wekan wekan \
&& apt-get update \
&& apt-get install --assume-yes --no-install-recommends apt-utils apt-transport-https ca-certificates 2>&1 \
&& apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS}
# Install NodeJS
RUN set -o xtrace \
&& cd /tmp \
&& curl -fsSLO --compressed "https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-$ARCHITECTURE.tar.xz" \
&& curl -fsSLO --compressed "https://nodejs.org/dist/$NODE_VERSION/SHASUMS256.txt.asc" \
&& grep " node-$NODE_VERSION-$ARCHITECTURE.tar.xz\$" SHASUMS256.txt.asc | sha256sum -c - \
&& tar -xJf "node-$NODE_VERSION-$ARCHITECTURE.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \
&& rm "node-$NODE_VERSION-$ARCHITECTURE.tar.xz" SHASUMS256.txt.asc \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs \
&& mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp /root/.node-gyp/${NODE_VERSION} /home/wekan/.config \
&& npm install -g npm@${NPM_VERSION} \
&& chown wekan:wekan --recursive /home/wekan/.config
ENV DEBIAN_FRONTEND=dialog
USER wekan
# Install Meteor
RUN set -o xtrace \
&& cd /home/wekan \
&& curl https://install.meteor.com/?release=$METEOR_VERSION --output /home/wekan/install-meteor.sh \
# Replace tar with bsdtar in the install script; https://github.com/jshimko/meteor-launchpad/issues/39
&& sed --in-place "s/tar -xzf.*/bsdtar -xf \"\$TARBALL_FILE\" -C \"\$INSTALL_TMPDIR\"/g" /home/wekan/install-meteor.sh \
&& sed --in-place 's/VERBOSITY="--silent"/VERBOSITY="--progress-bar"/' /home/wekan/install-meteor.sh \
&& printf "\n[-] Installing Meteor $METEOR_VERSION...\n\n" \
&& sh /home/wekan/install-meteor.sh
ENV PATH=$PATH:/home/wekan/.meteor/
USER root
RUN echo "export PATH=$PATH" >> /etc/environment
USER wekan
# Copy source dir
RUN set -o xtrace \
&& mkdir -p /home/wekan/app/.meteor \
&& mkdir -p /home/wekan/app/packages
COPY \
.meteor/.finished-upgraders \
.meteor/.id \
.meteor/cordova-plugins \
.meteor/packages \
.meteor/platforms \
.meteor/release \
.meteor/versions \
/home/wekan/app/.meteor/
COPY \
package.json \
settings.json \
/home/wekan/app/
COPY \
packages \
/home/wekan/app/packages/
USER root
RUN set -o xtrace \
&& chown -R wekan:wekan /home/wekan/app /home/wekan/.meteor
USER wekan
RUN \
set -o xtrace && \
sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js && \
cd /home/wekan/.meteor && \
/home/wekan/.meteor/meteor -- help;
RUN \
set -o xtrace && \
# Build app
cd /home/wekan/app && \
/home/wekan/.meteor/meteor add standard-minifier-js && \
/home/wekan/.meteor/meteor npm install && \
/home/wekan/.meteor/meteor build --directory /home/wekan/app_build
RUN \
set -o xtrace && \
cd /home/wekan/app_build/bundle/programs/server/ && \
chmod u+w package.json npm-shrinkwrap.json && \
npm install
ENV PORT=3000
EXPOSE $PORT
WORKDIR /home/wekan/app
CMD ["/home/wekan/.meteor/meteor", "run", "--verbose", "--settings", "settings.json"]
-12
View File
@@ -1,12 +0,0 @@
#!/bin/bash
cd /home/wekan/app
rm -rf node_modules
/home/wekan/.meteor/meteor npm install
rm -rf .build
/home/wekan/.meteor/meteor build .build --directory
cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
cd .build/bundle/programs/server
rm -rf node_modules
/home/wekan/.meteor/meteor npm install
cd /home/wekan/app
-17
View File
@@ -1,17 +0,0 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"dockerComposeFile": ["docker-compose.yml", "docker-compose.extend.yml"],
"service": "wekan-dev",
"workspaceFolder": "/home/wekan/app",
"extensions": [
"mutantdino.resourcemonitor",
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"codezombiech.gitignore",
"eamodio.gitlens",
"gruntfuggly.todo-tree",
"dotjoshjohnson.xml",
"redhat.vscode-yaml",
"vuhrmeister.vscode-meteor"
]
}
-55
View File
@@ -1,55 +0,0 @@
version: '3.7'
services:
wekandb-dev:
image: mongo:4.4
container_name: wekan-dev-db
restart: unless-stopped
command: mongod --oplogSize 128
networks:
- wekan-dev-tier
expose:
- 27017
volumes:
- ./volumes/wekan-db:/data/db
- ./volumes/wekan-db-dump:/dump
- /etc/localtime:/etc/localtime:ro
wekan-dev:
container_name: wekan-dev-app
restart: always
networks:
- wekan-dev-tier
build:
context: ..
dockerfile: .devcontainer/Dockerfile
ports:
- 3000:3000
- 9229:9229
environment:
- MONGO_URL=mongodb://wekandb-dev:27017/wekan
- ROOT_URL=http://localhost:3000
- WITH_API=true
- RICHER_CARD_COMMENT_EDITOR=true
- BROWSER_POLICY_ENABLED=true
depends_on:
- wekandb-dev
volumes:
- ../client:/home/wekan/app/client
- ../models:/home/wekan/app/models
- ../config:/home/wekan/app/config
- ../i18n:/home/wekan/app/i18n
- ../server:/home/wekan/app/server
- ../public:/home/wekan/app/public
- /etc/localtime:/etc/localtime:ro
volumes:
wekan-dev-db:
driver: local
wekan-dev-db-dump:
driver: local
networks:
wekan-dev-tier:
driver: bridge
-36
View File
@@ -1,36 +0,0 @@
*~
*.swp
.meteor-spk
*.sublime-workspace
tmp/
node_modules/
npm-debug.log
.gitmodules
.vscode/
.idea/
.build/*
**/parts/
**/stage
**/prime
**/*.snap
snap/.snapcraft/
.idea
.DS_Store
.DS_Store?
.build*
*.browserify.js.cached
*.browserify.js.map
.build*
versions.json
.versions
.npm
.build*
._*
.Trashes
Thumbs.db
ehthumbs.db
.eslintcache
.meteor/local
.devcontainer/docker-compose.extend.yml
.devcontainer/volumes*/
.git
-17
View File
@@ -8,20 +8,3 @@ end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
[*.{js,html}]
charset = utf-8
end_of_line = lf
indent_brace_style = 1TBS
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 80
quote_type = auto
spaces_around_operators = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
-2
View File
@@ -1,2 +0,0 @@
packages/*
.snap-meteor-1.8/*
+13 -37
View File
@@ -1,20 +1,16 @@
{
"extends": [
"eslint:recommended",
"plugin:meteor/recommended",
"prettier",
"prettier/standard"
],
"extends": "eslint:recommended",
"env": {
"es6": true,
"node": true,
"browser": true,
"meteor": true
"browser": true
},
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
"ecmaVersion": 2017,
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"rules": {
"strict": 0,
@@ -24,7 +20,7 @@
"consistent-return": 2,
"dot-notation": 2,
"eqeqeq": 2,
"indent": 0,
"indent": [2, 2],
"no-cond-assign": 2,
"no-constant-condition": 2,
"no-eval": 2,
@@ -32,12 +28,11 @@
"no-unneeded-ternary": 2,
"radix": 2,
"semi": [2, "always"],
"camelcase": [2, { "properties": "never" }],
"camelcase": [2, {"properties": "never"}],
"comma-spacing": 2,
"comma-style": 2,
"eol-last": 2,
"linebreak-style": [2, "unix"],
"meteor/audit-argument-checks": 0,
"new-parens": 2,
"no-lonely-if": 2,
"no-multiple-empty-lines": 2,
@@ -45,9 +40,10 @@
"no-spaced-func": 2,
"no-trailing-spaces": 2,
"operator-linebreak": 2,
"quotes": [2, "single", { "avoidEscape": true }],
"quotes": [2, "single"],
"semi-spacing": 2,
"space-unary-ops": 2,
"arrow-parens": 2,
"arrow-spacing": 2,
"no-class-assign": 2,
"no-dupe-class-members": 2,
@@ -56,27 +52,8 @@
"prefer-const": 2,
"prefer-spread": 2,
"prefer-template": 2,
"no-unused-vars": "warn",
"prettier/prettier": [
"error",
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"trailingComma": "all"
}
],
"meteor/no-session": 0
"no-unused-vars" : "warn"
},
"settings": {
"import/resolver": {
"meteor": {
"extensions": [".js", ".jsx"]
}
}
},
"plugins": ["prettier", "meteor"],
"globals": {
"Meteor": false,
"Session": false,
@@ -123,7 +100,7 @@
"Attachments": true,
"Boards": true,
"CardComments": true,
"DatePicker": true,
"DatePicker" : true,
"Cards": true,
"CustomFields": true,
"Lists": true,
@@ -146,7 +123,6 @@
"allowIsBoardMemberByCard": true,
"allowIsBoardMemberCommentOnly": true,
"allowIsBoardMemberNoComments": true,
"allowIsBoardMemberWorker": true,
"Emoji": true,
"Checklists": true,
"Settings": true,
-257
View File
@@ -1,257 +0,0 @@
name: wekan
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.
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
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://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.2.6.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.22.3
node-packages:
- node-gyp
- node-pre-gyp
- fibers
build-packages:
- ca-certificates
- apt-utils
- 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
# Create the OpenAPI specification
rm -rf .build
## Use Meteor 1.8.x on Snap
#rm -rf .meteor
#mv .snap-meteor-1.8/.meteor .
#mv .snap-meteor-1.8/package.json .
#mv .snap-meteor-1.8/package-lock.json .
## Meteor 1.9.x has changes to Buffer() => Buffer.alloc(), so reverting those
#mv .snap-meteor-1.8/cfs_access-point.txt fix-download-unicode/
#mv .snap-meteor-1.8/export.js models/
#mv .snap-meteor-1.8/wekanCreator.js models/
#mv .snap-meteor-1.8/ldap.js packages/wekan-ldap/server/ldap.js
#mv .snap-meteor-1.8/oidc_server.js packages/wekan-oidc/oidc_server.js
rm -rf .snap-meteor-1.8
#mkdir -p .build/python
#cd .build/python
#git clone --depth 1 -b master https://github.com/Kronuz/esprima-python
#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 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
# https://github.com/sandstorm-io/sandstorm/blob/0f1fec013fe7208ed0fd97eb88b31b77e3c61f42/shell/server/00-startup.js#L99-L129
# Also see beginning of wekan/server/authentication.js
# import Fiber from "fibers";
# Fiber.poolSize = 1e9;
# OLD: Download node version 8.12.0 prerelease build => Official node 8.12.0 has been released
# Description at https://releases.wekan.team/node.txt
##echo "375bd8db50b9c692c0bbba6e96d4114cd29bee3770f901c1ff2249d1038f1348 node" >> node-SHASUMS256.txt.asc
##curl https://releases.wekan.team/node -o node
# Verify Fibers patched node authenticity
##echo "Fibers 100% CPU issue patched node authenticity:"
##grep node node-SHASUMS256.txt.asc | shasum -a 256 -c -
##rm -f node-SHASUMS256.txt.asc
##chmod +x node
##mv node `which node`
# DOES NOT WORK: paxctl fix.
# Removed from build-packages: - paxctl
#echo "Applying paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303"
#paxctl -mC `which node`
#echo "Installing npm"
#curl -L https://www.npmjs.com/install.sh | sh
echo "Installing meteor"
curl https://install.meteor.com/ -o install_meteor.sh
#sed -i "s|RELEASE=.*|RELEASE=\"1.8.1-beta.0\"|g" install_meteor.sh
chmod +x install_meteor.sh
sh install_meteor.sh
rm install_meteor.sh
# REPOS BELOW ARE INCLUDED TO WEKAN REPO
#if [ ! -d "packages" ]; then
# mkdir packages
#fi
#if [ ! -d "packages/kadira-flow-router" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router
# cd ..
#fi
#if [ ! -d "packages/meteor-useraccounts-core" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core
# sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' meteor-useraccounts-core/package.js
# cd ..
#fi
#if [ ! -d "packages/meteor-accounts-cas" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git meteor-accounts-cas
# cd ..
#fi
#if [ ! -d "packages/wekan-ldap" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git
# cd ..
#fi
#if [ ! -d "packages/wekan-scrollbar" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git
# cd ..
#fi
#if [ ! -d "packages/wekan_accounts-oidc" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git
# mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan-accounts-oidc
# mv meteor-accounts-oidc/packages/switch_oidc wekan-oidc
# rm -rf meteor-accounts-oidc
# cd ..
#fi
#if [ ! -d "packages/markdown" ]; then
# cd packages
# git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git
# cd ..
#fi
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
#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
#cd .build/bundle/programs/server/npm/node_modules/meteor/npm-bcrypt
#rm -rf node_modules/bcrypt
#meteor npm install --save bcrypt
# Change from npm-bcrypt directory back to .build/bundle/programs/server directory.
#cd ../../../../
# Change to directory .build/bundle/programs/server
cd .build/bundle/programs/server
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 -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:
- -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
-198
View File
@@ -1,198 +0,0 @@
#!/bin/bash
echo "Note: If you use other locale than en_US.UTF-8 , you need to additionally install en_US.UTF-8"
echo " with 'sudo dpkg-reconfigure locales' , so that MongoDB works correctly."
echo " You can still use any other locale as your main locale."
#Below script installs newest node 8.x for Debian/Ubuntu/Mint.
#NODE_VERSION=12.21.0
#X64NODE="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz"
function pause(){
read -p "$*"
}
function cprec(){
if [[ -d "$1" ]]; then
if [[ ! -d "$2" ]]; then
sudo mkdir -p "$2"
fi
for i in $(ls -A "$1"); do
cprec "$1/$i" "$2/$i"
done
else
sudo cp "$1" "$2"
fi
}
# sudo npm doesn't work right, so this is a workaround
function npm_call(){
TMPDIR="/tmp/tmp_npm_prefix"
if [[ -d "$TMPDIR" ]]; then
rm -rf $TMPDIR
fi
mkdir $TMPDIR
NPM_PREFIX="$(npm config get prefix)"
npm config set prefix $TMPDIR
npm "$@"
npm config set prefix "$NPM_PREFIX"
echo "Moving files to $NPM_PREFIX"
for i in $(ls -A $TMPDIR); do
cprec "$TMPDIR/$i" "$NPM_PREFIX/$i"
done
rm -rf $TMPDIR
}
#function wekan_repo_check(){
## UNCOMMENTING, IT'S NOT REQUIRED THAT /HOME/USERNAME IS /HOME/WEKAN
# git_remotes="$(git remote show 2>/dev/null)"
# res=""
# for i in $git_remotes; do
# res="$(git remote get-url $i | sed 's/.*wekan\/wekan.*/wekan\/wekan/')"
# if [[ "$res" == "wekan/wekan" ]]; then
# break
# fi
# done
#
# if [[ "$res" != "wekan/wekan" ]]; then
# echo "$PWD is not a wekan repository"
# exit;
# fi
#}
echo
PS3='Please enter your choice: '
options=("Install Wekan dependencies" "Build Wekan" "Run Meteor for dev on http://localhost:4000" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000" "Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT" "Quit")
select opt in "${options[@]}"
do
case $opt in
"Install Wekan dependencies")
if [[ "$OSTYPE" == "linux-gnu" ]]; then
echo "Linux";
# Debian, Ubuntu, Mint
sudo apt-get install -y build-essential gcc g++ make git curl wget
# npm nodejs
#sudo npm -g install npm
curl -0 -L https://npmjs.org/install.sh | sudo sh
sudo chown -R $(id -u):$(id -g) $HOME/.npm
sudo npm -g install n
sudo n 12.21.0
#curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
#sudo apt-get install -y nodejs
elif [[ "$OSTYPE" == "darwin"* ]]; then
echo "macOS";
pause '1) Install XCode 2) Install Node 8.x from https://nodejs.org/en/ 3) Press [Enter] key to continue.'
elif [[ "$OSTYPE" == "cygwin" ]]; then
# POSIX compatibility layer and Linux environment emulation for Windows
echo "TODO: Add Cygwin";
exit;
elif [[ "$OSTYPE" == "msys" ]]; then
# Lightweight shell and GNU utilities compiled for Windows (part of MinGW)
echo "TODO: Add msys on Windows";
exit;
elif [[ "$OSTYPE" == "win32" ]]; then
# I'm not sure this can happen.
echo "TODO: Add Windows";
exit;
elif [[ "$OSTYPE" == "freebsd"* ]]; then
echo "TODO: Add FreeBSD";
exit;
else
echo "Unknown"
echo ${OSTYPE}
exit;
fi
## Latest npm with Meteor 1.8.x
npm_call -g install npm
npm_call -g install node-gyp
# Latest fibers for Meteor 1.8.x
sudo mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp
npm_call -g install fibers
# Install Meteor, if it's not yet installed
curl https://install.meteor.com | bash
sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor
break
;;
"Build Wekan")
echo "Building Wekan."
#wekan_repo_check
# REPOS BELOW ARE INCLUDED TO WEKAN REPO
#rm -rf packages/kadira-flow-router packages/meteor-useraccounts-core packages/meteor-accounts-cas packages/wekan-ldap packages/wekan-ldap packages/wekan-scrfollbar packages/meteor-accounts-oidc packages/markdown
#mkdir packages
#cd packages
#git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router
#git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core
#git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git
#git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git
#git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git
#git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git
#git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git
#mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan_accounts-oidc
#mv meteor-accounts-oidc/packages/switch_oidc wekan_oidc
#rm -rf meteor-accounts-oidc
#if [[ "$OSTYPE" == "darwin"* ]]; then
# echo "sed at macOS";
# sed -i '' 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js
#else
# echo "sed at ${OSTYPE}"
# sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js
#fi
#cd ..
sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor
rm -rf node_modules .meteor/local
npm install
rm -rf .build
meteor build .build --directory
cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
rm -rf .build/bundle/programs/web.browser.legacy
#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
#cd ~/repos/wekan/.build/bundle/programs/server/npm/node_modules/meteor/npm-bcrypt
#rm -rf node_modules/bcrypt
#meteor npm install bcrypt
cd .build/bundle/programs/server
rm -rf node_modules
npm install
#meteor npm install bcrypt
cd ../../../..
echo Done.
break
;;
"Run Meteor for dev on http://localhost:4000")
WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:4000 meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000
break
;;
"Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000")
IPADDRESS=$(ip a | grep 'noprefixroute' | grep 'inet ' | cut -d: -f2 | awk '{ print $2}' | cut -d '/' -f 1)
echo "Your IP address is $IPADDRESS"
WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:4000 meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000
break
;;
"Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT")
ip address
echo "From above list, what is your IP address?"
read IPADDRESS
echo "On what port you would like to run Wekan?"
read PORT
echo "ROOT_URL=http://$IPADDRESS:$PORT"
WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:$PORT meteor run --exclude-archs web.browser.legacy,web.cordova --port $PORT
break
;;
"Quit")
break
;;
*) echo invalid option;;
esac
done
-155
View File
@@ -1,155 +0,0 @@
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.22.3
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
-3
View File
@@ -1,3 +0,0 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
-3
View File
@@ -1,3 +0,0 @@
# These are supported funding model platforms
custom: ['https://wekan.team/commercial-support/']
+5 -16
View File
@@ -1,25 +1,14 @@
## Issue
Note: With Docker, please don't use latest tag. Only use release tags.
See https://github.com/wekan/wekan/issues/3874
If you can not login for any reason:
- https://github.com/wekan/wekan/wiki/Forgot-Password
Email settings:
- https://github.com/wekan/wekan/wiki/Troubleshooting-Mail
Add these issues to elsewhere:
- SECURITY ISSUES: https://github.com/wekan/wekan/blob/master/SECURITY.md
- UCS: https://github.com/wekan/univention/issues
- Snap: https://github.com/wekan/wekan-snap/issues
Other Wekan issues can be added here.
**Server Setup Information**:
* Note: Please anonymize info, and do not add to this public issue any of your Wekan board URLs, passwords, API tokens etc, do you understand?:
* Did you test in newest Wekan?:
* For new Wekan install, did you configure root-url correctly so Wekan cards open correctly https://github.com/wekan/wekan/wiki/Settings ?
* For new Wekan install, did you configure root-url correctly https://github.com/wekan/wekan/wiki/Settings ?
* Wekan version:
* If this is about old version of Wekan, what upgrade problem you have?:
* Operating System:
@@ -27,12 +16,12 @@ Other Wekan issues can be added here.
* Http frontend if any (Caddy, Nginx, Apache, see config examples from Wekan GitHub wiki first):
* Node Version:
* MongoDB Version:
* Wekan only works on newest desktop Firefox/Chromium/Chrome/Edge/Chromium Edge and mobile Chrome. What webbrowser version are you using?
* ROOT_URL environment variable http(s)://(subdomain).example.com(/suburl):
**Problem description**:
- *REQUIRED: Add recorded animated gif about how it works currently, and screenshot mockups how it should work. Use peek to record animgif in Linux https://github.com/phw/peek*
- *Explain steps how to reproduce*
- *In webbrowser, what does show Right Click / Inspect / Console ? Chrome shows more detailed info than Firefox.*
- *If using Snap, what does show command `sudo snap logs wekan.wekan` ? Please anonymize logs.*
- *If using Docker, what does show command `sudo docker logs wekan-app` ? Please anonymize logs.*
- *If using Snap, what does show command `sudo snap logs wekan.wekan` ?*
- *If using Docker, what does show command `sudo docker logs wekan-app` ?*
- *If logs are very long, attach them in .zip file*
-62
View File
@@ -1,62 +0,0 @@
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 16 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript', 'python']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
-17
View File
@@ -1,17 +0,0 @@
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)
-160
View File
@@ -1,160 +0,0 @@
name: Test suite
on:
push:
branches:
- master
pull_request:
jobs:
# the following are optional jobs and need to be configured according
# to this project's settings:
#
# lintcode:
# name: Javascript lint
# runs-on: ubuntu-latest
# steps:
# - name: checkout
# uses: actions/checkout@v2
#
# - name: setup node
# uses: actions/setup-node@v1
# with:
# node-version: '12.x'
#
# - name: cache dependencies
# uses: actions/cache@v1
# with:
# path: ~/.npm
# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-node-
#
# - run: npm install
# - run: npm run lint:code
#
# lintstyle:
# name: SCSS lint
# runs-on: ubuntu-latest
# needs: [lintcode]
# steps:
# - name: checkout
# uses: actions/checkout@v2
#
# - name: setup node
# uses: actions/setup-node@v1
# with:
# node-version: '12.x'
#
# - name: cache dependencies
# uses: actions/cache@v1
# with:
# path: ~/.npm
# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-node-
# - run: npm install
# - run: npm run lint:style
#
# lintdocs:
# name: documentation lint
# runs-on: ubuntu-latest
# needs: [lintcode,lintstyle]
# steps:
# - name: checkout
# uses: actions/checkout@v2
#
# - name: setup node
# uses: actions/setup-node@v1
# with:
# node-version: '12.x'
#
# - name: cache dependencies
# uses: actions/cache@v1
# with:
# path: ~/.npm
# key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
# restore-keys: |
# ${{ runner.os }}-node-
#
# - run: npm install
# - run: npm run lint:markdown
tests:
name: Meteor ${{ matrix.meteor }} tests
runs-on: ubuntu-latest
steps:
# CHECKOUTS
- name: Checkout
uses: actions/checkout@v2
# CACHING
- name: Install Meteor
id: cache-meteor-install
uses: actions/cache@v2
with:
path: ~/.meteor
key: v1-meteor-${{ hashFiles('.meteor/versions') }}
restore-keys: |
v1-meteor-
- name: Cache NPM dependencies
id: cache-meteor-npm
uses: actions/cache@v2
with:
path: ~/.npm
key: v1-npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
v1-npm-
- name: Cache Meteor build
id: cache-meteor-build
uses: actions/cache@v2
with:
path: |
.meteor/local/resolver-result-cache.json
.meteor/local/plugin-cache
.meteor/local/isopacks
.meteor/local/bundler-cache/scanner
key: v1-meteor_build_cache-${{ github.ref }}-${{ github.sha }}
restore-key: |
v1-meteor_build_cache-
- name: Setup meteor
uses: meteorengineer/setup-meteor@v1
with:
meteor-release: '2.2'
- name: Install NPM Dependencies
run: meteor npm ci
- name: Run Tests
run: sh ./test-wekan.sh -cv
- name: Upload coverage
uses: actions/upload-artifact@v2
with:
name: coverage-folder
path: .coverage/
coverage:
name: Coverage report
runs-on: ubuntu-latest
needs: [tests]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Download coverage
uses: actions/download-artifact@v2
with:
name: coverage-folder
path: .coverage/
- name: Coverage Report
uses: VeryGoodOpenSource/very_good_coverage@v1.1.1
with:
path: ".coverage/lcov.info"
min_coverage: 1 # TODO add tests and increase to 95!
+1 -6
View File
@@ -5,10 +5,10 @@
tmp/
node_modules/
npm-debug.log
.gitmodules
.vscode/
.idea/
.build/*
package-lock.json
**/parts/
**/stage
**/prime
@@ -29,8 +29,3 @@ versions.json
.Trashes
Thumbs.db
ehthumbs.db
.eslintcache
.meteor/local
.devcontainer/docker-compose.extend.yml
.devcontainer/volumes*/
.coverage
-10
View File
@@ -1,10 +0,0 @@
FROM gitpod/workspace-mongodb
USER gitpod
# Install custom tools, runtime, etc. using apt-get
# For example, the command below would install "bastet" - a command line tetris clone:
#
# RUN sudo apt-get -q update && # sudo apt-get install -yq bastet && # sudo rm -rf /var/lib/apt/lists/*
#
# More information: https://www.gitpod.io/docs/config-docker/
-4
View File
@@ -1,4 +0,0 @@
tasks:
- init: npm install
image:
file: .gitpod.Dockerfile
-2
View File
@@ -16,5 +16,3 @@ notices-for-facebook-graph-api-2
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base
1.8.3-split-jquery-from-blaze
+30 -85
View File
@@ -3,27 +3,27 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base@1.4.0
meteor-base@1.2.0
# Build system
ecmascript@0.15.1
standard-minifier-css@1.7.2
standard-minifier-js@2.6.0
ecmascript
stylus@2.513.13
standard-minifier-css@1.3.5
standard-minifier-js@2.2.0
mquandalle:jade
coffeescript@2.4.1!
# Polyfills
es5-shim@4.8.0
es5-shim@4.6.15
# Collections
aldeed:collection2
wekan-cfs-standard-packages
cfs:standard-packages
cottz:publish-relations
dburles:collection-helpers
idmontie:migrations
matb33:collection-hooks
matteodem:easy-search
mongo@1.11.0
mongo@1.3.1
mquandalle:collection-mutations
# Account system
@@ -36,23 +36,26 @@ wekan-accounts-cas
wekan-accounts-oidc
# Utilities
check@1.3.1
check@1.2.5
jquery@1.11.10
random@1.2.0
reactive-dict@1.3.0
session@1.2.0
tracker@1.2.0
random@1.0.10
reactive-dict@1.2.0
session@1.1.7
tracker@1.1.3
underscore@1.0.10
3stack:presence
alethes:pages
arillo:flow-router-helpers
audit-argument-checks@1.0.7
kadira:blaze-layout
kadira:dochead
meteorhacks:picker
meteorhacks:subs-manager
mquandalle:autofocus
ongoworks:speakingurl
raix:handlebar-helpers
tap:i18n
http@1.4.2
http@1.3.0
# UI components
blaze
@@ -67,83 +70,25 @@ templates:tabs
verron:autosize
simple:json-routes
rajit:bootstrap3-datepicker
shell-server@0.5.0
shell-server@0.3.0
simple:rest-accounts-password
useraccounts:core
email@2.0.0
email@1.2.3
horka:swipebox
dynamic-import@0.6.0
dynamic-import@0.2.0
staringatlights:fast-render
accounts-password@1.7.0
wekan-cfs-gridfs
mixmax:smart-disconnect
accounts-password@1.5.0
cfs:gridfs
rzymek:fullcalendar
momentjs:moment@2.22.2
browser-policy-framing@1.1.0
browser-policy-framing
mquandalle:moment
msavin:usercache
# Keep stylus in 1.1.0, because building v2 takes extra 52 minutes.
coagmano:stylus@1.1.0!
meteorhacks:subs-manager
meteorhacks:picker
lamhieu:unblock
meteorhacks:aggregate@1.3.0
wekan-scrollbar
mquandalle:perfect-scrollbar
mdg:meteor-apm-agent
meteorhacks:unblock
lucasantoniassi:accounts-lockout
wekan-markdown
konecty:mongo-counter
percolate:synced-cron
wekan-cfs-filesystem
steffo:meteor-accounts-saml
rajit:bootstrap3-datepicker-fi
rajit:bootstrap3-datepicker-ar
rajit:bootstrap3-datepicker-bg
rajit:bootstrap3-datepicker-br
rajit:bootstrap3-datepicker-ca
rajit:bootstrap3-datepicker-cs
rajit:bootstrap3-datepicker-da
rajit:bootstrap3-datepicker-de
rajit:bootstrap3-datepicker-el
rajit:bootstrap3-datepicker-en-gb
rajit:bootstrap3-datepicker-eo
rajit:bootstrap3-datepicker-es
rajit:bootstrap3-datepicker-eu
rajit:bootstrap3-datepicker-fa
rajit:bootstrap3-datepicker-fr
rajit:bootstrap3-datepicker-gl
rajit:bootstrap3-datepicker-he
rajit:bootstrap3-datepicker-hi
rajit:bootstrap3-datepicker-hu
rajit:bootstrap3-datepicker-hy
rajit:bootstrap3-datepicker-id
rajit:bootstrap3-datepicker-it
rajit:bootstrap3-datepicker-ja
rajit:bootstrap3-datepicker-ka
rajit:bootstrap3-datepicker-km
rajit:bootstrap3-datepicker-ko
rajit:bootstrap3-datepicker-lv
rajit:bootstrap3-datepicker-mk
rajit:bootstrap3-datepicker-mn
rajit:bootstrap3-datepicker-nb
rajit:bootstrap3-datepicker-nl
rajit:bootstrap3-datepicker-oc
rajit:bootstrap3-datepicker-pl
rajit:bootstrap3-datepicker-pt-br
rajit:bootstrap3-datepicker-pt
rajit:bootstrap3-datepicker-ro
rajit:bootstrap3-datepicker-ru
rajit:bootstrap3-datepicker-sl
rajit:bootstrap3-datepicker-sr
rajit:bootstrap3-datepicker-sv
rajit:bootstrap3-datepicker-sw
rajit:bootstrap3-datepicker-ta
rajit:bootstrap3-datepicker-th
rajit:bootstrap3-datepicker-tr
rajit:bootstrap3-datepicker-uk
rajit:bootstrap3-datepicker-vi
rajit:bootstrap3-datepicker-zh-cn
rajit:bootstrap3-datepicker-zh-tw
staringatlights:fast-render
spacebars
easylogic:summernote
pascoual:pdfkit
wekan-accounts-lockout
lmieulet:meteor-coverage
meteortesting:mocha
+1 -1
View File
@@ -1 +1 @@
METEOR@2.2
METEOR@1.6.0.1
+112 -174
View File
@@ -1,104 +1,113 @@
3stack:presence@1.1.2
accounts-base@1.9.0
accounts-oauth@1.2.0
accounts-password@1.7.1
accounts-base@1.4.0
accounts-oauth@1.1.15
accounts-password@1.5.0
aldeed:collection2@2.10.0
aldeed:collection2-core@1.2.0
aldeed:schema-deny@1.1.0
aldeed:schema-index@1.1.1
aldeed:simple-schema@1.5.4
aldeed:simple-schema@1.5.3
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.7.0
babel-compiler@7.6.1
babel-runtime@1.5.0
base64@1.0.12
binary-heap@1.0.11
blaze@2.5.0
blaze-tools@1.1.2
boilerplate-generator@1.7.1
autoupdate@1.3.12
babel-compiler@6.24.7
babel-runtime@1.1.1
base64@1.0.10
binary-heap@1.0.10
blaze@2.3.2
blaze-tools@1.0.10
boilerplate-generator@1.3.1
browser-policy-common@1.0.11
browser-policy-framing@1.1.0
caching-compiler@1.2.2
caching-html-compiler@1.2.0
callback-hook@1.3.0
caching-compiler@1.1.9
caching-html-compiler@1.1.2
callback-hook@1.0.10
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:gridfs@0.0.34
cfs:http-methods@0.0.32
check@1.3.1
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.9
cfs:storage-adapter@0.2.3
cfs:tempstore@0.1.5
cfs:upload-http@0.0.20
cfs:worker@0.1.4
check@1.2.5
chuangbo:cookie@1.1.0
coagmano:stylus@1.1.0
coffeescript@2.4.1
coffeescript-compiler@2.4.1
coffeescript@1.12.7_3
coffeescript-compiler@1.12.7_3
cottz:publish-relations@2.0.8
dburles:collection-helpers@1.1.0
ddp@1.4.0
ddp-client@2.4.1
ddp-common@1.4.0
ddp-rate-limiter@1.0.9
ddp-server@2.3.3
ddp-client@2.2.0
ddp-common@1.3.0
ddp-rate-limiter@1.0.7
ddp-server@2.1.1
deps@1.0.12
diff-sequence@1.1.1
dynamic-import@0.6.0
easylogic:summernote@0.8.8
ecmascript@0.15.1
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.11.1
ecmascript-runtime-server@0.10.1
ejson@1.1.1
email@2.0.0
es5-shim@4.8.0
diff-sequence@1.0.7
dynamic-import@0.2.1
ecmascript@0.9.0
ecmascript-runtime@0.5.0
ecmascript-runtime-client@0.5.0
ecmascript-runtime-server@0.5.0
ejson@1.1.0
email@1.2.3
es5-shim@4.6.15
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.1.2
htmljs@1.1.1
http@1.4.4
id-map@1.1.1
html-tools@1.0.11
htmljs@1.0.11
http@1.3.0
id-map@1.0.9
idmontie:migrations@1.0.3
inter-process-messaging@0.1.1
jquery@1.11.11
jquery@1.11.10
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.2.1
launch-screen@1.1.1
livedata@1.0.18
lmieulet:meteor-coverage@3.2.0
localstorage@1.2.0
logging@1.2.0
matb33:collection-hooks@0.9.1
logging@1.1.19
lucasantoniassi:accounts-lockout@1.0.0
matb33:collection-hooks@0.8.4
matteodem:easy-search@1.6.4
mdg:meteor-apm-agent@3.1.2
mdg:validation-error@0.5.1
meteor@1.9.3
meteor-base@1.4.0
meteor@1.8.2
meteor-base@1.2.0
meteor-platform@1.2.6
meteorhacks:aggregate@1.3.0
meteorhacks:collection-utils@1.2.0
meteorhacks:meteorx@1.4.1
meteorhacks:picker@1.0.3
meteorhacks:subs-manager@1.6.4
meteorhacks:unblock@1.1.0
meteorspark:util@0.2.0
meteortesting:browser-tests@1.3.4
meteortesting:mocha@2.0.1
meteortesting:mocha-core@8.0.1
minifier-css@1.5.4
minifier-js@2.6.0
minifier-css@1.2.16
minifier-js@2.2.2
minifiers@1.1.8-faster-rebuild.0
minimongo@1.6.2
mobile-status-bar@1.1.0
modern-browsers@0.1.5
modules@0.16.0
modules-runtime@0.12.0
momentjs:moment@2.29.1
mongo@1.11.1
mongo-decimal@0.1.2
minimongo@1.4.3
mixmax:smart-disconnect@0.0.4
mobile-status-bar@1.0.14
modules@0.11.0
modules-runtime@0.9.1
momentjs:moment@2.22.2
mongo@1.3.1
mongo-dev-server@1.1.0
mongo-id@1.0.8
mongo-id@1.0.6
mongo-livedata@1.0.12
mousetrap:mousetrap@1.4.6_1
mquandalle:autofocus@1.0.0
@@ -109,142 +118,71 @@ 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
msavin:usercache@1.8.0
npm-bcrypt@0.9.4
npm-mongo@3.9.0
oauth@1.3.2
oauth2@1.3.0
mquandalle:perfect-scrollbar@0.6.5_2
msavin:usercache@1.0.0
npm-bcrypt@0.9.3
npm-mongo@2.2.33
oauth@1.2.1
oauth2@1.2.0
observe-sequence@1.0.16
ongoworks:speakingurl@1.1.0
ordered-dict@1.1.0
pascoual:pdfkit@1.0.7
peerlibrary:assert@0.3.0
ordered-dict@1.0.9
peerlibrary:assert@0.2.5
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
peerlibrary:computed-field@0.7.0
peerlibrary:reactive-field@0.3.0
promise@0.10.0
raix:eventemitter@0.1.3
raix:handlebar-helpers@0.2.5
rajit:bootstrap3-datepicker@1.7.1_1
rajit:bootstrap3-datepicker-ar@1.7.1
rajit:bootstrap3-datepicker-bg@1.7.1
rajit:bootstrap3-datepicker-br@1.7.1
rajit:bootstrap3-datepicker-ca@1.7.1
rajit:bootstrap3-datepicker-cs@1.7.1
rajit:bootstrap3-datepicker-da@1.7.1
rajit:bootstrap3-datepicker-de@1.7.1
rajit:bootstrap3-datepicker-el@1.7.1
rajit:bootstrap3-datepicker-en-gb@1.7.1
rajit:bootstrap3-datepicker-eo@1.7.1
rajit:bootstrap3-datepicker-es@1.7.1
rajit:bootstrap3-datepicker-eu@1.7.1
rajit:bootstrap3-datepicker-fa@1.7.1
rajit:bootstrap3-datepicker-fi@1.7.1
rajit:bootstrap3-datepicker-fr@1.7.1
rajit:bootstrap3-datepicker-gl@1.7.1
rajit:bootstrap3-datepicker-he@1.7.1
rajit:bootstrap3-datepicker-hi@1.7.1
rajit:bootstrap3-datepicker-hu@1.7.1
rajit:bootstrap3-datepicker-hy@1.7.1
rajit:bootstrap3-datepicker-id@1.7.1
rajit:bootstrap3-datepicker-it@1.7.1
rajit:bootstrap3-datepicker-ja@1.7.1
rajit:bootstrap3-datepicker-ka@1.7.1
rajit:bootstrap3-datepicker-km@1.7.1
rajit:bootstrap3-datepicker-ko@1.7.1
rajit:bootstrap3-datepicker-lv@1.7.1
rajit:bootstrap3-datepicker-mk@1.7.1
rajit:bootstrap3-datepicker-mn@1.7.1
rajit:bootstrap3-datepicker-nb@1.7.1
rajit:bootstrap3-datepicker-nl@1.7.1
rajit:bootstrap3-datepicker-oc@1.7.1
rajit:bootstrap3-datepicker-pl@1.7.1
rajit:bootstrap3-datepicker-pt@1.7.1
rajit:bootstrap3-datepicker-pt-br@1.7.1
rajit:bootstrap3-datepicker-ro@1.7.1
rajit:bootstrap3-datepicker-ru@1.7.1
rajit:bootstrap3-datepicker-sl@1.7.1
rajit:bootstrap3-datepicker-sr@1.7.1
rajit:bootstrap3-datepicker-sv@1.7.1
rajit:bootstrap3-datepicker-sw@1.7.1
rajit:bootstrap3-datepicker-ta@1.7.1
rajit:bootstrap3-datepicker-th@1.7.1
rajit:bootstrap3-datepicker-tr@1.7.1
rajit:bootstrap3-datepicker-uk@1.7.1
rajit:bootstrap3-datepicker-vi@1.7.1
rajit:bootstrap3-datepicker-zh-cn@1.7.1
rajit:bootstrap3-datepicker-zh-tw@1.7.1
random@1.2.0
rate-limit@1.0.9
react-fast-refresh@0.1.1
reactive-dict@1.3.0
rajit:bootstrap3-datepicker@1.7.1
random@1.0.10
rate-limit@1.0.8
reactive-dict@1.2.0
reactive-var@1.0.11
reload@1.3.1
retry@1.1.0
routepolicy@1.1.0
reload@1.1.11
retry@1.0.9
routepolicy@1.0.12
rzymek:fullcalendar@3.8.0
server-render@0.3.1
service-configuration@1.0.11
session@1.2.0
session@1.1.7
sha@1.0.9
shell-server@0.5.0
shell-server@0.3.1
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.3.3
softwarerero:accounts-t9n@1.3.11
spacebars@1.2.0
spacebars-compiler@1.2.1
srp@1.1.0
standard-minifier-css@1.7.2
standard-minifier-js@2.6.0
staringatlights:fast-render@3.3.0
staringatlights:inject-data@2.3.0
steffo:meteor-accounts-saml@0.0.18
spacebars@1.0.15
spacebars-compiler@1.1.3
srp@1.0.10
standard-minifier-css@1.3.5
standard-minifier-js@2.2.3
staringatlights:fast-render@2.16.5
staringatlights:inject-data@2.0.5
stylus@2.513.13
tap:i18n@1.8.2
templates:tabs@2.3.0
templating@1.4.0
templating-compiler@1.4.1
templating-runtime@1.4.0
templating-tools@1.2.0
tracker@1.2.0
twbs:bootstrap@3.3.6
templating@1.3.2
templating-compiler@1.3.3
templating-runtime@1.3.2
templating-tools@1.1.2
tracker@1.1.3
ui@1.0.13
underscore@1.0.10
url@1.3.2
url@1.1.0
useraccounts:core@1.14.2
useraccounts:flow-routing@1.14.2
useraccounts:unstyled@1.14.2
verron:autosize@3.0.8
webapp@1.10.1
webapp-hashing@1.1.0
wekan-accounts-cas@0.1.0
wekan-accounts-lockout@1.0.0
webapp@1.4.0
webapp-hashing@1.0.9
wekan-accounts-oidc@1.0.10
wekan-cfs-access-point@0.1.50
wekan-cfs-base-package@0.0.30
wekan-cfs-collection@0.5.5
wekan-cfs-collection-filters@0.2.4
wekan-cfs-data-man@0.0.6
wekan-cfs-file@0.1.17
wekan-cfs-filesystem@0.1.2
wekan-cfs-gridfs@0.0.34
wekan-cfs-http-methods@0.0.32
wekan-cfs-http-publish@0.0.13
wekan-cfs-power-queue@0.9.11
wekan-cfs-reactive-list@0.0.9
wekan-cfs-reactive-property@0.0.4
wekan-cfs-standard-packages@0.5.10
wekan-cfs-storage-adapter@0.2.4
wekan-cfs-tempstore@0.1.6
wekan-cfs-upload-http@0.0.21
wekan-cfs-worker@0.1.5
wekan-ldap@0.0.2
wekan-markdown@1.0.9
wekan-markdown@1.0.7
wekan-oidc@1.0.12
wekan-scrollbar@3.1.3
wekan-accounts-cas@0.1.0
wekan-ldap@0.0.2
yasaricli:slugify@0.0.7
zimme:active-route@2.3.2
-13
View File
@@ -1,13 +0,0 @@
dependencies:
- libreadline-dev
- libssl-dev
- bsdtar
targets:
ubuntu-18.04:
ubuntu-16.04:
ubuntu-14.04:
centos-6:
centos-7:
debian-10:
sles-12:
sles-11:
-8
View File
@@ -1,8 +0,0 @@
packages/
node_modules/
.build/
.meteor/
.vscode/
.tx/
.github/
.snap-meteor-1.8/
-8
View File
@@ -1,8 +0,0 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "all"
}
+4 -4
View File
@@ -1,10 +1,10 @@
dist: focal
dist: trusty
sudo: required
env:
TRAVIS_DOCKER_COMPOSE_VERSION: 1.24.0
TRAVIS_NODE_VERSION: 12.22.3
TRAVIS_NPM_VERSION: latest
TRAVIS_DOCKER_COMPOSE_VERSION: 1.17.0
TRAVIS_NODE_VERSION: 8.9.3
TRAVIS_NPM_VERSION: 5.5.1
before_install:
- sudo apt-get update -y
+1 -1
View File
@@ -39,7 +39,7 @@ host = https://www.transifex.com
# tap:i18n requires us to use `-` separator in the language identifiers whereas
# Transifex uses a `_` separator, without an option to customize it on one side
# or the other, so we need to do a Manual mapping.
lang_map = ar_EG:ar-EG, bg_BG:bg, de_CH:de-CH, en_IT:en-IT, en_GB:en-GB, es_AR:es-AR, es_CL:es-CL, es_419:es-LA, es_PE:es-PE, es_MX:es-MX, es_TX:es-TX, es_PY:es-PY, el_GR:el, fa_IR:fa-IR, fi_FI:fi, hu_HU:hu, id_ID:id, mn_MN:mn, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, sl_SI:sl, zh_CN:zh-CN, zh_TW:zh-TW, zh_HK:zh-HK
lang_map = bg_BG:bg, en_GB:en-GB, es_AR:es-AR, el_GR:el, fi_FI:fi, hu_HU:hu, id_ID:id, mn_MN:mn, no:nb, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, zh_CN:zh-CN, zh_TW:zh-TW, zh_HK:zh-HK
[wekan.application]
file_filter = i18n/<lang>.i18n.json
-28
View File
@@ -1,28 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Meteor: Chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
},
{
"type": "node",
"request": "launch",
"name": "Meteor: Node",
"runtimeExecutable": "/home/wekan/.meteor/meteor",
"runtimeArgs": ["run", "--inspect-brk=9229"],
"outputCapture": "std",
"port": 9229,
"timeout": 60000
}
],
"compounds": [
{
"name": "Meteor: All",
"configurations": ["Meteor: Node", "Meteor: Chrome"]
}
]
}
+9 -4456
View File
File diff suppressed because it is too large. Load diff
+42 -98
View File
@@ -1,42 +1,26 @@
FROM quay.io/wekan/ubuntu:groovy-20210115
FROM ubuntu:disco
LABEL maintainer="wekan"
# 2020-12-03:
# - Above Ubuntu base image copied from Docker Hub ubuntu:groovy-20201125.2
# to Quay to avoid Docker Hub rate limits.
# Set the environment variables (defaults where required)
# DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
# ENV BUILD_DEPS="paxctl"
ARG DEBIAN_FRONTEND=noninteractive
ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates python3" \
ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential python3 python3-pip git ca-certificates gcc-8" \
DEBUG=false \
NODE_VERSION=v12.22.3 \
METEOR_RELEASE=1.10.2 \
NODE_VERSION=v8.16.0 \
METEOR_RELEASE=1.6.0.1 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
NPM_VERSION=latest \
FIBERS_VERSION=4.0.1 \
FIBERS_VERSION=2.0.0 \
ARCHITECTURE=linux-x64 \
SRC_PATH=./ \
WITH_API=true \
RESULTS_PER_PAGE="" \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \
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=false \
CARD_OPENED_WEBHOOK_ENABLED=false \
ATTACHMENTS_STORE_PATH="" \
MAX_IMAGE_PIXEL="" \
IMAGE_COMPRESS_RATIO="" \
NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="" \
BIGEVENTS_PATTERN=NONE \
NOTIFY_DUE_DAYS_BEFORE_AND_AFTER="" \
NOTIFY_DUE_AT_HOUR_OF_DAY="" \
EMAIL_NOTIFICATION_TIMEOUT=30000 \
MATOMO_ADDRESS="" \
MATOMO_SITE_ID="" \
@@ -46,8 +30,6 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-
TRUSTED_URL="" \
WEBHOOKS_ATTRIBUTES="" \
OAUTH2_ENABLED=false \
OAUTH2_CA_CERT="" \
OAUTH2_ADFS_ENABLED=false \
OAUTH2_LOGIN_STYLE=redirect \
OAUTH2_CLIENT_ID="" \
OAUTH2_SECRET="" \
@@ -64,9 +46,6 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-
LDAP_ENABLE=false \
LDAP_PORT=389 \
LDAP_HOST="" \
LDAP_AD_SIMPLE_AUTH="" \
LDAP_USER_AUTHENTICATION=false \
LDAP_USER_AUTHENTICATION_FIELD=uid \
LDAP_BASEDN="" \
LDAP_LOGIN_FALLBACK=false \
LDAP_RECONNECT=true \
@@ -78,12 +57,13 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-
LDAP_AUTHENTIFICATION_PASSWORD="" \
LDAP_LOG_ENABLED=false \
LDAP_BACKGROUND_SYNC=false \
LDAP_BACKGROUND_SYNC_INTERVAL="" \
LDAP_BACKGROUND_SYNC_INTERVAL=100 \
LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false \
LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false \
LDAP_ENCRYPTION=false \
LDAP_CA_CERT="" \
LDAP_REJECT_UNAUTHORIZED=false \
LDAP_USER_AUTHENTICATION=false \
LDAP_USER_SEARCH_FILTER="" \
LDAP_USER_SEARCH_SCOPE="" \
LDAP_USER_SEARCH_FIELD="" \
@@ -121,25 +101,7 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-
CORS="" \
CORS_ALLOW_HEADERS="" \
CORS_EXPOSE_HEADERS="" \
DEFAULT_AUTHENTICATION_METHOD="" \
PASSWORD_LOGIN_ENABLED=true \
CAS_ENABLED=false \
CAS_BASE_URL="" \
CAS_LOGIN_URL="" \
CAS_VALIDATE_URL="" \
SAML_ENABLED=false \
SAML_PROVIDER="" \
SAML_ENTRYPOINT="" \
SAML_ISSUER="" \
SAML_CERT="" \
SAML_IDPSLO_REDIRECTURL="" \
SAML_PRIVATE_KEYFILE="" \
SAML_PUBLIC_CERTFILE="" \
SAML_IDENTIFIER_FORMAT="" \
SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE="" \
SAML_ATTRIBUTES="" \
ORACLE_OIM_ENABLED=false \
WAIT_SPINNER=""
DEFAULT_AUTHENTICATION_METHOD=""
# Copy the app to the image
COPY ${SRC_PATH} /home/wekan/app
@@ -151,7 +113,7 @@ RUN \
\
# OS dependencies
apt-get update -y && apt-get install -y --no-install-recommends ${BUILD_DEPS} && \
#pip3 install -U pip setuptools wheel && \
pip3 install -U pip setuptools wheel && \
\
# Meteor installer doesn't work with the default tar binary, so using bsdtar while installing.
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
@@ -207,39 +169,33 @@ RUN \
mv node-${NODE_VERSION}-${ARCHITECTURE} /opt/nodejs && \
ln -s /opt/nodejs/bin/node /usr/bin/node && \
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \
chown wekan --recursive /home/wekan/.config && \
\
#DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
#paxctl -mC `which node` && \
\
# Install Node dependencies. Python path for node-gyp.
# Install Node dependencies
npm install -g npm@${NPM_VERSION} && \
#npm config set python python2.7 && \
#npm install -g node-gyp && \
#npm install -g fibers@${FIBERS_VERSION} && \
npm install -g node-gyp && \
npm install -g fibers@${FIBERS_VERSION} && \
\
# Change user to wekan and install meteor
cd /home/wekan/ && \
chown wekan --recursive /home/wekan && \
#curl "https://install.meteor.com" -o /home/wekan/install_meteor.sh && \
chown wekan:wekan --recursive /home/wekan && \
curl "https://install.meteor.com" -o /home/wekan/install_meteor.sh && \
#curl "https://install.meteor.com/?release=${METEOR_RELEASE}" -o /home/wekan/install_meteor.sh && \
# OLD: sed -i "s|RELEASE=.*|RELEASE=${METEOR_RELEASE}\"\"|g" ./install_meteor.sh && \
# Install Meteor forcing its progress
#sed -i 's/VERBOSITY="--silent"/VERBOSITY="--progress-bar"/' ./install_meteor.sh && \
sed -i 's/VERBOSITY="--silent"/VERBOSITY="--progress-bar"/' ./install_meteor.sh && \
echo "Starting meteor ${METEOR_RELEASE} installation... \n" && \
gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh && \
mv /root/.meteor /home/wekan/ && \
chown wekan --recursive /home/wekan/.meteor && \
chown wekan:wekan /home/wekan/install_meteor.sh && \
\
# Check if opting for a release candidate instead of major release
#if [ "$USE_EDGE" = false ]; then \
#gosu wekan:wekan sh /home/wekan/install_meteor.sh; \
# gosu wekan:wekan curl https://install.meteor.com/ | sh; \
#else \
# gosu wekan:wekan git clone --recursive --depth 1 -b release/METEOR@${METEOR_EDGE} https://github.com/meteor/meteor.git /home/wekan/.meteor; \
#fi; \
#\
if [ "$USE_EDGE" = false ]; then \
gosu wekan:wekan sh /home/wekan/install_meteor.sh; \
else \
gosu wekan:wekan git clone --recursive --depth 1 -b release/METEOR@${METEOR_EDGE} git://github.com/meteor/meteor.git /home/wekan/.meteor; \
fi; \
\
# Get additional packages
#mkdir -p /home/wekan/app/packages && \
#chown wekan:wekan --recursive /home/wekan && \
@@ -260,46 +216,34 @@ RUN \
gosu wekan:wekan /home/wekan/.meteor/meteor -- help; \
\
# extract the OpenAPI specification
#npm install -g api2html@0.3.3 && \
#mkdir -p /home/wekan/python && \
#chown wekan --recursive /home/wekan/python && \
#cd /home/wekan/python && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/Kronuz/esprima-python && \
#cd /home/wekan/python/esprima-python && \
#python3 setup.py install --record files.txt && \
#cd /home/wekan/app && \
#mkdir -p /home/wekan/app/public/api && \
#chown wekan --recursive /home/wekan/app && \
#gosu wekan:wekan python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml && \
#gosu wekan:wekan /opt/nodejs/bin/api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml; \
npm install -g api2html@0.3.3 && \
mkdir -p /home/wekan/python && \
chown wekan:wekan --recursive /home/wekan/python && \
cd /home/wekan/python && \
gosu wekan:wekan git clone --depth 1 -b master https://github.com/Kronuz/esprima-python && \
cd /home/wekan/python/esprima-python && \
python3 setup.py install --record files.txt && \
cd /home/wekan/app &&\
gosu wekan:wekan mkdir -p ./public/api && \
gosu wekan:wekan python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml && \
gosu wekan:wekan /opt/nodejs/bin/api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml; \
# Build app
cd /home/wekan/app && \
mkdir -p /home/wekan/.npm && \
chown wekan --recursive /home/wekan/.npm /home/wekan/.config /home/wekan/.meteor && \
#gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js && \
chmod u+w *.json && \
gosu wekan:wekan npm install && \
gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js && \
gosu wekan:wekan /home/wekan/.meteor/meteor npm install && \
gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build && \
#cp /home/wekan/app/fix-download-unicode/cfs_access-point.txt /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \
#rm /home/wekan/app_build/bundle/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs && \
#chown wekan /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \
cp /home/wekan/app/fix-download-unicode/cfs_access-point.txt /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \
rm /home/wekan/app_build/bundle/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs && \
chown wekan:wekan /home/wekan/app_build/bundle/programs/server/packages/cfs_access-point.js && \
#Removed binary version of bcrypt because of security vulnerability that is not fixed yet.
#https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac
#https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c
#cd /home/wekan/app_build/bundle/programs/server/npm/node_modules/meteor/npm-bcrypt && \
#gosu wekan:wekan rm -rf node_modules/bcrypt && \
#gosu wekan:wekan npm install bcrypt && \
#
# Delete phantomjs
#cd /home/wekan/app_build/bundle && \
#find . -name "*phantomjs*" | xargs rm -rf && \
#
cd /home/wekan/app_build/bundle/programs/server/ && \
chmod u+w *.json && \
gosu wekan:wekan npm install && \
#gosu wekan:wekan npm install bcrypt && \
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy && \
mv /home/wekan/app_build/bundle /build && \
\
# Put back the original tar
@@ -312,10 +256,10 @@ RUN \
rm -R /var/lib/apt/lists/* && \
rm -R /home/wekan/.meteor && \
rm -R /home/wekan/app && \
rm -R /home/wekan/app_build
#cat /home/wekan/python/esprima-python/files.txt | xargs rm -R && \
#rm -R /home/wekan/python
#rm /home/wekan/install_meteor.sh
rm -R /home/wekan/app_build && \
cat /home/wekan/python/esprima-python/files.txt | xargs rm -R && \
rm -R /home/wekan/python && \
rm /home/wekan/install_meteor.sh
ENV PORT=8080
EXPOSE $PORT
-77
View File
@@ -1,77 +0,0 @@
FROM amd64/alpine:3.7 AS builder
# Set the environment variables for builder
ENV QEMU_VERSION=v4.2.0-6 \
QEMU_ARCHITECTURE=aarch64 \
NODE_ARCHITECTURE=linux-arm64 \
NODE_VERSION=v12.22.3 \
WEKAN_VERSION=latest \
WEKAN_ARCHITECTURE=arm64
# Install dependencies
RUN apk update && apk add ca-certificates outils-sha1 && \
\
# Download qemu static for our architecture
wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \
\
# Download wekan and shasum
wget https://releases.wekan.team/raspi3/wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
wget https://releases.wekan.team/raspi3/SHA256SUMS.txt && \
# Verify wekan
grep wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip SHA256SUMS.txt | sha256sum -c - && \
\
# Unzip wekan
unzip wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
\
# Download node and shasums
wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
\
# Verify nodejs authenticity
grep node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz SHASUMS256.txt.asc | sha256sum -c - && \
\
# Extract node and remove tar.gz
tar xvzf node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz
# Build wekan dockerfile
FROM arm64v8/ubuntu:19.10
LABEL maintainer="wekan"
# Set the environment variables (defaults where required)
ENV QEMU_ARCHITECTURE=aarch64 \
NODE_ARCHITECTURE=linux-arm64 \
NODE_VERSION=v12.22.3 \
NODE_ENV=production \
NPM_VERSION=latest \
WITH_API=true \
PORT=8080 \
ROOT_URL=http://localhost \
MONGO_URL=mongodb://127.0.0.1:27017/wekan
# Copy qemu-static to image
COPY --from=builder qemu-${QEMU_ARCHITECTURE}-static /usr/bin
# Copy the app to the image
COPY --from=builder bundle /home/wekan/bundle
# Copy
COPY --from=builder node-${NODE_VERSION}-${NODE_ARCHITECTURE} /opt/nodejs
RUN \
set -o xtrace && \
# Add non-root user wekan
useradd --user-group --system --home-dir /home/wekan wekan && \
\
# Install Node
ln -s /opt/nodejs/bin/node /usr/bin/node && \
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \
chown wekan --recursive /home/wekan/.config && \
\
# Install Node dependencies
npm install -g npm@${NPM_VERSION}
EXPOSE $PORT
USER wekan
CMD ["node", "/home/wekan/bundle/main.js"]
+39 -24
View File
@@ -1,5 +1,3 @@
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/wekan/wekan)
# Wekan - Open Source kanban
[![Contributors](https://img.shields.io/github/contributors/wekan/wekan.svg "Contributors")](https://github.com/wekan/wekan/graphs/contributors)
@@ -12,7 +10,6 @@
[![Project Dependencies](https://david-dm.org/wekan/wekan.svg "Project Dependencies")](https://david-dm.org/wekan/wekan)
[![Code analysis at Open Hub](https://img.shields.io/badge/code%20analysis-at%20Open%20Hub-brightgreen.svg "Code analysis at Open Hub")](https://www.openhub.net/p/wekan)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwekan%2Fwekan.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_shield)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4619/badge)](https://bestpractices.coreinfrastructure.org/projects/4619)
## [Translate Wekan at Transifex](https://transifex.com/wekan/wekan)
@@ -21,25 +18,21 @@ New English strings of new features can be added as PRs to edge branch file weka
## [Wekan feature requests and bugs](https://github.com/wekan/wekan/issues)
Please add most of your questions as GitHub issue: [Wekan Feature Requests and Bugs](https://github.com/wekan/wekan/issues).
Please add most of your questions as GitHub issue: [Wekan feature requests and bugs](https://github.com/wekan/wekan/issues).
It's better than at chat where details get lost when chat scrolls up.
## Chat
[Discussions][discussions] - Wekan Community GitHub Discussions, that are not [Feature Requests and Bugs](https://github.com/wekan/wekan/issues).
[![Wekan Chat][vanila_badge]][wekan_chat] - Most Wekan community and developers are here. Works on webbrowser
and PWA app that can be added as icon on Android and bookmark on iOS, used like native app.
[Wekan IRC FAQ](https://github.com/wekan/wekan/wiki/IRC-FAQ)
## Docker: Please only use Docker release tags
Note: With Docker, please don't use latest tag. Only use release tags.
See https://github.com/wekan/wekan/issues/3874
## FAQ
**NOTE**:
**NOTE**:
- Please read the [FAQ](https://github.com/wekan/wekan/wiki/FAQ) first
- Please don't feed the [trolls](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-troll) and [spammers](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-spammer) that are mentioned in the FAQ :)
- Please don't feed the trolls and spammers that are mentioned in the FAQ :)
## About Wekan
@@ -57,19 +50,20 @@ that by providing one-click installation on various platforms.
- Wekan is used in [most countries of the world](https://snapcraft.io/wekan).
- Wekan largest user has 13k users using Wekan in their company.
- Wekan has been [translated](https://transifex.com/wekan/wekan) to about 63 languages.
- Wekan has been [translated](https://transifex.com/wekan/wekan) to about 50 languages.
- [Features][features]: Wekan has real-time user interface.
- [Platforms][platforms]: Wekan supports many platforms.
Wekan is critical part of new platforms Wekan is currently being integrated to.
- [Integrations][integrations]: Current possible integrations and future plans.
## Requirements
- 64bit: Linux [Snap](https://github.com/wekan/wekan-snap/wiki/Install) or [Sandstorm](https://sandstorm.io) /
[Mac](https://github.com/wekan/wekan/wiki/Mac) / [Windows](https://github.com/wekan/wekan/wiki/Install-Wekan-from-source-on-Windows).
[More Platforms](https://github.com/wekan/wekan/wiki/Platforms), bundle for RasPi3 ARM and other CPUs where Node.js and MongoDB exists.
[More Platforms](https://github.com/wekan/wekan/wiki/Platforms). [ARM progress](https://github.com/wekan/wekan/issues/1053#issuecomment-410919264).
- 1 GB RAM minimum free for Wekan. Production server should have minimum total 4 GB RAM.
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/master/docker-compose.yml): 3 frontend servers,
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/devel/docker-compose.yml): 3 frontend servers,
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
- Enough disk space and alerts about low disk space. If you run out disk space, MongoDB database gets corrupted.
- SECURITY: Updating to newest Wekan version very often. Please check you do not have automatic updates of Sandstorm or Snap turned off.
Old versions have security issues because of old versions Node.js etc. Only newest Wekan is supported.
@@ -80,25 +74,41 @@ that by providing one-click installation on various platforms.
Bugs, updates, users deleting list or card, harddrive full, harddrive crash etc can eat your data. There is no undo yet.
Some bug can cause Wekan board to not load at all, requiring manual fixing of database content.
## Roadmap and Demo
## Roadmap
[Roadmap][roadmap_wekan] - Public read-only board at Wekan demo.
[Roadmap Milestones](https://github.com/wekan/wekan/milestones)
[Developer Documentation][dev_docs]
- There is many companies and individuals contributing code to Wekan, to add features and bugfixes
[many times a day](https://github.com/wekan/wekan/blob/devel/CHANGELOG.md).
- [Please add Add new Feature Requests and Bug Reports immediately](https://github.com/wekan/wekan/issues).
- [Commercial Support](https://wekan.team/commercial-support/).
- [Commercial Support](https://wekan.team).
- [Bounties](https://wekan.team/bounties/index.html).
We also welcome sponsors for features and bugfixes.
By working directly with Wekan you get the benefit of active maintenance and new features added by growing Wekan developer community.
## Demo
[Wekan demo][roadmap_wefork]
## Screenshot
[More screenshots at Features page](https://github.com/wekan/wekan/wiki/Features)
[![Screenshot of Wekan][screenshot_wekan]][roadmap_wekan]
[![Screenshot of Wekan][screenshot_wefork]][roadmap_wefork]
## Stable
- master+devel branch. At release, devel is merged to master.
- Receives fixes and features that have been tested at edge that they work.
- If you want automatic updates, [use Snap](https://github.com/wekan/wekan-snap/wiki/Install).
- If you want to test before update, [use Docker quay.io release tags](https://github.com/wekan/wekan/wiki/Docker).
## Edge
- edge branch. All new fixes and features are added to here first. [Testing Edge](https://github.com/wekan/wekan-snap/wiki/Snap-Developer-Docs).
## License
@@ -107,11 +117,14 @@ with [Meteor](https://www.meteor.com).
[platforms]: https://github.com/wekan/wekan/wiki/Platforms
[dev_docs]: https://github.com/wekan/wekan/wiki/Developer-Documentation
[screenshot_wekan]: https://wekan.github.io/wekan-markdown.png
[screenshot_wekan]: http://i.imgur.com/cI4jW2h.png
[screenshot_wefork]: https://wekan.github.io/wekan-markdown.png
[features]: https://github.com/wekan/wekan/wiki/Features
[roadmap_wekan]: https://boards.wekan.team/b/D2SzJKZDS4Z48yeQH/wekan-open-source-kanban-board-with-mit-license
[wekan_issues]: https://github.com/wekan/wekan/issues
[integrations]: https://github.com/wekan/wekan/wiki/Integrations
[roadmap_wekan]: http://try.wekan.io/b/MeSsFJaSqeuo9M6bs/wekan-roadmap
[roadmap_wefork]: https://wekan.indie.host/b/t2YaGmyXgNkppcFBq/wekan-fork-roadmap
[wekan_issues]: https://github.com/wekan/wekan/issues
[wefork_issues]: https://github.com/wefork/wekan/issues
[docker_image]: https://hub.docker.com/r/wekanteam/wekan/
[travis_badge]: https://travis-ci.org/wekan/wekan.svg?branch=devel
[travis_status]: https://travis-ci.org/wekan/wekan
@@ -119,6 +132,8 @@ with [Meteor](https://www.meteor.com).
[translate_wekan]: https://www.transifex.com/wekan/wekan/
[open_source]: https://en.wikipedia.org/wiki/Open-source_software
[free_software]: https://en.wikipedia.org/wiki/Free_software
[discussions]: https://github.com/wekan/wekan/discussions
[vanila_badge]: https://vanila.io/img/join-chat-button2.png
[wekan_chat]: https://community.vanila.io/wekan
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwekan%2Fwekan.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_large)
+3 -3
View File
@@ -1,10 +1,10 @@
Security is very important to us. If you discover any issue regarding security, please disclose
the information responsibly by sending an email to support (at) wekan.team using
[this PGP public key](support-at-wekan.team_pgp-publickey.asc) and not by
the information responsibly by sending an email to security (at) wekan.team and not by
creating a GitHub issue. We will respond swiftly to fix verifiable security issues.
We thank you with a place at our hall of fame page, that is
at https://wekan.github.io/hall-of-fame
at https://wekan.github.io/hall-of-fame . Others have just posted public GitHub issue,
so they are not at that hall-of-fame page.
## How should reports be formatted?
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v5.41.0"
appVersion: "v2.95.0"
files:
userUploads:
- README.md
-291
View File
@@ -1,291 +0,0 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# vi:ts=4:et
# Wekan API Python CLI, originally from here, where is more details:
# https://github.com/wekan/wekan/wiki/New-card-with-Python3-and-REST-API
try:
# python 3
from urllib.parse import urlencode
except ImportError:
# python 2
from urllib import urlencode
import json
import requests
import sys
arguments = len(sys.argv) - 1
if arguments == 0:
print("=== Wekan API Python CLI: Shows IDs for addcard ===")
print("AUTHORID is USERID that writes card.")
print("If *nix: chmod +x api.py => ./api.py users")
print("Syntax:")
print(" python3 api.py users # All users")
print(" python3 api.py boards USERID # Boards of USERID")
print(" python3 api.py board BOARDID # Info of BOARDID")
print(" python3 api.py swimlanes BOARDID # Swimlanes of BOARDID")
print(" python3 api.py lists BOARDID # Lists of BOARDID")
print(" python3 api.py list BOARDID LISTID # Info of LISTID")
print(" python3 api.py createlist BOARDID LISTTITLE # Create list")
print(" python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION")
print(" python3 api.py editcard BOARDID LISTID CARDID NEWCARDTITLE NEWCARDDESCRIPTION")
print(" python3 api.py listattachments BOARDID # List attachments")
# TODO:
# print(" python3 api.py attachmentjson BOARDID ATTACHMENTID # One attachment as JSON base64")
# print(" python3 api.py attachmentbinary BOARDID ATTACHMENTID # One attachment as binary file")
# print(" python3 api.py attachmentdownload BOARDID ATTACHMENTID # One attachment as file")
# print(" python3 api.py attachmentsdownload BOARDID # All attachments as files")
exit
# ------- SETTINGS START -------------
# Username is your Wekan username or email address.
# OIDC/OAuth2 etc uses email address as username.
username = 'testtest'
password = 'testtest'
wekanurl = 'http://localhost:4000/'
# ------- SETTINGS END -------------
"""
EXAMPLE:
python3 api.py
OR:
chmod +x api.py
./api.py
=== Wekan API Python CLI: Shows IDs for addcard ===
AUTHORID is USERID that writes card.
Syntax:
python3 api.py users # All users
python3 api.py boards USERID # Boards of USERID
python3 api.py board BOARDID # Info of BOARDID
python3 api.py swimlanes BOARDID # Swimlanes of BOARDID
python3 api.py lists BOARDID # Lists of BOARDID
python3 api.py list BOARDID LISTID # Info of LISTID
python3 api.py createlist BOARDID LISTTITLE # Create list
python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION
python3 api.py editcard BOARDID LISTID CARDID NEWCARDTITLE NEWCARDDESCRIPTION
python3 api.py listattachments BOARDID # List attachments
python3 api.py attachmentjson BOARDID ATTACHMENTID # One attachment as JSON base64
python3 api.py attachmentbinary BOARDID ATTACHMENTID # One attachment as binary file
=== USERS ===
python3 api.py users
=> abcd1234
=== BOARDS ===
python3 api.py boards abcd1234
=== SWIMLANES ===
python3 api.py swimlanes dYZ
[{"_id":"Jiv","title":"Default"}
]
=== LISTS ===
python3 api.py lists dYZ
[]
There is no lists, so create a list:
=== CREATE LIST ===
python3 api.py createlist dYZ 'Test'
{"_id":"7Kp"}
# python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION
python3 api.py addcard ppg dYZ Jiv 7Kp 'Test card' 'Test description'
=== LIST ATTACHMENTS WITH DOWNLOAD URLs ====
python3 api.py listattachments BOARDID
"""
# ------- API URL GENERATION START -----------
loginurl = 'users/login'
wekanloginurl = wekanurl + loginurl
apiboards = 'api/boards/'
apiattachments = 'api/attachments/'
apiusers = 'api/users'
e = 'export'
s = '/'
l = 'lists'
sw = 'swimlane'
sws = 'swimlanes'
cs = 'cards'
bs = 'boards'
atl = 'attachmentslist'
at = 'attachment'
ats = 'attachments'
users = wekanurl + apiusers
# ------- API URL GENERATION END -----------
# ------- LOGIN TOKEN START -----------
data = {"username": username, "password": password}
body = requests.post(wekanloginurl, data=data)
d = body.json()
apikey = d['token']
# ------- LOGIN TOKEN END -----------
if arguments == 7:
if sys.argv[1] == 'addcard':
# ------- WRITE TO CARD START -----------
authorid = sys.argv[2]
boardid = sys.argv[3]
swimlaneid = sys.argv[4]
listid = sys.argv[5]
cardtitle = sys.argv[6]
carddescription = sys.argv[7]
cardtolist = wekanurl + apiboards + boardid + s + l + s + listid + s + cs
# Write to card
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
post_data = {'authorId': '{}'.format(authorid), 'title': '{}'.format(cardtitle), 'description': '{}'.format(carddescription), 'swimlaneId': '{}'.format(swimlaneid)}
body = requests.post(cardtolist, data=post_data, headers=headers)
print(body.text)
# ------- WRITE TO CARD END -----------
if arguments == 6:
if sys.argv[1] == 'editcard':
# ------- LIST OF BOARD START -----------
boardid = sys.argv[2]
listid = sys.argv[3]
cardid = sys.argv[4]
newcardtitle = sys.argv[5]
newcarddescription = sys.argv[6]
edcard = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + cardid
print(edcard)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
put_data = {'title': '{}'.format(newcardtitle), 'description': '{}'.format(newcarddescription)}
body = requests.put(edcard, data=put_data, headers=headers)
print("=== EDIT CARD ===\n")
body = requests.get(edcard, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- LISTS OF BOARD END -----------
if arguments == 3:
if sys.argv[1] == 'createlist':
# ------- CREATE LIST START -----------
boardid = sys.argv[2]
listtitle = sys.argv[3]
list = wekanurl + apiboards + boardid + s + l
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
post_data = {'title': '{}'.format(listtitle)}
body = requests.post(list, data=post_data, headers=headers)
print("=== CREATE LIST ===\n")
print(body.text)
# ------- CREATE LIST END -----------
if sys.argv[1] == 'list':
# ------- LIST OF BOARD START -----------
boardid = sys.argv[2]
listid = sys.argv[3]
listone = wekanurl + apiboards + boardid + s + l + s + listid
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
print("=== INFO OF ONE LIST ===\n")
body = requests.get(listone, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- LISTS OF BOARD END -----------
if arguments == 2:
# ------- BOARDS LIST START -----------
userid = sys.argv[2]
boards = users + s + userid + s + bs
if sys.argv[1] == 'boards':
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
#post_data = {'userId': '{}'.format(userid)}
body = requests.get(boards, headers=headers)
print("=== BOARDS ===\n")
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- BOARDS LIST END -----------
if sys.argv[1] == 'board':
# ------- BOARD INFO START -----------
boardid = sys.argv[2]
board = wekanurl + apiboards + boardid
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
body = requests.get(board, headers=headers)
print("=== BOARD ===\n")
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- BOARD INFO END -----------
if sys.argv[1] == 'swimlanes':
boardid = sys.argv[2]
swimlanes = wekanurl + apiboards + boardid + s + sws
# ------- SWIMLANES OF BOARD START -----------
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
print("=== SWIMLANES ===\n")
body = requests.get(swimlanes, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- SWIMLANES OF BOARD END -----------
if sys.argv[1] == 'lists':
# ------- LISTS OF BOARD START -----------
boardid = sys.argv[2]
lists = wekanurl + apiboards + boardid + s + l
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
print("=== LISTS ===\n")
body = requests.get(lists, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- LISTS OF BOARD END -----------
if sys.argv[1] == 'listattachments':
# ------- LISTS OF ATTACHMENTS START -----------
boardid = sys.argv[2]
listattachments = wekanurl + apiboards + boardid + s + ats
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
print("=== LIST OF ATTACHMENTS ===\n")
body = requests.get(listattachments, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- LISTS OF ATTACHMENTS END -----------
if arguments == 1:
if sys.argv[1] == 'users':
# ------- LIST OF USERS START -----------
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
print(users)
print("=== USERS ===\n")
body = requests.get(users, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- LIST OF USERS END -----------
-6
View File
@@ -1,6 +0,0 @@
// PWA
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/pwa-service-worker.js');
});
}
+188 -206
View File
@@ -8,234 +8,216 @@ template(name="activities")
+cardActivities
template(name="boardActivities")
each activityData in currentBoard.activities
+activity(activity=activityData card=card mode=mode)
each currentBoard.activities
.activity
+userAvatar(userId=user._id)
p.activity-desc
+memberName(user=user)
if($eq activityType 'deleteAttachment')
| {{{_ 'activity-delete-attach' cardLink}}}.
if($eq activityType 'addAttachment')
| {{{_ 'activity-attached' attachmentLink cardLink}}}.
if($eq activityType 'addBoardMember')
| {{{_ 'activity-added' memberLink boardLabel}}}.
if($eq activityType 'addComment')
| {{{_ 'activity-on' cardLink}}}
a.activity-comment(href="{{ card.absoluteUrl }}")
+viewer
= comment.text
if($eq activityType 'addChecklist')
| {{{_ 'activity-checklist-added' cardLink}}}.
.activity-checklist(href="{{ card.absoluteUrl }}")
+viewer
= checklist.title
if($eq activityType 'removeChecklist')
| {{{_ 'activity-checklist-removed' cardLink}}}.
if($eq activityType 'checkedItem')
| {{{_ 'activity-checked-item' checkItem checklist.title cardLink}}}.
if($eq activityType 'uncheckedItem')
| {{{_ 'activity-unchecked-item' checkItem checklist.title cardLink}}}.
if($eq activityType 'checklistCompleted')
| {{{_ 'activity-checklist-completed' checklist.title cardLink}}}.
if($eq activityType 'checklistUncompleted')
| {{{_ 'activity-checklist-uncompleted' checklist.title cardLink}}}.
if($eq activityType 'addChecklistItem')
| {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}.
.activity-checklist(href="{{ card.absoluteUrl }}")
+viewer
= checklistItem.title
if($eq activityType 'removedChecklistItem')
| {{{_ 'activity-checklist-item-removed' checklist.title cardLink}}}.
if($eq activityType 'archivedCard')
| {{{_ 'activity-archived' cardLink}}}.
if($eq activityType 'archivedList')
| {{_ 'activity-archived' list.title}}.
if($eq activityType 'archivedSwimlane')
| {{_ 'activity-archived' swimlane.title}}.
if($eq activityType 'createBoard')
| {{_ 'activity-created' boardLabel}}.
if($eq activityType 'createCard')
| {{{_ 'activity-added' cardLink boardLabel}}}.
if($eq activityType 'createCustomField')
| {{_ 'activity-customfield-created' customField}}.
if($eq activityType 'createList')
| {{_ 'activity-added' list.title boardLabel}}.
if($eq activityType 'createSwimlane')
| {{_ 'activity-added' swimlane.title boardLabel}}.
if($eq activityType 'removeList')
| {{_ 'activity-removed' title boardLabel}}.
if($eq activityType 'importBoard')
| {{{_ 'activity-imported-board' boardLabel sourceLink}}}.
if($eq activityType 'importCard')
| {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}.
if($eq activityType 'importList')
| {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}.
if($eq activityType 'joinMember')
if($eq user._id member._id)
| {{{_ 'activity-joined' cardLink}}}.
else
| {{{_ 'activity-added' memberLink cardLink}}}.
if($eq activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
if($eq activityType 'moveCard')
| {{{_ 'activity-moved' cardLink oldList.title list.title}}}.
if($eq activityType 'removeBoardMember')
| {{{_ 'activity-excluded' memberLink boardLabel}}}.
if($eq activityType 'restoredCard')
| {{{_ 'activity-sent' cardLink boardLabel}}}.
if($eq activityType 'addedLabel')
| {{{_ 'activity-added-label' lastLabel cardLink}}}.
if($eq activityType 'removedLabel')
| {{{_ 'activity-removed-label' lastLabel cardLink}}}.
if($eq activityType 'setCustomField')
| {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}.
if($eq activityType 'unsetCustomField')
| {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}.
if($eq activityType 'unjoinMember')
if($eq user._id member._id)
| {{{_ 'activity-unjoined' cardLink}}}.
else
| {{{_ 'activity-removed' memberLink cardLink}}}.
span(title=createdAt).activity-meta {{ moment createdAt }}
template(name="cardActivities")
each activityData in currentCard.activities
+activity(activity=activityData card=card mode=mode)
each currentCard.activities
.activity
+userAvatar(userId=user._id)
p.activity-desc
+memberName(user=user)
if($eq activityType 'createCard')
| {{_ 'activity-added' cardLabel listName}}.
if($eq activityType 'importCard')
| {{{_ 'activity-imported' cardLabel list.title sourceLink}}}.
if($eq activityType 'joinMember')
if($eq user._id member._id)
| {{_ 'activity-joined' cardLabel}}.
else
| {{{_ 'activity-added' memberLink cardLabel}}}.
if($eq activityType 'unjoinMember')
if($eq user._id member._id)
| {{_ 'activity-unjoined' cardLabel}}.
else
| {{{_ 'activity-removed' cardLabel memberLink}}}.
if($eq activityType 'archivedCard')
| {{_ 'activity-archived' cardLabel}}.
template(name="editOrDeleteComment")
= ' - '
a.js-open-inlined-form {{_ "edit"}}
= ' - '
a.js-delete-comment {{_ "delete"}}
if($eq activityType 'addedLabel')
| {{{_ 'activity-added-label-card' lastLabel }}}.
template(name="activity")
.activity
+userAvatar(userId=activity.user._id)
p.activity-desc
span.activity-member
+memberName(user=activity.user)
if($eq activityType 'removedLabel')
| {{{_ 'activity-removed-label-card' lastLabel }}}.
//- attachment activity -------------------------------------------------
if($eq activity.activityType 'deleteAttachment')
| {{{_ 'activity-delete-attach' cardLink}}}.
if($eq activityType 'removeChecklist')
| {{{_ 'activity-checklist-removed' cardLabel}}}.
if($eq activity.activityType 'addAttachment')
| {{{_ 'activity-attached' attachmentLink cardLink}}}.
if($neq mode 'board')
if activity.attachment.isImage
img.attachment-image-preview(src=activity.attachment.url)
if($eq activityType 'checkedItem')
| {{{_ 'activity-checked-item-card' checkItem checklist.title }}}.
//- board activity ------------------------------------------------------
if($eq mode 'board')
if($eq activity.activityType 'createBoard')
| {{{_ 'activity-created' boardLabelLink}}}.
if($eq activityType 'uncheckedItem')
| {{{_ 'activity-unchecked-item-card' checkItem checklist.title }}}.
if($eq activity.activityType 'importBoard')
| {{{_ 'activity-imported-board' boardLabelLink sourceLink}}}.
if($eq activityType 'checklistCompleted')
| {{{_ 'activity-checklist-completed-card' checklist.title }}}.
if($eq activity.activityType 'addBoardMember')
| {{{_ 'activity-added' memberLink boardLabelLink}}}.
if($eq activityType 'checklistUncompleted')
| {{{_ 'activity-checklist-uncompleted-card' checklist.title }}}.
if($eq activity.activityType 'removeBoardMember')
| {{{_ 'activity-excluded' memberLink boardLabelLink}}}.
if($eq activityType 'restoredCard')
| {{_ 'activity-sent' cardLabel boardLabel}}.
if($eq activityType 'moveCard')
| {{_ 'activity-moved' cardLabel oldList.title list.title}}.
//- card activity -------------------------------------------------------
if($eq activity.activityType 'createCard')
if($eq mode 'card')
| {{{_ 'activity-added' cardLabelLink (sanitize activity.listName)}}}.
else
| {{{_ 'activity-added' cardLabelLink boardLabelLink}}}.
if($eq activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
if($eq activity.activityType 'importCard')
| {{{_ 'activity-imported' cardLink boardLabelLink sourceLink}}}.
if($eq activity.activityType 'moveCard')
| {{{_ 'activity-moved' cardLabelLink (sanitize activity.oldList.title) (sanitize activity.list.title)}}}.
if($eq activity.activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink (sanitize activity.oldBoardName) (sanitize activity.boardName)}}}.
if($eq activity.activityType 'archivedCard')
| {{{_ 'activity-archived' cardLink}}}.
if($eq activity.activityType 'restoredCard')
| {{{_ 'activity-sent' cardLink boardLabelLink}}}.
//- checklist activity --------------------------------------------------
if($eq activity.activityType 'addChecklist')
| {{{_ 'activity-checklist-added' cardLink}}}.
if($eq mode 'card')
if($eq activityType 'addAttachment')
| {{{_ 'activity-attached' attachmentLink cardLabel}}}.
if attachment.isImage
img.attachment-image-preview(src=attachment.url)
if($eq activityType 'deleteAttachment')
| {{{_ 'activity-delete-attach' cardLabel}}}.
if($eq activityType 'removedChecklist')
| {{{_ 'activity-checklist-removed' cardLabel}}}.
if($eq activityType 'addChecklist')
| {{{_ 'activity-checklist-added' cardLabel}}}.
.activity-checklist
+viewer
= activity.checklist.title
else
a.activity-checklist(href="{{ activity.card.originRelativeUrl }}")
= checklist.title
if($eq activityType 'addChecklistItem')
| {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}.
.activity-checklist(href="{{ card.absoluteUrl }}")
+viewer
= activity.checklist.title
= checklistItem.title
if($eq activity.activityType 'removedChecklist')
| {{{_ 'activity-checklist-removed' cardLink}}}.
if($eq activity.activityType 'completeChecklist')
| {{{_ 'activity-checklist-completed' (sanitize activity.checklist.title) cardLink}}}.
if($eq activity.activityType 'uncompleteChecklist')
| {{{_ 'activity-checklist-uncompleted' (sanitize activity.checklist.title) cardLink}}}.
if($eq activity.activityType 'checkedItem')
| {{{_ 'activity-checked-item' (sanitize checkItem) (sanitize activity.checklist.title) cardLink}}}.
if($eq activity.activityType 'uncheckedItem')
| {{{_ 'activity-unchecked-item' (sanitize checkItem) (sanitize activity.checklist.title) cardLink}}}.
if($eq activity.activityType 'addChecklistItem')
| {{{_ 'activity-checklist-item-added' (sanitize activity.checklist.title) cardLink}}}.
.activity-checklist(href="{{ activity.card.originRelativeUrl }}")
+viewer
= activity.checklistItem.title
if($eq activity.activityType 'removedChecklistItem')
| {{{_ 'activity-checklist-item-removed' (sanitize activity.checklist.title) cardLink}}}.
//- comment activity ----------------------------------------------------
if($eq mode 'card')
//- if we are in card mode we display the comment in a way that it
//- can be edited by the owner
if($eq activity.activityType 'addComment')
if($eq activityType 'addComment')
+inlinedForm(classNames='js-edit-comment')
+editor(autofocus=true)
= activity.comment.text
= comment.text
.edit-controls
button.primary(type="submit") {{_ 'edit'}}
else
.activity-comment
+viewer
= activity.comment.text
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
if($eq currentUser._id activity.comment.userId)
+editOrDeleteComment
else if currentUser.isBoardAdmin
+editOrDeleteComment
= comment.text
span(title=createdAt).activity-meta {{ moment createdAt }}
if ($eq currentUser._id comment.userId)
= ' - '
a.js-open-inlined-form {{_ "edit"}}
= ' - '
a.js-delete-comment {{_ "delete"}}
if($eq activity.activityType 'deleteComment')
| {{{_ 'activity-deleteComment' currentData.commentId}}}.
if($eq activity.activityType 'editComment')
| {{{_ 'activity-editComment' currentData.commentId}}}.
else
//- if we are not in card mode we only display a summary of the comment
if($eq activity.activityType 'addComment')
| {{{_ 'activity-on' cardLink}}}
a.activity-comment(href="{{ activity.card.originRelativeUrl }}")
+viewer
= activity.comment.text
//- date activity ------------------------------------------------
if($eq mode 'card')
if($eq activity.activityType 'a-receivedAt')
| {{{_ 'activity-receivedDate' (sanitize receivedDate) cardLink}}}.
if($eq activity.activityType 'a-startAt')
| {{{_ 'activity-startDate' (sanitize startDate) cardLink}}}.
if($eq activity.activityType 'a-dueAt')
| {{{_ 'activity-dueDate' (sanitize dueDate) cardLink}}}.
if($eq activity.activityType 'a-endAt')
| {{{_ 'activity-endDate' (sanitize endDate) cardLink}}}.
if($eq mode 'board')
if($eq activity.activityType 'a-receivedAt')
| {{{_ 'activity-receivedDate' (sanitize receivedDate) cardLink}}}.
if($eq activity.activityType 'a-startAt')
| {{{_ 'activity-startDate' (sanitize startDate) cardLink}}}.
if($eq activity.activityType 'a-dueAt')
| {{{_ 'activity-dueDate' (sanitize dueDate) cardLink}}}.
if($eq activity.activityType 'a-endAt')
| {{{_ 'activity-endDate' (sanitize endDate) cardLink}}}.
//- customField activity ------------------------------------------------
if($eq mode 'board')
if($eq activity.activityType 'createCustomField')
| {{_ 'activity-customfield-created' customField}}.
if($eq activity.activityType 'setCustomField')
| {{{_ 'activity-set-customfield' (sanitize lastCustomField) (sanitize lastCustomFieldValue) cardLink}}}.
if($eq activity.activityType 'unsetCustomField')
| {{{_ 'activity-unset-customfield' (sanitize lastCustomField) cardLink}}}.
//- label activity ------------------------------------------------------
if($eq activity.activityType 'addedLabel')
| {{{_ 'activity-added-label' (sanitize lastLabel) cardLink}}}.
if($eq activity.activityType 'removedLabel')
| {{{_ 'activity-removed-label' (sanitize lastLabel) cardLink}}}.
//- list activity -------------------------------------------------------
if($neq mode 'card')
if($eq activity.activityType 'createList')
| {{{_ 'activity-added' (sanitize listLabel) boardLabelLink}}}.
if($eq activity.activityType 'importList')
| {{{_ 'activity-imported' (sanitize listLabel) boardLabelLink sourceLink}}}.
if($eq activity.activityType 'removeList')
| {{{_ 'activity-removed' (sanitize activity.title) boardLabelLink}}}.
if($eq activity.activityType 'archivedList')
| {{_ 'activity-archived' (sanitize listLabel)}}.
//- member activity ----------------------------------------------------
if($eq activity.activityType 'joinMember')
if($eq user._id activity.member._id)
| {{{_ 'activity-joined' cardLink}}}.
else
| {{{_ 'activity-added' memberLink cardLink}}}.
if($eq activity.activityType 'unjoinMember')
if($eq user._id activity.member._id)
| {{{_ 'activity-unjoined' cardLink}}}.
else
| {{{_ 'activity-removed' memberLink cardLink}}}.
//- swimlane activity --------------------------------------------------
if($neq mode 'card')
if($eq activity.activityType 'createSwimlane')
| {{_ 'activity-added' (sanitize activity.swimlane.title) boardLabelLink}}.
if($eq activity.activityType 'archivedSwimlane')
| {{_ 'activity-archived' (sanitize activity.swimlane.title)}}.
//- I don't understand this part ----------------------------------------
if(currentData.timeKey)
| {{_ activity.activityType }}
= ' '
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
if (currentData.timeOldValue)
= ' '
| {{{_ "previous_as" }}}
= ' '
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
= ' @'
else if(currentData.timeValue)
| {{_ activity.activityType currentData.timeValue}}
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
span(title=createdAt).activity-meta {{ moment createdAt }}
+76 -180
View File
@@ -1,22 +1,19 @@
import DOMPurify from 'dompurify';
const activitiesPerPage = 500;
const activitiesPerPage = 20;
BlazeComponent.extendComponent({
onCreated() {
// XXX Should we use ReactiveNumber?
this.page = new ReactiveVar(1);
this.loadNextPageLocked = false;
// TODO is sidebar always available? E.g. on small screens/mobile devices
const sidebar = Sidebar;
sidebar && sidebar.callFirstWith(null, 'resetNextPeak');
const sidebar = this.parentComponent(); // XXX for some reason not working
sidebar.callFirstWith(null, 'resetNextPeak');
this.autorun(() => {
let mode = this.data().mode;
const capitalizedMode = Utils.capitalize(mode);
let thisId, searchId;
if (mode === 'linkedcard' || mode === 'linkedboard') {
thisId = Session.get('currentCard');
searchId = Cards.findOne({ _id: thisId }).linkedId;
searchId = Cards.findOne({_id: thisId}).linkedId;
mode = mode.replace('linked', '');
} else {
thisId = Session.get(`current${capitalizedMode}`);
@@ -25,13 +22,12 @@ BlazeComponent.extendComponent({
const limit = this.page.get() * activitiesPerPage;
const user = Meteor.user();
const hideSystem = user ? user.hasHiddenSystemMessages() : false;
if (searchId === null) return;
if (searchId === null)
return;
this.subscribe('activities', mode, searchId, limit, hideSystem, () => {
this.loadNextPageLocked = false;
// TODO the guard can be removed as soon as the TODO above is resolved
if (!sidebar) return;
// If the sibear peak hasn't increased, that mean that there are no more
// activities, and we can stop calling new subscriptions.
// XXX This is hacky! We need to know excatly and reactively how many
@@ -46,131 +42,83 @@ BlazeComponent.extendComponent({
});
});
},
loadNextPage() {
if (this.loadNextPageLocked === false) {
this.page.set(this.page.get() + 1);
this.loadNextPageLocked = true;
}
},
}).register('activities');
BlazeComponent.extendComponent({
checkItem() {
const checkItemId = this.currentData().activity.checklistItemId;
const checkItem = ChecklistItems.findOne({ _id: checkItemId });
return checkItem && checkItem.title;
checkItem(){
const checkItemId = this.currentData().checklistItemId;
const checkItem = ChecklistItems.findOne({_id:checkItemId});
return checkItem.title;
},
boardLabelLink() {
const data = this.currentData();
if (data.mode !== 'board') {
return createBoardLink(data.activity.board(), data.activity.listName);
}
boardLabel() {
return TAPi18n.__('this-board');
},
cardLabelLink() {
const data = this.currentData();
if (data.mode !== 'card') {
return createCardLink(data.activity.card());
}
cardLabel() {
return TAPi18n.__('this-card');
},
cardLink() {
return createCardLink(this.currentData().activity.card());
const card = this.currentData().card();
return card && Blaze.toHTML(HTML.A({
href: card.absoluteUrl(),
'class': 'action-card',
}, card.title));
},
receivedDate() {
const receivedDate = this.currentData().activity.card();
if (!receivedDate) return null;
return receivedDate.receivedAt;
},
startDate() {
const startDate = this.currentData().activity.card();
if (!startDate) return null;
return startDate.startAt;
},
dueDate() {
const dueDate = this.currentData().activity.card();
if (!dueDate) return null;
return dueDate.dueAt;
},
endDate() {
const endDate = this.currentData().activity.card();
if (!endDate) return null;
return endDate.endAt;
},
lastLabel() {
const lastLabelId = this.currentData().activity.labelId;
if (!lastLabelId) return null;
const lastLabel = Boards.findOne(
this.currentData().activity.boardId,
).getLabelById(lastLabelId);
if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
lastLabel(){
const lastLabelId = this.currentData().labelId;
if (!lastLabelId)
return null;
const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(lastLabelId);
if(lastLabel.name === undefined || lastLabel.name === ''){
return lastLabel.color;
} else {
}else{
return lastLabel.name;
}
},
lastCustomField() {
const lastCustomField = CustomFields.findOne(
this.currentData().activity.customFieldId,
);
if (!lastCustomField) return null;
lastCustomField(){
const lastCustomField = CustomFields.findOne(this.currentData().customFieldId);
if (!lastCustomField)
return null;
return lastCustomField.name;
},
lastCustomFieldValue() {
const lastCustomField = CustomFields.findOne(
this.currentData().activity.customFieldId,
);
if (!lastCustomField) return null;
const value = this.currentData().activity.value;
if (
lastCustomField.settings.dropdownItems &&
lastCustomField.settings.dropdownItems.length > 0
) {
const dropDownValue = _.find(
lastCustomField.settings.dropdownItems,
item => {
return item._id === value;
},
);
if (dropDownValue) return dropDownValue.name;
lastCustomFieldValue(){
const lastCustomField = CustomFields.findOne(this.currentData().customFieldId);
if (!lastCustomField)
return null;
const value = this.currentData().value;
if (lastCustomField.settings.dropdownItems && lastCustomField.settings.dropdownItems.length > 0) {
const dropDownValue = _.find(lastCustomField.settings.dropdownItems, (item) => {
return item._id === value;
});
if (dropDownValue)
return dropDownValue.name;
}
return value;
},
listLabel() {
const activity = this.currentData().activity;
const list = activity.list();
return (list && list.title) || activity.title;
return this.currentData().list().title;
},
sourceLink() {
const source = this.currentData().activity.source;
if (source) {
if (source.url) {
return Blaze.toHTML(
HTML.A(
{
href: source.url,
},
DOMPurify.sanitize(source.system, {
ALLOW_UNKNOWN_PROTOCOLS: true,
}),
),
);
const source = this.currentData().source;
if(source) {
if(source.url) {
return Blaze.toHTML(HTML.A({
href: source.url,
}, source.system));
} else {
return DOMPurify.sanitize(source.system, {
ALLOW_UNKNOWN_PROTOCOLS: true,
});
return source.system;
}
}
return null;
@@ -178,97 +126,45 @@ BlazeComponent.extendComponent({
memberLink() {
return Blaze.toHTMLWithData(Template.memberName, {
user: this.currentData().activity.member(),
user: this.currentData().member(),
});
},
attachmentLink() {
const attachment = this.currentData().activity.attachment();
const attachment = this.currentData().attachment();
// trying to display url before file is stored generates js errors
return (
(attachment &&
attachment.url({ download: true }) &&
Blaze.toHTML(
HTML.A(
{
href: attachment.url({ download: true }),
target: '_blank',
},
DOMPurify.sanitize(attachment.name()),
),
)) ||
DOMPurify.sanitize(this.currentData().activity.attachmentName)
);
return attachment && attachment.url({ download: true }) && Blaze.toHTML(HTML.A({
href: attachment.url({ download: true }),
target: '_blank',
}, attachment.name()));
},
customField() {
const customField = this.currentData().activity.customField();
if (!customField) return null;
const customField = this.currentData().customField();
if (!customField)
return null;
return customField.name;
},
events() {
return [
{
// XXX We should use Popup.afterConfirmation here
'click .js-delete-comment'() {
const commentId = this.currentData().activity.commentId;
CardComments.remove(commentId);
},
'submit .js-edit-comment'(evt) {
evt.preventDefault();
const commentText = this.currentComponent()
.getValue()
.trim();
const commentId = Template.parentData().activity.commentId;
if (commentText) {
CardComments.update(commentId, {
$set: {
text: commentText,
},
});
}
},
return [{
// XXX We should use Popup.afterConfirmation here
'click .js-delete-comment'() {
const commentId = this.currentData().commentId;
CardComments.remove(commentId);
},
];
'submit .js-edit-comment'(evt) {
evt.preventDefault();
const commentText = this.currentComponent().getValue().trim();
const commentId = Template.parentData().commentId;
if (commentText) {
CardComments.update(commentId, {
$set: {
text: commentText,
},
});
}
},
}];
},
}).register('activity');
Template.activity.helpers({
sanitize(value) {
return DOMPurify.sanitize(value, { ALLOW_UNKNOWN_PROTOCOLS: true });
},
});
function createCardLink(card) {
if (!card) return '';
return (
card &&
Blaze.toHTML(
HTML.A(
{
href: card.originRelativeUrl(),
class: 'action-card',
},
DOMPurify.sanitize(card.title, { ALLOW_UNKNOWN_PROTOCOLS: true }),
),
)
);
}
function createBoardLink(board, list) {
let text = board.title;
if (list) text += `: ${list}`;
return (
board &&
Blaze.toHTML(
HTML.A(
{
href: board.originRelativeUrl(),
class: 'action-board',
},
DOMPurify.sanitize(text, { ALLOW_UNKNOWN_PROTOCOLS: true }),
),
)
);
}
}).register('activities');
+2 -6
View File
@@ -9,17 +9,13 @@
clear: both
.activity
margin: 0.5px 0
padding: 6px 0;
margin: 10px 0
display: flex
.member
width: 32px
width: 24px
height: @width
.activity-member
font-weight: 700
.activity-desc
word-wrap: break-word
overflow: hidden
+1 -1
View File
@@ -1,7 +1,7 @@
template(name="commentForm")
.new-comment.js-new-comment(
class="{{#if commentFormIsOpen}}is-open{{/if}}")
+userAvatar(userId=currentUser._id noRemove=true)
+userAvatar(userId=currentUser._id)
form.js-new-comment-form
+editor(class="js-new-comment-input")
| {{getUnsavedValue 'cardComment' currentCard._id}}
+36 -41
View File
@@ -3,7 +3,6 @@ const commentFormIsOpen = new ReactiveVar(false);
BlazeComponent.extendComponent({
onDestroyed() {
commentFormIsOpen.set(false);
$('.note-popover').hide();
},
commentFormIsOpen() {
@@ -15,45 +14,45 @@ BlazeComponent.extendComponent({
},
events() {
return [
{
'submit .js-new-comment-form'(evt) {
const input = this.getInput();
const text = input.val().trim();
const card = this.currentData();
let boardId = card.boardId;
let cardId = card._id;
if (card.isLinkedCard()) {
boardId = Cards.findOne(card.linkedId).boardId;
cardId = card.linkedId;
}
if (text) {
CardComments.insert({
text,
boardId,
cardId,
});
resetCommentInput(input);
Tracker.flush();
autosize.update(input);
input.trigger('submitted');
}
evt.preventDefault();
},
// Pressing Ctrl+Enter should submit the form
'keydown form textarea'(evt) {
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
this.find('button[type=submit]').click();
}
},
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();
const card = this.currentData();
let boardId = card.boardId;
let cardId = card._id;
if (card.isLinkedCard()) {
boardId = Cards.findOne(card.linkedId).boardId;
cardId = card.linkedId;
}
if (text) {
CardComments.insert({
text,
boardId,
cardId,
});
resetCommentInput(input);
Tracker.flush();
autosize.update(input);
}
evt.preventDefault();
},
// Pressing Ctrl+Enter should submit the form
'keydown form textarea'(evt) {
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
this.find('button[type=submit]').click();
}
},
}];
},
}).register('commentForm');
// XXX This should be a static method of the `commentForm` component
function resetCommentInput(input) {
input.val(''); // without manually trigger, input event won't be fired
input.val('');
input.blur();
commentFormIsOpen.set(false);
}
@@ -70,8 +69,7 @@ Tracker.autorun(() => {
});
});
EscapeActions.register(
'inlinedForm',
EscapeActions.register('inlinedForm',
() => {
const draftKey = {
fieldName: 'cardComment',
@@ -86,10 +84,7 @@ EscapeActions.register(
}
resetCommentInput(commentInput);
},
() => {
return commentFormIsOpen.get();
},
{
() => { return commentFormIsOpen.get(); }, {
noClickEscapeOn: '.js-new-comment',
},
}
);
@@ -46,23 +46,3 @@
&: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
+27 -40
View File
@@ -3,50 +3,37 @@ BlazeComponent.extendComponent({
this.subscribe('archivedBoards');
},
isBoardAdmin() {
return Meteor.user().isBoardAdmin();
},
archivedBoards() {
return Boards.find(
{ archived: true },
{
sort: { archivedAt: -1, modifiedAt: -1 },
},
);
return Boards.find({ archived: true }, {
sort: ['title'],
});
},
events() {
return [
{
'click .js-restore-board'() {
// TODO : Make isSandstorm variable global
const isSandstorm =
Meteor.settings &&
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && Session.get('currentBoard')) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
currentBoard.archive();
}
const board = this.currentData();
board.restore();
Utils.goBoardId(board._id);
},
'click .js-delete-board': Popup.afterConfirm('boardDelete', function() {
Popup.close();
const isSandstorm =
Meteor.settings &&
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && Session.get('currentBoard')) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
Boards.remove(currentBoard._id);
}
Boards.remove(this._id);
FlowRouter.go('home');
}),
return [{
'click .js-restore-board'() {
// TODO : Make isSandstorm variable global
const isSandstorm = Meteor.settings && Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && Session.get('currentBoard')) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
currentBoard.archive();
}
const board = this.currentData();
board.restore();
Utils.goBoardId(board._id);
},
];
'click .js-delete-board': Popup.afterConfirm('boardDelete', function() {
Popup.close();
const isSandstorm = Meteor.settings && Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && Session.get('currentBoard')) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
Boards.remove(currentBoard._id);
}
Boards.remove(this._id);
FlowRouter.go('home');
}),
}];
},
}).register('archivedBoards');
+4 -4
View File
@@ -6,16 +6,16 @@ template(name="board")
else
+boardBody
else
//-- XXX We need a better error message in case the board has been archived
+message(label="board-not-found")
//-- | {{goHome}}
//- XXX We need a better error message in case the board has been archived
//+message(label="board-not-found")
{{goHome}}
else
+spinner
template(name="boardBody")
.board-wrapper(class=currentBoard.colorClass)
+sidebar
.board-canvas.js-swimlanes(
.board-canvas.js-swimlanes.js-perfect-scrollbar(
class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}"
class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}"
class="{{#if draggingActive.get}}is-dragging-active{{/if}}")
+60 -144
View File
@@ -1,5 +1,5 @@
const subManager = new SubsManager();
const { calculateIndex } = Utils;
const { calculateIndex, enableClickOnTouch } = Utils;
const swimlaneWhileSortingHeight = 150;
BlazeComponent.extendComponent({
@@ -12,7 +12,8 @@ BlazeComponent.extendComponent({
// unfortunatly, Blaze doesn't have this notion.
this.autorun(() => {
const currentBoardId = Session.get('currentBoard');
if (!currentBoardId) return;
if (!currentBoardId)
return;
const handle = subManager.subscribe('board', currentBoardId, false);
Tracker.nonreactive(() => {
Tracker.autorun(() => {
@@ -29,6 +30,7 @@ BlazeComponent.extendComponent({
goHome() {
FlowRouter.go('home');
},
}).register('board');
BlazeComponent.extendComponent({
@@ -45,7 +47,7 @@ BlazeComponent.extendComponent({
if (nullSortSwimlanes.count() > 0) {
const swimlanes = currentBoardData.swimlanes();
let count = 0;
swimlanes.forEach(s => {
swimlanes.forEach((s) => {
Swimlanes.update(s._id, {
$set: {
sort: count,
@@ -60,7 +62,7 @@ BlazeComponent.extendComponent({
if (nullSortLists.count() > 0) {
const lists = currentBoardData.lists();
let count = 0;
lists.forEach(l => {
lists.forEach((l) => {
Lists.update(l._id, {
$set: {
sort: count,
@@ -89,6 +91,7 @@ BlazeComponent.extendComponent({
helper.append(list.clone());
return helper;
},
handle: '.js-swimlane-header',
items: '.swimlane:not(.placeholder)',
placeholder: 'swimlane placeholder',
distance: 7,
@@ -107,15 +110,12 @@ BlazeComponent.extendComponent({
// resize all swimlanes + headers to be a total of 150 px per row
// this could be achieved by setIsDragging(true) but we want immediate
// result
ui.item
.siblings('.js-swimlane')
.css('height', `${swimlaneWhileSortingHeight - 26}px`);
ui.item.siblings('.js-swimlane').css('height', `${swimlaneWhileSortingHeight - 26}px`);
// set the new scroll height after the resize and insertion of
// the placeholder. We want the element under the cursor to stay
// at the same place on the screen
ui.item.parent().get(0).scrollTop =
ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY;
ui.item.parent().get(0).scrollTop = ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY;
},
beforeStop(evt, ui) {
const parentOffset = ui.item.parent().offset();
@@ -124,8 +124,7 @@ BlazeComponent.extendComponent({
// compute the new scroll height after the resize and removal of
// the placeholder
const scrollTop =
ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY;
const scrollTop = ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY;
// then reset the original view of the swimlane
siblings.removeClass('moving-swimlane');
@@ -155,14 +154,11 @@ BlazeComponent.extendComponent({
sort(evt, ui) {
// get the mouse position in the sortable
const parentOffset = ui.item.parent().offset();
const cursorY =
evt.pageY - parentOffset.top + ui.item.parent().scrollTop();
const cursorY = evt.pageY - parentOffset.top + ui.item.parent().scrollTop();
// compute the intended index of the placeholder (we need to skip the
// slots between the headers and the list of cards)
const newplaceholderIndex = Math.floor(
cursorY / swimlaneWhileSortingHeight,
);
const newplaceholderIndex = Math.floor(cursorY / swimlaneWhileSortingHeight);
let destPlaceholderIndex = (newplaceholderIndex + 1) * 2;
// if we are scrolling far away from the bottom of the list
@@ -173,58 +169,19 @@ BlazeComponent.extendComponent({
// update the placeholder position in the DOM tree
if (destPlaceholderIndex !== ui.placeholder.index()) {
if (destPlaceholderIndex < boardComponent.origPlaceholderIndex) {
ui.placeholder.insertBefore(
ui.placeholder
.siblings()
.slice(destPlaceholderIndex - 2, destPlaceholderIndex - 1),
);
ui.placeholder.insertBefore(ui.placeholder.siblings().slice(destPlaceholderIndex - 2, destPlaceholderIndex - 1));
} else {
ui.placeholder.insertAfter(
ui.placeholder
.siblings()
.slice(destPlaceholderIndex - 1, destPlaceholderIndex),
);
ui.placeholder.insertAfter(ui.placeholder.siblings().slice(destPlaceholderIndex - 1, destPlaceholderIndex));
}
}
},
});
this.autorun(() => {
let showDesktopDragHandles = false;
currentUser = Meteor.user();
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
} else if (window.localStorage.getItem('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
//$swimlanesDom.sortable('option', 'disabled', !userIsMember());
$swimlanesDom.sortable(
'option',
'disabled',
!Meteor.user().isBoardAdmin(),
);
});
// ugly touch event hotfix
enableClickOnTouch('.js-swimlane:not(.placeholder)');
function userIsMember() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly()
);
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
}
// If there is no data in the board (ie, no lists) we autofocus the list
@@ -236,57 +193,47 @@ BlazeComponent.extendComponent({
},
isViewSwimlanes() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).boardView === 'board-view-swimlanes';
} else {
return (
window.localStorage.getItem('boardView') === 'board-view-swimlanes'
);
}
const currentUser = Meteor.user();
if (!currentUser) return false;
return ((currentUser.profile || {}).boardView === 'board-view-swimlanes');
},
isViewLists() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).boardView === 'board-view-lists';
} else {
return window.localStorage.getItem('boardView') === 'board-view-lists';
}
const currentUser = Meteor.user();
if (!currentUser) return true;
return ((currentUser.profile || {}).boardView === 'board-view-lists');
},
isViewCalendar() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).boardView === 'board-view-cal';
} else {
return window.localStorage.getItem('boardView') === 'board-view-cal';
}
const currentUser = Meteor.user();
if (!currentUser) return false;
return ((currentUser.profile || {}).boardView === 'board-view-cal');
},
openNewListForm() {
if (this.isViewSwimlanes()) {
this.childComponents('swimlane')[0]
.childComponents('addListAndSwimlaneForm')[0]
.open();
.childComponents('addListAndSwimlaneForm')[0].open();
} else if (this.isViewLists()) {
this.childComponents('listsGroup')[0]
.childComponents('addListForm')[0]
.open();
.childComponents('addListForm')[0].open();
}
},
events() {
return [
{
// XXX The board-overlay div should probably be moved to the parent
// component.
mouseup() {
if (this._isDragging) {
this._isDragging = false;
}
},
return [{
// XXX The board-overlay div should probably be moved to the parent
// component.
'mouseenter .board-overlay'() {
if (this.mouseHasEnterCardDetails) {
this.showOverlay.set(false);
}
},
];
'mouseup'() {
if (this._isDragging) {
this._isDragging = false;
}
},
}];
},
// XXX Flow components allow us to avoid creating these two setter methods by
@@ -298,24 +245,23 @@ BlazeComponent.extendComponent({
scrollLeft(position = 0) {
const swimlanes = this.$('.js-swimlanes');
swimlanes &&
swimlanes.animate({
scrollLeft: position,
});
swimlanes && swimlanes.animate({
scrollLeft: position,
});
},
scrollTop(position = 0) {
const swimlanes = this.$('.js-swimlanes');
swimlanes &&
swimlanes.animate({
scrollTop: position,
});
swimlanes && swimlanes.animate({
scrollTop: position,
});
},
}).register('boardBody');
BlazeComponent.extendComponent({
onRendered() {
this.autorun(function() {
this.autorun(function(){
$('#calendar-view').fullCalendar('refetchEvents');
});
},
@@ -327,8 +273,7 @@ BlazeComponent.extendComponent({
timezone: 'local',
header: {
left: 'title today prev,next',
center:
'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,listMonth',
center: 'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,timelineMonth timelineYear',
right: '',
},
// height: 'parent', nope, doesn't work as the parent might be small
@@ -338,7 +283,7 @@ BlazeComponent.extendComponent({
nowIndicator: true,
businessHours: {
// days of week. an array of zero-based day of week integers (0=Sunday)
dow: [1, 2, 3, 4, 5], // Monday - Friday
dow: [ 1, 2, 3, 4, 5 ], // Monday - Friday
start: '8:00',
end: '18:00',
},
@@ -346,45 +291,19 @@ BlazeComponent.extendComponent({
events(start, end, timezone, callback) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const events = [];
const pushEvent = function(card, title, start, end, extraCls) {
start = start || card.startAt;
end = end || card.endAt;
title = title || card.title;
const className =
(extraCls ? `${extraCls} ` : '') +
(card.color ? `calendar-event-${card.color}` : '');
currentBoard.cardsInInterval(start.toDate(), end.toDate()).forEach(function(card){
events.push({
id: card._id,
title,
start,
end: end || card.endAt,
allDay:
Math.abs(end.getTime() - start.getTime()) / 1000 === 24 * 3600,
url: FlowRouter.path('card', {
title: card.title,
start: card.startAt,
end: card.endAt,
allDay: Math.abs(card.endAt.getTime() - card.startAt.getTime()) / 1000 === 24*3600,
url: FlowRouter.url('card', {
boardId: currentBoard._id,
slug: currentBoard.slug,
cardId: card._id,
}),
className,
});
};
currentBoard
.cardsInInterval(start.toDate(), end.toDate())
.forEach(function(card) {
pushEvent(card);
});
currentBoard
.cardsDueInBetween(start.toDate(), end.toDate())
.forEach(function(card) {
pushEvent(
card,
`${card.title} ${TAPi18n.__('card-due')}`,
card.dueAt,
new Date(card.dueAt.getTime() + 36e5),
);
});
events.sort(function(first, second) {
return first.id > second.id ? 1 : -1;
});
callback(events);
},
@@ -418,11 +337,8 @@ BlazeComponent.extendComponent({
};
},
isViewCalendar() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).boardView === 'board-view-cal';
} else {
return window.localStorage.getItem('boardView') === 'board-view-cal';
}
const currentUser = Meteor.user();
if (!currentUser) return false;
return ((currentUser.profile || {}).boardView === 'board-view-cal');
},
}).register('calendarView');
-78
View File
@@ -53,81 +53,3 @@ position()
padding: 0 0px 0px 0
overflow-x: hidden
overflow-y: auto
calendar-event-color(background, borderColor, color...)
background: background !important
border-color: borderColor
if color
color: color !important //overwrite text for better visibility
.calendar-event-green
calendar-event-color(#3cb500, #2a8000, #ffffff) //White text for better visibility
.calendar-event-yellow
calendar-event-color(#fad900, #c7ac00, #000) //Black text for better visibility
.calendar-event-orange
calendar-event-color(#ff9f19, #cc7c14, #000) //Black text for better visibility
.calendar-event-red
calendar-event-color(#eb4646, #b83737, #ffffff) //White text for better visibility
.calendar-event-purple
calendar-event-color(#a632db, #7d26a6, #ffffff) //White text for better visibility
.calendar-event-blue
calendar-event-color(#0079bf, #005a8a, #ffffff) //White text for better visibility
.calendar-event-pink
calendar-event-color(#ff78cb, #cc62a3, #000) //Black text for better visibility
.calendar-event-sky
calendar-event-color(#00c2e0, #0094ab, #ffffff) //White text for better visibility
.calendar-event-black
calendar-event-color(#4d4d4d, #1a1a1a, #ffffff) //White text for better visibility
.calendar-event-lime
calendar-event-color(#51e898, #3eb375, #000) //Black text for better visibility
.calendar-event-silver
calendar-event-color(#c0c0c0, #8c8c8c, #000) //Black text for better visibility
.calendar-event-peachpuff
calendar-event-color(#ffdab9, #ccaf95, #000) //Black text for better visibility
.calendar-event-crimson
calendar-event-color(#dc143c, #a8112f, #ffffff) //White text for better visibility
.calendar-event-plum
calendar-event-color(#dda0dd, #a87ba8, #000) //Black text for better visibility
.calendar-event-darkgreen
calendar-event-color(#006400, #003000, #ffffff) //White text for better visibility
.calendar-event-slateblue
calendar-event-color(#6a5acd, #4f4399, #ffffff) //White text for better visibility
.calendar-event-magenta
calendar-event-color(#ff00ff, #cc00cc, #ffffff) //White text for better visibility
.calendar-event-gold
calendar-event-color(#ffd700, #ccaa00, #000) //Black text for better visibility
.calendar-event-navy
calendar-event-color(#000080, #000033, #ffffff) //White text for better visibility
.calendar-event-gray
calendar-event-color(#808080, #333333, #ffffff) //White text for better visibility
.calendar-event-saddlebrown
calendar-event-color(#8b4513, #572b0c, #ffffff) //White text for better visibility
.calendar-event-paleturquoise
calendar-event-color(#afeeee, #8ababa, #000) //Black text for better visibility
.calendar-event-mistyrose
calendar-event-color(#ffe4e1, #ccb8b6, #000) //Black text for better visibility
.calendar-event-indigo
calendar-event-color(#4b0082, #2b004d, #ffffff) //White text for better visibility
+20 -866
View File
@@ -15,8 +15,7 @@ setBoardColor(color)
.is-selected .minicard
border-left: 3px solid color
button[type=submit].primary, input[type=submit].primary,
.sidebar .sidebar-content .sidebar-btn
button[type=submit].primary, input[type=submit].primary
background-color: darken(color, 20%)
&.pop-over .pop-over-list li a:not(.disabled):hover,
@@ -74,6 +73,21 @@ setBoardColor(color)
.board-color-nephritis
setBoardColor(#27AE60)
// RELAX MODE: light green backgroud, when green background color selected.
.ui-sortable
background-color:#a7e366
/* Hidden in relax mode.
card fields: received, start, due, end, members, requested, assigned
*/
.card-details-item.card-details-item-received,
.card-details-item.card-details-item-start,
.card-details-item.card-details-item-due,
.card-details-item.card-details-item-end,
.card-details-item.card-details-item-members,
.card-details-item.card-details-item-name { display:none; }
.card-details-items:empty { display:none; }
.board-color-pomegranate
setBoardColor(#C0392B)
@@ -86,23 +100,9 @@ setBoardColor(color)
.board-color-midnight
setBoardColor(#2C3E50)
.board-color-pumpkin
setBoardColor(#E67E22)
.board-color-moderatepink
setBoardColor(#CD5A91)
.board-color-strongcyan
setBoardColor(#00AECC)
.board-color-limegreen
setBoardColor(#4BBF6B)
.board-color-dark
setBoardColor(#2C3E51)
/* Not hidden in dark mode.
/* Hidden in dark mode.
card fields: received, start, due, end, members, requested, assigned
*/
.card-details-item.card-details-item-received,
.card-details-item.card-details-item-start,
.card-details-item.card-details-item-due,
@@ -110,7 +110,6 @@ setBoardColor(color)
.card-details-item.card-details-item-members,
.card-details-item.card-details-item-name { display:none; }
.card-details-items:empty { display:none; }
*/
// DARK MODE, when dark background mode selected.
// Modified version from https://github.com/wekan/wekan/wiki/Custom-CSS-themes#dark-theme
@@ -214,850 +213,5 @@ setBoardColor(color)
.sidebar-content h3, .sidebar-content h2, .sidebar-content
color:rgba(255,255,255,.7) !important
.board-color-relax
setBoardColor(#27AE61)
// RELAX MODE: light green background, with green background color,
// to help this theme users to relax.
// Colors and emphasis are specific to this Wekan theme contributor's company.
.ui-sortable
background-color:#a7e366
.list-header
background-color:#a7e366
border-bottom: 6px solid #a7e366
.list-body
background-color:#a7e366
.list
border-left: 1px dotted #000000
// Card details text emphasis: black border and white background
// to make it details text field easier to find for RELAX MODE users,
// and focus attention.
.card-details .card-details-items
& ~ .js-open-inlined-form
.viewer
background-color #ffffff !important
padding 15px !important
border 1px solid #000000 !important
word-wrap: break-word
// When card has comment, emphasis on minicard:
// bigger red comment icon and number of comments,
// to make it easier notice card comments and focus attention.
.minicard .badges .badge
.badge-icon,
.badge-text
&.badge-comment
display: block
border-radius: 4px
padding: 1px 3px
margin-bottom: 0.3rem
color: #ff0000
background-color: #ffffff
font-weight: bold
font-size: 11pt
.board-color-corteza
setBoardColor(#568BA2)
/*
Wekan for Corteza https://cortezaproject.org
Theme to match Corteza colors from:
https://github.com/cortezaproject/corteza-webapp-messaging/blob/master/src/assets/sass/variables.scss
// Paths
$fonts_dir : './assets/fonts/';
$icomoon-font-path: $fonts_dir + 'icomoon' !default;
$icomoon-font-family: "icomoon" !default;
// Typography
$regular: 'nunito_sansregular';
$bold: 'nunito_sansbold';
$semibold: 'nunito_sanssemibold';
// Color system
$white: #fff !default;
$black: #000 !default;
$primary: #568ba2;
$secondary: #90A3B1;
$success: #719430;
$warning: #F5D380;
$danger: #E85568;
$light: #F3F3F5;
$dark: #1e2224;
$currentmymessagebgcolor : #a7d0e3;
*/
//.header-quick-access
// backgroud-color: #568ba2
/*
Alternate "Clear" Styling
*/
setBoardClear(color1,color2)
//color1: The quick access color
//color2: The main bar color
&.sk-spinner div,
.board-backgrounds-list &.background-box,
.board-list & a
background: linear-gradient(180deg, color1 0%, color2 100%)
//background: linear-gradient(180deg, rgb(73, 155, 234) 0%, rgb(0, 174, 204) 100%)
.is-selected .minicard
border-left: 3px solid color1
&.pop-over .pop-over-list li a:not(.disabled):hover,
.sidebar .sidebar-content .sidebar-btn:hover,
.sidebar-list li a:hover
background-color: lighten(color1, 10%)
&#header ul li.current, &#header-quick-access ul li.current
border-bottom: 4px solid lighten(color2, 10%)
&#header-quick-access
background: darken(color1, 10%)
//background: rgba(66,137,204,1)
color: #FFF
&#header-quick-access #header-new-board-icon,
&#header-quick-access #header-user-bar,
&#header-quick-access ul li
color: rgba(255,255,255,0.5)
// The background-color value here is not seen,
// its covered by the background of #header-main-bar
// it's just to aid transitions between boards
&#header
background-color: color2
border-bottom: 1px solid darken(color2, 20%)
border-top: 1px solid darken(color2, 40%)
// Since the theme uses a gradient for the header
// and gradients break transitions, it has to be set here
&#header #header-main-bar
background: linear-gradient(180deg, color1 0%, color2 100%)
&#header #header-main-bar p
margin-bottom: 6px
&#header #header-main-bar .board-header-btn.emphasis
background: lighten(color2, 10%)
&:hover,
.board-header-btn-close
background: rgba(0,0,0,0.2)
&:hover .board-header-btn-close
background: rgba(0,0,0,0.2)
.materialCheckBox.is-checked
border-bottom: 2px solid color1
border-right: 2px solid color1
.is-multiselection-active .multi-selection-checkbox
&.is-checked + .minicard
background: lighten(color2, 90%)
&:not(.is-checked) + .minicard:hover:not(.minicard-composer)
background: lighten(color2, 97%)
.toggle-switch:checked ~ .toggle-label
background-color: lighten(color1, 20%)
&:after
background-color: darken(color1, 20%)
.board-canvas
background: linear-gradient(135deg, color1 0%, color2 100%)
.swimlane
background: none
.list:first-child
margin-left: 15px
.list
background: rgba(255,255,255,0.35)
margin: 10px
border: 0
border-radius: 14px
.list.list-composer
background: rgba(255,255,255,0.1)
height: min-content
flex: unset
width: 270px
padding-bottom: 16px
.list.list-composer .open-list-composer
border-radius: 7px
color: rgba(0,0,0,0.3)
padding: 7px 10px
display: block
.list.list-composer .open-list-composer:hover
box-shadow: 0 1px 2px rgba(0,0,0,.2)
background: rgba(255,255,255,0.7)
color: rgba(0,0,0,0.6)
.list-header
background-color: rgba(255,255,255,0.25)
border-radius: 14px 14px 0 0
.list-header:not([class*="list-header-"])
border-bottom: 6px solid rgba(255,255,255,0)
.list-header .list-header-name
color: rgba(0,0,0,0.6)
.list-body
padding: 11px
.minicard
border-radius: 7px
padding: 10px 10px 4px 10px
box-shadow: 2px 2px 4px 0px rgba(0,0,0,0.15)
color: #222
.card-details
border-radius: 0 0 14px 14px
box-shadow: 0 0 7px 0 rgba(0,0,0,0.5)
margin-left: -10px
.list-body .open-minicard-composer
border-radius: 7px
color: rgba(0,0,0,.3)
margin-bottom: 11px
.list-body .open-minicard-composer:hover
background: rgba(255,255,255,0.7)
color: rgba(0,0,0,0.6)
button[type=submit].primary, input[type=submit].primary
box-shadow: none
background-color: rgba(255,255,255,0.5)
color: rgba(0,0,0,0.55)
border-radius: 7px
border: 0
button[type="submit"].primary:hover, input[type="submit"].primary:hover
background-color: rgba(255,255,255,0.7)
color: rgba(0,0,0,0.8)
box-shadow: 0 1px 2px rgba(0,0,0,.2)
.quiet, .quiet a
color: rgba(0,0,0,0.4)
.list-header .list-header-watch-icon
color: rgba(0,0,0,0.5)
position: absolute
margin-top: -34px
margin-let: -11px
a.fa, a i.fa
color: rgba(0,0,0,0.3)
a:not(.disabled).is-active.fa, a:not(.disabled).is-active i.fa, a:not(.disabled):hover.fa, a:not(.disabled):hover i.fa
color: rgba(0,0,0,0.6)
input[type="email"], input[type="password"], input[type="text"]
border: 0
border-radius: 7px
.sidebar-shadow
box-shadow: none
border-left: 9px solid color2
.is-open .sidebar-shadow
box-shadow: -10px 0 8px rgba(0,0,0,0.3)
.list.ui-sortable-helper
transform:rotate(0deg)
.minicard-wrapper.placeholder
background: rgba(0,0,0,0.1)
.minicard-wrapper.ui-sortable-helper
transform:rotate(0deg)
opacity: 0.8
.list-body .open-minicard-composer
color: rgba(0,0,0,.3)
.swinlane.ui-sortable-helper
transform:rotate(0deg)
.swimlane .swimlane-header-wrap
background: linear-gradient(0deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.25) 100%)
.swimlane-header-wrap .inlined-form
width: 100%
.swimlane-header-wrap .list-composer
text-align: center
margin: 5px
.swimlane-header-wrap .list-name-input.full-line
margin: 0
display: inline-block
width: 270px
.swimlane-header-wrap .edit-controls
display: inline-block
vertical-align: middle
.swimlane-header-wrap .primary.confirm
margin-right: 0
.swimlane-header-wrap .fa.fa-times-thin
margin-top: 2px
// This is a general fix so that the little grabby hand appears when dragging the list via the title
.list.ui-sortable-helper,
.list.ui-sortable-helper .list-header.ui-sortable-handle,
.list.ui-sortable-helper .viewer
cursor:-webkit-grabbing;
cursor:grabbing
.board-color-clearblue
setBoardClear(rgb(73, 155, 234),rgb(0, 174, 204))
/*
Alternate "Natural" Styling
*/
.board-color-natural
setBoardColor(#596557)
&#header-quick-access
background-color: #2d392b
.ui-sortable
background-color:#dedede
.list-header
background-color: #c9cfc3
border-bottom: 6px solid #c9cfc3
.swimlane .swimlane-header-wrap
background-color: #c2c0ab
/*
Alternate "Modern" Styling
*/
.board-color-modern
setBoardColor(#2A80B8)
/* General */
body
background: #f5f5f5
&#header-quick-access
padding: 10px
font-size: 14px
background: #333 !important
&#header-quick-access ul
overflow: visible
&#header-quick-access ul li.current
border: 0 !important
font-weight: bold
&#header-quick-access ul li.separator
display: none
&#header-quick-access ul li:nth-child(3)
margin-right: 10px
&#header-quick-access ul li a
padding: 5px 10px
border-radius: 2px
&#header-quick-access ul li.current a
border-radius: 2px
background: rgba(255,255,255,.2)
&#header #header-main-bar h1
font-family: Poppins
font-weight: bold
&#header-quick-access #header-user-bar
position relative
&#header-quick-access #header-user-bar .header-user-bar-name
margin: 5px 3px 0 0
section#notifications-drawer
top: 46px
box-shadow: 0 4px 20px rgba(0,0,0,.1)
max-width: 100%
section#notifications-drawer .header
top: 46px
border-radius: 0 3px
height: 21px
background: #f7f7f7
.board-canvas
background: #f5f5f5
/* Swimlane */
.swimlane
background: none
.swimlane .swimlane-header-wrap .swimlane-header
font-family: Poppins
/* All board views */
.board-list .board-list-item
padding: 20px
.board-list-item-name
font-family: Poppins
/* Board */
.list
background: transparent
border-left: 0
margin: 10px 0
padding: 0px
border-radius: 5px
min-width: 300px
.list-body .open-minicard-composer:hover /*me*/
background: none
box-shadow: none
.list:first-child
margin-left: 5px
.list.list-composer.js-list-composer
transition: all .3s ease
min-width: 80px
.open-list-composer.js-open-inlined-form:hover
color: #222
.list-header
background: none
border-bottom-width: 0px
.list-header .list-header-name
font-family: Poppins
color: #000
font-weight: 500
/* Card changes */
.minicard
padding: 15px 15px 10px
box-shadow: 0 3px 8px rgba(0,0,0,.05)
.minicard-plum:hover:not(.minicard-composer), .is-selected .minicard-plum, .draggable-hover-card .minicard-plum
background: none
.minicard-title
line-height: 1.5em
.minicard .minicard-cover
background-size: cover
margin: -15px -15px 10px
height: 100px
.card-label-orange
color: #fff
.card-date
font-size: 12px
padding: 3px 5px
/* Pop over */
.header-title
font-family: Poppins
font-size: 16px
color: #333
.pop-over
box-shadow: 0 4px 20px rgba(0,0,0,.2)
border: 0
border-radius: 5px
.pop-over .header
padding: 10px
border-bottom: 0
border-radius: 5px 5px 0 0
background:#eee
.pop-over .header .header-title
font-family: Poppins
font-size:16px
color:#333
.pop-over .header .close-btn
font-size:20px
top:6px
right:8px
.pop-over .content-container .content
padding: 5px 20px 20px
width: 260px
.pop-over-list li > a
border-radius: 5px
.pop-over-list li > a > i
margin-right: 5px
.pop-over-list li>a .sub-name
margin-bottom: 8px
/* Sidebar */
.sidebar .sidebar-shadow
box-shadow: 0 0 60px rgba(0,0,0,.2)
.sidebar .sidebar-content
padding: 30px
/* Notifications */
.board-color-modern section#notifications-drawer
border-radius:5px
.board-color-modern section#notifications-drawer .header
padding: 18px 16px
border-bottom: 0
border-radius: 5px 5px 0 0
background: #eee
.board-color-modern section#notifications-drawer .header h5
font-family: Poppins
font-weight: bold
.board-color-modern section#notifications-drawer .header .close
font-size: 20px
top: 14px
section#notifications-drawer .header .toggle-read
top: 18px
/*
Alternate "Modern Dark" Styling
*/
.board-color-moderndark
setBoardColor(#2a2a2a)
/* General */
body
background: #2a2a2a
.board-wrapper .board-canvas .board-overlay
opacity: .6
/* Forms */
button[type=submit].primary, .board-color-modern input[type=submit].primary
background-color: #819C5D
.toggle-switch:checked~.toggle-label
background-color: #D2E9B4
.toggle-label:after, .board-color-modern .toggle-switch:checked~.toggle-label:after
background-color: #819C5D !important
button, input:not([type=file]), select, textarea
border-radius: 2px
/* Headers */
&#header
background-color: #262626
border-bottom: 1px solid #555555;
border-top: 1px solid #555555;
&#header-quick-access, .background-box, #header
background-color: #333333
&#header-quick-access
padding: 4px
font-size: 14px
&#header-quick-access .allBoards
padding: 5px 10px 0 10px;
&#header-quick-access ul.header-quick-access-list
margin: -5px 0 -5px 0
&#header #header-main-bar
padding-top: 3px
padding-bottom: 3px
&#header-quick-access ul
overflow: visible
&#header-quick-access ul li.current
border: 0 !important
font-weight: bold
&#header-quick-access ul li.separator
display: none
&#header-quick-access ul li:nth-child(3)
margin-right: 10px
&#header-quick-access ul li a
padding: 5px 10px
border-radius: 2px
&#header-quick-access ul li.current a
border-radius: 2px
background: rgba(255,255,255,.2)
&#header #header-main-bar h1
font-family: Poppins
font-weight: bold
line-height: 0.8em
padding-top: 10px
/* Content */
.board-canvas
background: #2a2a2a
/* Swimlanes */
.swimlane .swimlane-header-wrap
background-color: #494949
color: #cccccc
padding: 4px 0
.swimlane .swimlane-header-wrap .swimlane-header
font-family: Poppins
.swimlane .swimlane-header-wrap .swimlane-header-menu
padding: 6px
font-size: 16px
.swimlane .swimlane-header-wrap .swimlane-header-plus-icon
font-size: 16px
.swimlane
background: #2a2a2a
line-height: 15px
max-height: 100%
/* Lists */
.swimlane .list
background: #666666
border-radius: 0
border: 0px solid #666666
flex: 0 0 265px;
.swimlane .list:first-child
margin-left: 0
.swimlane .list:nth-child(even)
background: #5f5f5f
.swimlane .list:nth-child(odd) .list-header
background: #3b3b3b
.list-header
background: #333333
padding: 10px
border-bottom: 0
.list-header .viewer
padding-left: 10px
.list-header .list-header-name
line-height: 14px
color: #eeeeee
.list-header .list-header-menu
padding: 10px
top: 0
.list-header .list-header-plus-icon
color: #a6a6a6
.list-body
scrollbar-width: thin
scrollbar-color: #343434 #999999
.list-body::-webkit-scrollbar
width: 10px
.list-body::-webkit-scrollbar-track
background: #343434
border-radius: 3px
margin: 4px 0
.list-body::-webkit-scrollbar-thumb
background-color: #999999
border-radius: 6px
border: 3px solid #343434
.list-body .open-minicard-composer:hover
background: none
box-shadow: none
border-bottom: 0
.list-body a.open-minicard-composer, .list-body a.open-minicard-composer i, .list .list-composer .open-list-composer i
color: #bbbbbb
.list-body a.open-minicard-composer:hover, .list-body a.open-minicard-composer:hover i, .list .list-composer .open-list-composer:hover i
color: #ffffff
/* Mini Card */
.minicard-wrapper
margin-bottom: 12px
.minicard
background-color: #444444
color: #cccccc
border-radius: 2px
font-size: 0.95em
padding: 10px
box-shadow: 0 4px 3px -3px rgba(0,0,0,0.8)
border-bottom: 1px solid #666666
.minicard:hover
background-color: #494949 !important
.minicard .minicard-labels
margin-bottom: 4px
.minicard .card-label
font-size: 11px
font-weight: 400
padding: 1px 6px 0
border-radius: 2px
.minicard .badges
color: #bbbbbb
.minicard .date
margin-top: 10px
font-size: 11px
.card-date
color: #444444
border-radius: 2px
.card-date.almost-due
color: #444444
.minicard.minicard-composer textarea.minicard-composer-textarea:focus
background-color: #eeeeee
color: #333333
padding: 6px
.is-selected .minicard
background-color: #666666
/* Card Details */
.card-details
position: absolute
top: 30px
left: calc(50% - 384px)
width: 768px
max-height: calc(100% - 60px)
background-color: #454545
color: #cccccc
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)
border: 1px solid #111111
z-index: 100 !important
.card-details
scrollbar-width: thin
scrollbar-color: #343434 #999999
.card-details::-webkit-scrollbar
width: 16px
.card-details::-webkit-scrollbar-track
background: #343434
.card-details::-webkit-scrollbar-thumb
background-color: #999999
border-radius: 6px
border: 4px solid #343434
.card-details .card-details-header
background: #333333
color: #cccccc
border-bottom: 2px solid #2d2d2d
.card-details hr
background: #2d2d2d
.card-details .card-details-item-title
color: #ffffff
.card-details .new-description textarea, .card-details .new-comment textarea
background-color: #dddddd
color: #111111
.card-details .checklist
background-color: transparent
margin-bottom: 10px
.card-details .checklist-item
background-color: rgba(255,255,255,0.1)
padding: 4px 8px
border-radius: 2px
font-size: 13px
margin-top: 5px
.card-details .checklist-item:hover
background-color: rgba(255,255,255,0.2)
.card-details .checklist-item .item-title .viewer p
max-width: auto
.card-details .check-box.materialCheckBox
border-color: #ffffff
.card-details .check-box.materialCheckBox.is-checked
border-bottom: 2px solid #819C5D
border-right: 2px solid #819C5D
border-top: 0
border-left: 0
.card-details .js-add-checklist-item
margin-top: 4px
.checklist-items .add-checklist-item
margin-top: .7em
.card-details .activities .activity .activity-desc .activity-comment
background-color: #cccccc
color: #222222
/* Sidebar */
.sidebar .sidebar-shadow
background-color: #222222
box-shadow: -10px 0 5px -10px #444444
border-left: 1px solid #333333
color: #cccccc
.activities .activity .activity-desc .activity-comment
background-color: #cccccc
color: #222222
/* Pop-Ups for "Modern Dark" */
.pop-over.board-color-moderndark
background-color: #454545
color: #cccccc
border: 1px solid #111111
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)
.pop-over.board-color-moderndark .header
background-color: #333333
.pop-over.board-color-moderndark .header-title
font-family: Poppins
font-size: 16px
color: #cccccc
.pop-over.board-color-moderndark .pop-over-list li:hover > a
background-color: #819C5D !important
.board-color-pumpkin
setBoardColor(#E67E22)
+13 -88
View File
@@ -1,9 +1,7 @@
template(name="boardHeaderBar")
h1.header-board-menu
with currentBoard
if $eq title 'Templates'
| {{_ 'templates'}}
else
a(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}")
+viewer
= title
@@ -11,10 +9,6 @@ template(name="boardHeaderBar")
unless isMiniScreen
if currentBoard
if currentUser
with currentBoard
a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title)
i.fa.fa-pencil-square-o
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
@@ -37,12 +31,6 @@ template(name="boardHeaderBar")
if $eq watchLevel "muted"
i.fa.fa-bell-slash
span {{_ watchLevel}}
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
i.fa.fa-sort
span {{#if isSortActive }}{{_ 'Sort is on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
if isSortActive
a.board-header-btn-close.js-sort-reset(title="Remove Sort")
i.fa.fa-times-thin
else
a.board-header-btn.js-log-in(
@@ -54,10 +42,6 @@ template(name="boardHeaderBar")
if currentBoard
if isMiniScreen
if currentUser
with currentBoard
a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title)
i.fa.fa-pencil-square-o
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
@@ -93,11 +77,6 @@ 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}}
a.board-header-btn.js-open-filter-view(
title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
class="{{#if Filter.isActive}}emphasis{{/if}}")
@@ -106,6 +85,15 @@ 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
@@ -114,14 +102,8 @@ template(name="boardHeaderBar")
unless currentBoard.isTemplatesBoard
a.board-header-btn.js-toggle-board-view(
title="{{_ 'board-view'}}")
i.fa.fa-caret-down
if $eq boardView 'board-view-swimlanes'
i.fa.fa-th-large
if $eq boardView 'board-view-lists'
i.fa.fa-trello
if $eq boardView 'board-view-cal'
i.fa.fa-calendar
span {{#if boardView}}{{_ boardView}}{{else}}{{_ 'board-view-swimlanes'}}{{/if}}
i.fa.fa-th-large
span {{#if currentUser.profile.boardView}}{{_ currentUser.profile.boardView}}{{else}}{{_ 'board-view-lists'}}{{/if}}
if canModifyBoard
a.board-header-btn.js-multiselection-activate(
@@ -134,7 +116,7 @@ template(name="boardHeaderBar")
i.fa.fa-times-thin
.separator
a.board-header-btn.js-toggle-sidebar(title="{{_ 'sidebar-open'}} {{_ 'or'}} {{_ 'sidebar-close'}}")
a.board-header-btn.js-toggle-sidebar
i.fa.fa-navicon
template(name="boardVisibilityList")
@@ -186,30 +168,6 @@ template(name="boardChangeWatchPopup")
i.fa.fa-check
span.sub-name {{_ 'muted-info'}}
template(name="boardChangeViewPopup")
ul.pop-over-list
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-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-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
template(name="createBoard")
form
label
@@ -228,9 +186,6 @@ template(name="createBoard")
= " "
| {{{_ 'board-private-info'}}}
a.js-change-visibility {{_ 'change'}}.
//a.flex.js-toggle-add-template-container
// .materialCheckBox#add-template-container
// span {{_ 'add-template-container'}}
input.primary.wide(type="submit" value="{{_ 'create'}}")
span.quiet
| {{_ 'or'}}
@@ -239,20 +194,6 @@ 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="boardChangeTitlePopup")
form
label
@@ -266,19 +207,3 @@ template(name="boardChangeTitlePopup")
template(name="boardCreateRulePopup")
p {{_ 'close-board-pop'}}
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
template(name="cardsSortPopup")
ul.pop-over-list
li
a.js-sort-due {{_ 'due-date'}}
hr
li
a.js-sort-title {{_ 'title-alphabetically'}}
hr
li
a.js-sort-created-desc {{_ 'created-at-newest-first'}}
hr
li
a.js-sort-created-asc {{_ 'created-at-oldest-first'}}
+110 -338
View File
@@ -1,8 +1,3 @@
/*
const DOWNCLS = 'fa-sort-down';
const UPCLS = 'fa-sort-up';
*/
const sortCardsBy = new ReactiveVar('');
Template.boardMenuPopup.events({
'click .js-rename-board': Popup.open('boardChangeTitle'),
'click .js-custom-fields'() {
@@ -31,25 +26,34 @@ 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({
exportUrl() {
const params = {
boardId: Session.get('currentBoard'),
};
const queryParams = {
authToken: Accounts._storedLoginToken(),
};
return FlowRouter.path('/api/boards/:boardId/export', params, queryParams);
},
exportFilename() {
const boardId = Session.get('currentBoard');
return `wekan-export-board-${boardId}.json`;
},
});
Template.boardChangeTitlePopup.events({
submit(event, templateInstance) {
const newTitle = templateInstance
.$('.js-board-name')
.val()
.trim();
const newDesc = templateInstance
.$('.js-board-desc')
.val()
.trim();
submit(evt, tpl) {
const newTitle = tpl.$('.js-board-name').val().trim();
const newDesc = tpl.$('.js-board-desc').val().trim();
if (newTitle) {
this.rename(newTitle);
this.setDescription(newDesc);
Popup.close();
}
event.preventDefault();
evt.preventDefault();
},
});
@@ -70,118 +74,69 @@ BlazeComponent.extendComponent({
const currentBoard = Boards.findOne(Session.get('currentBoard'));
return currentBoard && currentBoard.stars >= 2;
},
/*
showSort() {
return Meteor.user().hasSortBy();
},
directionClass() {
return this.currentDirection() === -1 ? DOWNCLS : UPCLS;
},
changeDirection() {
const direction = 0 - this.currentDirection() === -1 ? '-' : '';
Meteor.call('setListSortBy', direction + this.currentListSortBy());
},
currentDirection() {
return Meteor.user().getListSortByDirection();
},
currentListSortBy() {
return Meteor.user().getListSortBy();
},
listSortShortDesc() {
return `list-label-short-${this.currentListSortBy()}`;
},
*/
events() {
return [
{
'click .js-edit-board-title': Popup.open('boardChangeTitle'),
'click .js-star-board'() {
Meteor.user().toggleBoardStar(Session.get('currentBoard'));
},
'click .js-open-board-menu': Popup.open('boardMenu'),
'click .js-change-visibility': Popup.open('boardChangeVisibility'),
'click .js-watch-board': Popup.open('boardChangeWatch'),
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
},
'click .js-toggle-board-view': Popup.open('boardChangeView'),
'click .js-toggle-sidebar'() {
Sidebar.toggle();
},
'click .js-open-filter-view'() {
Sidebar.setView('filter');
},
'click .js-sort-cards': Popup.open('cardsSort'),
/*
'click .js-open-sort-view'(evt) {
const target = evt.target;
if (target.tagName === 'I') {
// click on the text, popup choices
this.changeDirection();
} else {
// change the sort order
Popup.open('listsort')(evt);
}
},
*/
'click .js-filter-reset'(event) {
event.stopPropagation();
Sidebar.setView();
Filter.reset();
},
'click .js-sort-reset'() {
Session.set('sortBy', '');
},
'click .js-open-search-view'() {
Sidebar.setView('search');
},
'click .js-multiselection-activate'() {
const currentCard = Session.get('currentCard');
MultiSelection.activate();
if (currentCard) {
MultiSelection.add(currentCard);
}
},
'click .js-multiselection-reset'(event) {
event.stopPropagation();
MultiSelection.disable();
},
'click .js-log-in'() {
FlowRouter.go('atSignIn');
},
return [{
'click .js-edit-board-title': Popup.open('boardChangeTitle'),
'click .js-star-board'() {
Meteor.user().toggleBoardStar(Session.get('currentBoard'));
},
];
'click .js-open-board-menu': Popup.open('boardMenu'),
'click .js-change-visibility': Popup.open('boardChangeVisibility'),
'click .js-watch-board': Popup.open('boardChangeWatch'),
'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-sidebar'() {
Sidebar.toggle();
},
'click .js-open-filter-view'() {
Sidebar.setView('filter');
},
'click .js-filter-reset'(evt) {
evt.stopPropagation();
Sidebar.setView();
Filter.reset();
},
'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();
if (currentCard) {
MultiSelection.add(currentCard);
}
},
'click .js-multiselection-reset'(evt) {
evt.stopPropagation();
MultiSelection.disable();
},
'click .js-log-in'() {
FlowRouter.go('atSignIn');
},
}];
},
}).register('boardHeaderBar');
Template.boardHeaderBar.helpers({
canModifyBoard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly()
);
},
boardView() {
return Utils.boardView();
},
isSortActive() {
return Session.get('sortBy') ? true : false;
},
});
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();
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});
@@ -209,105 +164,45 @@ const CreateBoard = BlazeComponent.extendComponent({
this.visibilityMenuIsOpen.set(!this.visibilityMenuIsOpen.get());
},
toggleAddTemplateContainer() {
$('#add-template-container').toggleClass('is-checked');
},
onSubmit(event) {
event.preventDefault();
onSubmit(evt) {
evt.preventDefault();
const title = this.find('.js-new-board-title').value;
const visibility = this.visibility.get();
const addTemplateContainer = $('#add-template-container.is-checked').length > 0;
if (addTemplateContainer) {
//const templateContainerId = Meteor.call('setCreateTemplateContainer');
//Utils.goBoardId(templateContainerId);
//alert('niinku template ' + Meteor.call('setCreateTemplateContainer'));
this.boardId.set(Boards.insert({
title,
permission: visibility,
}));
this.boardId.set(
Boards.insert({
// title: TAPi18n.__('templates'),
title: title,
permission: 'private',
type: 'template-container',
}),
);
Swimlanes.insert({
title: 'Default',
boardId: this.boardId.get(),
});
// Insert the card templates swimlane
Swimlanes.insert({
// title: TAPi18n.__('card-templates-swimlane'),
title: 'Card Templates',
boardId: this.boardId.get(),
sort: 1,
type: 'template-container',
}),
// Insert the list templates swimlane
Swimlanes.insert(
{
// title: TAPi18n.__('list-templates-swimlane'),
title: 'List Templates',
boardId: this.boardId.get(),
sort: 2,
type: 'template-container',
},
);
// Insert the board templates swimlane
Swimlanes.insert(
{
//title: TAPi18n.__('board-templates-swimlane'),
title: 'Board Templates',
boardId: this.boardId.get(),
sort: 3,
type: 'template-container',
},
);
Utils.goBoardId(this.boardId.get());
} else {
const visibility = this.visibility.get();
this.boardId.set(
Boards.insert({
title,
permission: visibility,
}),
);
Swimlanes.insert({
title: 'Default',
boardId: this.boardId.get(),
});
Utils.goBoardId(this.boardId.get());
}
Utils.goBoardId(this.boardId.get());
},
events() {
return [
{
'click .js-select-visibility'() {
this.setVisibility(this.currentData());
},
'click .js-change-visibility': this.toggleVisibilityMenu,
'click .js-import': Popup.open('boardImportBoard'),
submit: this.onSubmit,
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-board-template': Popup.open('searchElement'),
'click .js-toggle-add-template-container': this.toggleAddTemplateContainer,
return [{
'click .js-select-visibility'() {
this.setVisibility(this.currentData());
},
];
'click .js-change-visibility': this.toggleVisibilityMenu,
'click .js-import': Popup.open('boardImportBoard'),
submit: this.onSubmit,
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-board-template': Popup.open('searchElement'),
}];
},
}).register('createBoardPopup');
(class HeaderBarCreateBoard extends CreateBoard {
onSubmit(event) {
super.onSubmit(event);
onSubmit(evt) {
super.onSubmit(evt);
// Immediately star boards crated with the headerbar popup.
Meteor.user().toggleBoardStar(this.boardId.get());
}
}.register('headerBarCreateBoardPopup'));
}).register('headerBarCreateBoardPopup');
BlazeComponent.extendComponent({
visibilityCheck() {
@@ -323,11 +218,9 @@ BlazeComponent.extendComponent({
},
events() {
return [
{
'click .js-select-visibility': this.selectBoardVisibility,
},
];
return [{
'click .js-select-visibility': this.selectBoardVisibility,
}];
},
}).register('boardChangeVisibilityPopup');
@@ -342,134 +235,13 @@ BlazeComponent.extendComponent({
},
events() {
return [
{
'click .js-select-watch'() {
const level = this.currentData();
Meteor.call(
'watch',
'board',
Session.get('currentBoard'),
level,
(err, ret) => {
if (!err && ret) Popup.close();
},
);
},
return [{
'click .js-select-watch'() {
const level = this.currentData();
Meteor.call('watch', 'board', Session.get('currentBoard'), level, (err, ret) => {
if (!err && ret) Popup.close();
});
},
];
}];
},
}).register('boardChangeWatchPopup');
/*
BlazeComponent.extendComponent({
onCreated() {
//this.sortBy = new ReactiveVar();
////this.sortDirection = new ReactiveVar();
//this.setSortBy();
this.downClass = DOWNCLS;
this.upClass = UPCLS;
},
allowedSortValues() {
const types = [];
const pushed = {};
Meteor.user()
.getListSortTypes()
.forEach(type => {
const key = type.replace(/^-/, '');
if (pushed[key] === undefined) {
types.push({
name: key,
label: `list-label-${key}`,
shortLabel: `list-label-short-${key}`,
});
pushed[key] = 1;
}
});
return types;
},
Direction() {
return Meteor.user().getListSortByDirection() === -1
? this.downClass
: this.upClass;
},
sortby() {
return Meteor.user().getListSortBy();
},
setSortBy(type = null) {
const user = Meteor.user();
if (type === null) {
type = user._getListSortBy();
} else {
let value = '';
if (type.map) {
// is an array
value = (type[1] === -1 ? '-' : '') + type[0];
}
Meteor.call('setListSortBy', value);
}
//this.sortBy.set(type[0]);
//this.sortDirection.set(type[1]);
},
events() {
return [
{
'click .js-sort-by'(evt) {
evt.preventDefault();
const target = evt.target;
const sortby = target.getAttribute('name');
const down = !!target.querySelector(`.${this.upClass}`);
const direction = down ? -1 : 1;
this.setSortBy([sortby, direction]);
if (Utils.isMiniScreen) {
Popup.close();
}
},
},
];
},
}).register('listsortPopup');
*/
BlazeComponent.extendComponent({
events() {
return [
{
'click .js-sort-due'() {
const sortBy = {
dueAt: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('due-date'));
Popup.close();
},
'click .js-sort-title'() {
const sortBy = {
title: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('title'));
Popup.close();
},
'click .js-sort-created-asc'() {
const sortBy = {
createdAt: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-newest-first'));
Popup.close();
},
'click .js-sort-created-desc'() {
const sortBy = {
createdAt: -1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-oldest-first'));
Popup.close();
},
},
];
},
}).register('cardsSortPopup');
+32 -96
View File
@@ -1,11 +1,10 @@
template(name="boardList")
.wrapper
ul.board-list.clearfix.js-boards
ul.board-list.clearfix
li.js-add-board
a.board-list-item.label(title="{{_ 'add-board'}}")
| {{_ 'add-board'}}
a.board-list-item.label {{_ 'add-board'}}
each boards
li(class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board
li(class="{{#if isStarred}}starred{{/if}}" class=colorClass)
if isInvited
.board-list-item
span.details
@@ -17,97 +16,34 @@ template(name="boardList")
button.js-accept-invite.primary {{_ 'accept'}}
button.js-decline-invite {{_ 'decline'}}
else
if $eq type "template-container"
a.js-open-board.template-container.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'template-container'}}")
+viewer
= title
i.fa.js-star-board(
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
title="{{_ 'star-board-title'}}")
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
if isMiniScreen
i.fa.board-handle(
class="fa-arrows"
title="{{_ 'Drag board'}}")
unless isMiniScreen
if isSandstorm
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else if isAdministrable
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else if currentUser.isAdmin
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else
a.js-open-board.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}")
+viewer
= title
i.fa.js-star-board(
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
title="{{_ 'star-board-title'}}")
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
if isMiniScreen
i.fa.board-handle(
class="fa-arrows"
title="{{_ 'Drag board'}}")
unless isMiniScreen
if isSandstorm
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else if isAdministrable
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else if currentUser.isAdmin
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
a.js-open-board.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name
+viewer
= title
i.fa.js-star-board(
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
title="{{_ 'star-board-title'}}")
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
template(name="boardListHeaderBar")
h1 {{_ title }}
//.board-header-btns.right
// a.board-header-btn.js-open-archived-board
// i.fa.fa-archive
// span {{_ 'archives'}}
// a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
// i.fa.fa-clone
// span {{_ 'templates'}}
h1 {{_ 'my-boards'}}
.board-header-btns.right
a.board-header-btn.js-open-archived-board
i.fa.fa-archive
span {{_ 'archives'}}
a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
i.fa.fa-clone
span {{_ 'templates'}}
+47 -166
View File
@@ -1,5 +1,4 @@
const subManager = new SubsManager();
const { calculateIndex, enableClickOnTouch } = Utils;
Template.boardListHeaderBar.events({
'click .js-open-archived-board'() {
@@ -8,141 +7,30 @@ Template.boardListHeaderBar.events({
});
Template.boardListHeaderBar.helpers({
title() {
return FlowRouter.getRouteName() === 'home' ? 'my-boards' : 'public';
},
templatesBoardId() {
return Meteor.user() && Meteor.user().getTemplatesBoardId();
return Meteor.user().getTemplatesBoardId();
},
templatesBoardSlug() {
return Meteor.user() && Meteor.user().getTemplatesBoardSlug();
return Meteor.user().getTemplatesBoardSlug();
},
});
BlazeComponent.extendComponent({
onCreated() {
Meteor.subscribe('setting');
let currUser = Meteor.user();
let userLanguage;
if(currUser && currUser.profile){
userLanguage = currUser.profile.language
}
if (userLanguage) {
TAPi18n.setLanguage(userLanguage);
T9n.setLanguage(userLanguage);
}
},
onRendered() {
const itemsSelector = '.js-board:not(.placeholder)';
const $boards = this.$('.js-boards');
$boards.sortable({
connectWith: '.js-boards',
tolerance: 'pointer',
appendTo: '.board-list',
helper: 'clone',
distance: 7,
items: itemsSelector,
placeholder: 'board-wrapper placeholder',
start(evt, ui) {
ui.helper.css('z-index', 1000);
ui.placeholder.height(ui.helper.height());
EscapeActions.executeUpTo('popup-close');
},
stop(evt, ui) {
// To attribute the new index number, we need to get the DOM element
// of the previous and the following card -- if any.
const prevBoardDom = ui.item.prev('.js-board').get(0);
const nextBoardBom = ui.item.next('.js-board').get(0);
const sortIndex = calculateIndex(prevBoardDom, nextBoardBom, 1);
const boardDomElement = ui.item.get(0);
const board = Blaze.getData(boardDomElement);
// Normally the jquery-ui sortable library moves the dragged DOM element
// to its new position, which disrupts Blaze reactive updates mechanism
// (especially when we move the last card of a list, or when multiple
// users move some cards at the same time). To prevent these UX glitches
// we ask sortable to gracefully cancel the move, and to put back the
// DOM in its initial state. The card move is then handled reactively by
// Blaze with the below query.
$boards.sortable('cancel');
board.move(sortIndex.base);
},
});
// ugly touch event hotfix
enableClickOnTouch(itemsSelector);
// Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => {
if (Utils.isMiniScreen()) {
$boards.sortable({
handle: '.board-handle',
});
}
});
},
boards() {
const query = {
//archived: false,
////type: { $in: ['board','template-container'] },
//type: 'board',
$and: [
{ archived: false },
{ type: 'board' },
{ $or:[] }
]
};
if (FlowRouter.getRouteName() === 'home'){
query.$and[2].$or.push({'members.userId': Meteor.userId()});
const currUser = Users.findOne(Meteor.userId());
// const currUser = Users.findOne(Meteor.userId(), {
// fields: {
// orgs: 1,
// teams: 1,
// },
// });
let orgIdsUserBelongs = currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : '';
if(orgIdsUserBelongs && orgIdsUserBelongs != ''){
let orgsIds = orgIdsUserBelongs.split(',');
// for(let i = 0; i < orgsIds.length; i++){
// query.$and[2].$or.push({'orgs.orgId': orgsIds[i]});
// }
//query.$and[2].$or.push({'orgs': {$elemMatch : {orgId: orgsIds[0]}}});
query.$and[2].$or.push({'orgs.orgId': {$in : orgsIds}});
}
let teamIdsUserBelongs = currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : '';
if(teamIdsUserBelongs && teamIdsUserBelongs != ''){
let teamsIds = teamIdsUserBelongs.split(',');
// for(let i = 0; i < teamsIds.length; i++){
// query.$or[2].$or.push({'teams.teamId': teamsIds[i]});
// }
//query.$and[2].$or.push({'teams': { $elemMatch : {teamId: teamsIds[0]}}});
query.$and[2].$or.push({'teams.teamId': {$in : teamsIds}});
}
}
else query.permission = 'public';
return Boards.find(query, {
//sort: { sort: 1 /* boards default sorting */ },
});
return Boards.find({
archived: false,
'members.userId': Meteor.userId(),
type: 'board',
}, { sort: ['title'] });
},
isStarred() {
const user = Meteor.user();
return user && user.hasStarred(this.currentData()._id);
},
isAdministrable() {
const user = Meteor.user();
return user && user.isBoardAdmin(this.currentData()._id);
},
hasOvertimeCards() {
subManager.subscribe('board', this.currentData()._id, false);
@@ -160,53 +48,46 @@ BlazeComponent.extendComponent({
},
events() {
return [
{
'click .js-add-board': Popup.open('createBoard'),
'click .js-star-board'(evt) {
const boardId = this.currentData()._id;
Meteor.user().toggleBoardStar(boardId);
evt.preventDefault();
},
'click .js-clone-board'(evt) {
Meteor.call(
'copyBoard',
this.currentData()._id,
{
sort: Boards.find({ archived: false }).count(),
type: 'board',
title: Boards.findOne(this.currentData()._id).title,
},
(err, res) => {
if (err) {
this.setError(err.error);
} else {
Session.set('fromBoard', null);
Utils.goBoardId(res);
}
},
);
evt.preventDefault();
},
'click .js-archive-board'(evt) {
const boardId = this.currentData()._id;
Meteor.call('archiveBoard', boardId);
evt.preventDefault();
},
'click .js-accept-invite'() {
const boardId = this.currentData()._id;
Meteor.call('acceptInvite', boardId);
},
'click .js-decline-invite'() {
const boardId = this.currentData()._id;
Meteor.call('quitBoard', boardId, (err, ret) => {
if (!err && ret) {
Meteor.call('acceptInvite', boardId);
FlowRouter.go('home');
}
});
},
return [{
'click .js-add-board': Popup.open('createBoard'),
'click .js-star-board'(evt) {
const boardId = this.currentData()._id;
Meteor.user().toggleBoardStar(boardId);
evt.preventDefault();
},
];
'click .js-clone-board'(evt) {
Meteor.call('cloneBoard',
this.currentData()._id,
Session.get('fromBoard'),
(err, res) => {
if (err) {
this.setError(err.error);
} else {
Session.set('fromBoard', null);
Utils.goBoardId(res);
}
}
);
evt.preventDefault();
},
'click .js-archive-board'(evt) {
const boardId = this.currentData()._id;
Meteor.call('archiveBoard', boardId);
evt.preventDefault();
},
'click .js-accept-invite'() {
const boardId = this.currentData()._id;
Meteor.user().removeInvite(boardId);
},
'click .js-decline-invite'() {
const boardId = this.currentData()._id;
Meteor.call('quitBoard', boardId, (err, ret) => {
if (!err && ret) {
Meteor.user().removeInvite(boardId);
FlowRouter.go('home');
}
});
},
}];
},
}).register('boardList');
+6 -40
View File
@@ -7,23 +7,10 @@ $spaceBetweenTiles = 16px
li
float: left
width: 20%
width: 25%
box-sizing: border-box
position: relative
&.placeholder:after
content: '';
display: block;
background: darken(white, 20%)
border-radius: 3px;
height: 106px;
margin: 8px;
&.ui-sortable-helper
cursor: grabbing
transform: rotate(4deg)
display: block !important
&.starred
.fa-star,
.fa-star-o
@@ -33,20 +20,17 @@ $spaceBetweenTiles = 16px
overflow: hidden;
background-color: #999
color: #f6f6f6
min-height: 100px
height: 90px
font-size: 16px
line-height: 22px
border-radius: 3px
display: block
font-weight: 700
min-height: 18px
padding: 8px
margin: ($spaceBetweenTiles/2)
position: relative
text-decoration: none
word-wrap: break-word
&.template-container
border: 4px solid #fff
&.tile
background-size: auto
@@ -71,7 +55,7 @@ $spaceBetweenTiles = 16px
.label
font-weight: normal
line-height: 56px
line-height:90px
:hover
background-color:#939393
@@ -177,7 +161,6 @@ $spaceBetweenTiles = 16px
padding-right: 6px
.background-box
color: white
border-radius: 3px
background-size: cover
display: block
@@ -199,33 +182,16 @@ $spaceBetweenTiles = 16px
overflow: scroll
li
width: 50%
width: 33.3%
.board-list-item
overflow: hidden
height: 8rem
.board-list-item-sub-name
position: relative
top: -100px
left: -100px
.board-handle
position: absolute
padding: 7px
top: 50%
transform: translateY(-50%)
right: 10px
font-size: 24px
@media screen and (max-width: 360px)
li
width: 100%
.board-handle
position: absolute
padding: 7px
top: 50%
transform: translateY(-50%)
right: 10px
font-size: 24px
width: 50%
+12 -18
View File
@@ -37,23 +37,17 @@ template(name="attachmentsGalery")
i.fa.fa-download
| {{_ 'download'}}
if currentUser.isBoardMember
unless currentUser.isCommentOnly
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'}}
if currentUser.isBoardAdmin
a.js-confirm-delete
i.fa.fa-close
| {{_ 'delete'}}
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
unless currentUser.isWorker
//li.attachment-item.add-attachment
a.js-add-attachment(title="{{_ 'add-attachment' }}")
i.fa.fa-plus
li.attachment-item.add-attachment
a.js-add-attachment {{_ 'add-attachment' }}
+45 -91
View File
@@ -1,11 +1,10 @@
Template.attachmentsGalery.events({
'click .js-add-attachment': Popup.open('cardAttachments'),
'click .js-confirm-delete': Popup.afterConfirm(
'attachmentDelete',
'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete',
function() {
Attachments.remove(this._id);
Popup.close();
},
}
),
// If we let this event bubble, FlowRouter will handle it and empty the page
// content, see #101.
@@ -18,8 +17,8 @@ Template.attachmentsGalery.events({
'click .js-remove-cover'() {
Cards.findOne(this.cardId).unsetCover();
},
'click .js-preview-image'(event) {
Popup.open('previewAttachedImage').call(this, event);
'click .js-preview-image'(evt) {
Popup.open('previewAttachedImage').call(this, evt);
// when multiple thumbnails, if click one then another very fast,
// we might get a wrong width from previous img.
// when popup reused, onRendered() won't be called, so we cannot get there.
@@ -32,128 +31,83 @@ Template.attachmentsGalery.events({
// if the image is too large, we resize & center the popup.
if (w > 300) {
$('div.pop-over').css({
width: w + 20,
width: (w + 20),
position: 'absolute',
left: (window.innerWidth - w) / 2,
top: (window.innerHeight - h) / 2,
left: (window.innerWidth - w)/2,
top: (window.innerHeight - h)/2,
});
}
};
const url = $(event.currentTarget).attr('src');
if (img.src === url && img.complete) rePosPopup();
else img.onload = rePosPopup;
},
});
Template.attachmentsGalery.helpers({
isBoardAdmin() {
return Meteor.user().isBoardAdmin();
const url = $(evt.currentTarget).attr('src');
if (img.src === url && img.complete)
rePosPopup();
else
img.onload = rePosPopup;
},
});
Template.previewAttachedImagePopup.events({
'click .js-large-image-clicked'() {
'click .js-large-image-clicked'(){
Popup.close();
},
});
Template.cardAttachmentsPopup.events({
'change .js-attach-file'(event) {
'change .js-attach-file'(evt) {
const card = this;
const processFile = f => {
Utils.processUploadedAttachment(card, f, attachment => {
if (attachment && attachment._id && attachment.isImage()) {
card.setCover(attachment._id);
}
Popup.close();
});
};
FS.Utility.eachFile(event, f => {
if (
MAX_IMAGE_PIXEL > 0 &&
typeof f.type === 'string' &&
f.type.match(/^image/)
) {
// is image
const reader = new FileReader();
reader.onload = function(e) {
const dataurl = e && e.target && e.target.result;
if (dataurl !== undefined) {
Utils.shrinkImage({
dataurl,
maxSize: MAX_IMAGE_PIXEL,
ratio: COMPRESS_RATIO,
toBlob: true,
callback(blob) {
if (blob === false) {
processFile(f);
} else {
blob.name = f.name;
processFile(blob);
}
},
});
} else {
// couldn't process it let other function handle it?
processFile(f);
}
};
reader.readAsDataURL(f);
FS.Utility.eachFile(evt, (f) => {
const file = new FS.File(f);
if (card.isLinkedCard()) {
file.boardId = Cards.findOne(card.linkedId).boardId;
file.cardId = card.linkedId;
} else {
processFile(f);
file.boardId = card.boardId;
file.swimlaneId = card.swimlaneId;
file.listId = card.listId;
file.cardId = card._id;
}
file.userId = Meteor.userId();
const attachment = Attachments.insert(file);
if (attachment && attachment._id && attachment.isImage()) {
card.setCover(attachment._id);
}
Popup.close();
});
},
'click .js-computer-upload'(event, templateInstance) {
templateInstance.find('.js-attach-file').click();
event.preventDefault();
'click .js-computer-upload'(evt, tpl) {
tpl.find('.js-attach-file').click();
evt.preventDefault();
},
'click .js-upload-clipboard-image': Popup.open('previewClipboardImage'),
});
const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
let pastedResults = null;
Template.previewClipboardImagePopup.onRendered(() => {
// we can paste image from clipboard
const handle = results => {
$(document.body).pasteImageReader((results) => {
if (results.dataURL.startsWith('data:image/')) {
const direct = results => {
$('img.preview-clipboard-image').attr('src', results.dataURL);
pastedResults = results;
};
if (MAX_IMAGE_PIXEL) {
// if has size limitation on image we shrink it before uploading
Utils.shrinkImage({
dataurl: results.dataURL,
maxSize: MAX_IMAGE_PIXEL,
ratio: COMPRESS_RATIO,
callback(changed) {
if (changed !== false && !!changed) {
results.dataURL = changed;
}
direct(results);
},
});
} else {
direct(results);
}
$('img.preview-clipboard-image').attr('src', results.dataURL);
pastedResults = results;
}
};
$(document.body).pasteImageReader(handle);
});
// we can also drag & drop image file to it
$(document.body).dropImageReader(handle);
$(document.body).dropImageReader((results) => {
if (results.dataURL.startsWith('data:image/')) {
$('img.preview-clipboard-image').attr('src', results.dataURL);
pastedResults = results;
}
});
});
Template.previewClipboardImagePopup.events({
'click .js-upload-pasted-image'() {
const results = pastedResults;
if (results && results.file) {
window.oPasted = pastedResults;
const card = this;
const file = new FS.File(results.file);
if (!results.name) {
+2 -63
View File
@@ -4,7 +4,8 @@ template(name="cardCustomFieldsPopup")
li.item(class="")
a.name.js-select-field(href="#")
span.full-name
= name
+viewer
= name
if hasCustomField
i.fa.fa-check
hr
@@ -30,10 +31,6 @@ template(name="cardCustomField-text")
= value
else
| {{_ 'edit'}}
else
+viewer
= value
template(name="cardCustomField-number")
if canModifyCard
@@ -48,34 +45,6 @@ template(name="cardCustomField-number")
= value
else
| {{_ 'edit'}}
else
if value
= value
template(name="cardCustomField-checkbox")
.js-checklist-item.checklist-item(class="{{#if data.value }}is-checked{{/if}}")
if canModifyCard
.check-box-container
.check-box.materialCheckBox(class="{{#if data.value }}is-checked{{/if}}")
else
.materialCheckBox(class="{{#if data.value }}is-checked{{/if}}")
template(name="cardCustomField-currency")
if canModifyCard
+inlinedForm(classNames="js-card-customfield-currency")
input(type="text" value=data.value)
.edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
else
a.js-open-inlined-form
if value
= formattedValue
else
| {{_ 'edit'}}
else
if value
= formattedValue
template(name="cardCustomField-date")
if canModifyCard
@@ -86,11 +55,6 @@ template(name="cardCustomField-date")
| {{showDate}}
else
| {{_ 'edit'}}
else
if value
div.card-date
time(datetime="{{showISODate}}")
| {{showDate}}
template(name="cardCustomField-dropdown")
if canModifyCard
@@ -115,28 +79,3 @@ template(name="cardCustomField-dropdown")
= selectedItem
else
| {{_ 'edit'}}
else
if value
+viewer
= selectedItem
template(name="cardCustomField-stringtemplate")
if canModifyCard
+inlinedForm(classNames="js-card-customfield-stringtemplate")
each item in stringtemplateItems.get
input.js-card-customfield-stringtemplate-item(type="text" value=item placeholder="")
input.js-card-customfield-stringtemplate-item.last(type="text" value="" placeholder="{{_ 'custom-field-stringtemplate-item-placeholder'}}" autofocus)
.edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
else
a.js-open-inlined-form
if value
+viewer
= formattedValue
else
| {{_ 'edit'}}
else
if value
+viewer
= formattedValue
+42 -189
View File
@@ -1,6 +1,3 @@
import { DatePicker } from '/client/lib/datepicker';
import Cards from '/models/cards';
Template.cardCustomFieldsPopup.helpers({
hasCustomField() {
const card = Cards.findOne(Session.get('currentCard'));
@@ -10,21 +7,22 @@ Template.cardCustomFieldsPopup.helpers({
});
Template.cardCustomFieldsPopup.events({
'click .js-select-field'(event) {
'click .js-select-field'(evt) {
const card = Cards.findOne(Session.get('currentCard'));
const customFieldId = this._id;
card.toggleCustomField(customFieldId);
event.preventDefault();
evt.preventDefault();
},
'click .js-settings'(event) {
'click .js-settings'(evt) {
EscapeActions.executeUpTo('detailsPane');
Sidebar.setView('customFields');
event.preventDefault();
evt.preventDefault();
},
});
// cardCustomField
const CardCustomField = BlazeComponent.extendComponent({
getTemplate() {
return `cardCustomField-${this.data().definition.type}`;
},
@@ -36,105 +34,52 @@ const CardCustomField = BlazeComponent.extendComponent({
},
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly()
);
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});
CardCustomField.register('cardCustomField');
// cardCustomField-text
(class extends CardCustomField {
onCreated() {
super.onCreated();
}
events() {
return [
{
'submit .js-card-customfield-text'(event) {
event.preventDefault();
const value = this.currentComponent().getValue();
this.card.setCustomField(this.customFieldId, value);
},
return [{
'submit .js-card-customfield-text'(evt) {
evt.preventDefault();
const value = this.currentComponent().getValue();
this.card.setCustomField(this.customFieldId, value);
},
];
}];
}
}.register('cardCustomField-text'));
}).register('cardCustomField-text');
// cardCustomField-number
(class extends CardCustomField {
onCreated() {
super.onCreated();
}
events() {
return [
{
'submit .js-card-customfield-number'(event) {
event.preventDefault();
const value = parseInt(this.find('input').value, 10);
this.card.setCustomField(this.customFieldId, value);
},
return [{
'submit .js-card-customfield-number'(evt) {
evt.preventDefault();
const value = parseInt(this.find('input').value, 10);
this.card.setCustomField(this.customFieldId, value);
},
];
}
}.register('cardCustomField-number'));
// cardCustomField-checkbox
(class extends CardCustomField {
onCreated() {
super.onCreated();
}];
}
toggleItem() {
this.card.setCustomField(this.customFieldId, !this.data().value);
}
events() {
return [
{
'click .js-checklist-item .check-box-container': this.toggleItem,
},
];
}
}.register('cardCustomField-checkbox'));
// cardCustomField-currency
(class extends CardCustomField {
onCreated() {
super.onCreated();
this.currencyCode = this.data().definition.settings.currencyCode;
}
formattedValue() {
const locale = TAPi18n.getLanguage();
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: this.currencyCode,
}).format(this.data().value);
}
events() {
return [
{
'submit .js-card-customfield-currency'(event) {
event.preventDefault();
// To allow input separated by comma, the comma is replaced by a period.
const value = Number(this.find('input').value.replace(/,/i, '.'), 10);
this.card.setCustomField(this.customFieldId, value);
},
},
];
}
}.register('cardCustomField-currency'));
}).register('cardCustomField-number');
// cardCustomField-date
(class extends CardCustomField {
onCreated() {
super.onCreated();
const self = this;
@@ -163,10 +108,8 @@ CardCustomField.register('cardCustomField');
}
classes() {
if (
this.date.get().isBefore(this.now.get(), 'minute') &&
this.now.get().isBefore(this.data().value)
) {
if (this.date.get().isBefore(this.now.get(), 'minute') &&
this.now.get().isBefore(this.data().value)) {
return 'current';
}
return '';
@@ -177,13 +120,12 @@ CardCustomField.register('cardCustomField');
}
events() {
return [
{
'click .js-edit-date': Popup.open('cardCustomField-date'),
},
];
return [{
'click .js-edit-date': Popup.open('cardCustomField-date'),
}];
}
}.register('cardCustomField-date'));
}).register('cardCustomField-date');
// cardCustomField-datePopup
(class extends DatePicker {
@@ -202,10 +144,11 @@ CardCustomField.register('cardCustomField');
_deleteDate() {
this.card.setCustomField(this.customFieldId, '');
}
}.register('cardCustomField-datePopup'));
}).register('cardCustomField-datePopup');
// cardCustomField-dropdown
(class extends CardCustomField {
onCreated() {
super.onCreated();
this._items = this.data().definition.settings.dropdownItems;
@@ -217,110 +160,20 @@ CardCustomField.register('cardCustomField');
}
selectedItem() {
const selected = this._items.find(item => {
const selected = this._items.find((item) => {
return item._id === this.data().value;
});
return selected
? selected.name
: TAPi18n.__('custom-field-dropdown-unknown');
return (selected) ? selected.name : TAPi18n.__('custom-field-dropdown-unknown');
}
events() {
return [
{
'submit .js-card-customfield-dropdown'(event) {
event.preventDefault();
const value = this.find('select').value;
this.card.setCustomField(this.customFieldId, value);
},
return [{
'submit .js-card-customfield-dropdown'(evt) {
evt.preventDefault();
const value = this.find('select').value;
this.card.setCustomField(this.customFieldId, value);
},
];
}
}.register('cardCustomField-dropdown'));
// cardCustomField-stringtemplate
(class extends CardCustomField {
onCreated() {
super.onCreated();
this.stringtemplateFormat = this.data().definition.settings.stringtemplateFormat;
this.stringtemplateSeparator = this.data().definition.settings.stringtemplateSeparator;
this.stringtemplateItems = new ReactiveVar(this.data().value ?? []);
}];
}
formattedValue() {
return (this.data().value ?? [])
.filter(value => !!value.trim())
.map(value => this.stringtemplateFormat.replace(/%\{value\}/gi, value))
.join(this.stringtemplateSeparator ?? '');
}
getItems() {
return Array.from(this.findAll('input'))
.map(input => input.value)
.filter(value => !!value.trim());
}
events() {
return [
{
'submit .js-card-customfield-stringtemplate'(event) {
event.preventDefault();
const items = this.getItems();
this.card.setCustomField(this.customFieldId, items);
},
'keydown .js-card-customfield-stringtemplate-item'(event) {
if (event.keyCode === 13) {
event.preventDefault();
if (event.metaKey || event.ctrlKey) {
this.find('button[type=submit]').click();
} else if (event.target.value.trim()) {
const inputLast = this.find('input.last');
let items = this.getItems();
if (event.target === inputLast) {
inputLast.value = '';
} else if (event.target.nextSibling === inputLast) {
inputLast.focus();
} else {
event.target.blur();
const idx = Array.from(this.findAll('input')).indexOf(
event.target,
);
items.splice(idx + 1, 0, '');
Tracker.afterFlush(() => {
const element = this.findAll('input')[idx + 1];
element.focus();
element.value = '';
});
}
this.stringtemplateItems.set(items);
}
}
},
'blur .js-card-customfield-stringtemplate-item'(event) {
if (
!event.target.value.trim() ||
event.target === this.find('input.last')
) {
const items = this.getItems();
this.stringtemplateItems.set(items);
this.find('input.last').value = '';
}
},
'click .js-close-inlined-form'(event) {
this.stringtemplateItems.set(this.data().value ?? []);
},
},
];
}
}.register('cardCustomField-stringtemplate'));
}).register('cardCustomField-dropdown');
-4
View File
@@ -8,7 +8,3 @@ template(name="dateBadge")
time(datetime="{{showISODate}}")
| {{showDate}}
template(name="dateCustomField")
a(title="{{showTitle}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
+132 -132
View File
@@ -1,22 +1,102 @@
import { DatePicker } from '/client/lib/datepicker';
// Edit received, start, due & end dates
BlazeComponent.extendComponent({
template() {
return 'editCardDate';
},
onCreated() {
this.error = new ReactiveVar('');
this.card = this.data();
this.date = new ReactiveVar(moment.invalid());
},
onRendered() {
const $picker = this.$('.js-datepicker').datepicker({
todayHighlight: true,
todayBtn: 'linked',
language: TAPi18n.getLanguage(),
}).on('changeDate', function(evt) {
this.find('#date').value = moment(evt.date).format('L');
this.error.set('');
this.find('#time').focus();
}.bind(this));
if (this.date.get().isValid()) {
$picker.datepicker('update', this.date.get().toDate());
}
},
showDate() {
if (this.date.get().isValid())
return this.date.get().format('L');
return '';
},
showTime() {
if (this.date.get().isValid())
return this.date.get().format('LT');
return '';
},
dateFormat() {
return moment.localeData().longDateFormat('L');
},
timeFormat() {
return moment.localeData().longDateFormat('LT');
},
events() {
return [{
'keyup .js-date-field'() {
// parse for localized date format in strict mode
const dateMoment = moment(this.find('#date').value, 'L', true);
if (dateMoment.isValid()) {
this.error.set('');
this.$('.js-datepicker').datepicker('update', dateMoment.toDate());
}
},
'keyup .js-time-field'() {
// parse for localized time format in strict mode
const dateMoment = moment(this.find('#time').value, 'LT', true);
if (dateMoment.isValid()) {
this.error.set('');
}
},
'submit .edit-date'(evt) {
evt.preventDefault();
// if no time was given, init with 12:00
const time = evt.target.time.value || moment(new Date().setHours(12, 0, 0)).format('LT');
const dateString = `${evt.target.date.value} ${time}`;
const newDate = moment(dateString, 'L LT', true);
if (newDate.isValid()) {
this._storeDate(newDate.toDate());
Popup.close();
}
else {
this.error.set('invalid-date');
evt.target.date.focus();
}
},
'click .js-delete-date'(evt) {
evt.preventDefault();
this._deleteDate();
Popup.close();
},
}];
},
});
Template.dateBadge.helpers({
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});
// editCardReceivedDatePopup
(class extends DatePicker {
onCreated() {
super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
this.data().getReceived() &&
this.date.set(moment(this.data().getReceived()));
super.onCreated();
this.data().getReceived() && this.date.set(moment(this.data().getReceived()));
}
_storeDate(date) {
@@ -26,22 +106,20 @@ Template.dateBadge.helpers({
_deleteDate() {
this.card.setReceived(null);
}
}.register('editCardReceivedDatePopup'));
}).register('editCardReceivedDatePopup');
// editCardStartDatePopup
(class extends DatePicker {
onCreated() {
super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
super.onCreated();
this.data().getStart() && this.date.set(moment(this.data().getStart()));
}
onRendered() {
super.onRendered();
if (moment.isDate(this.card.getReceived())) {
this.$('.js-datepicker').datepicker(
'setStartDate',
this.card.getReceived(),
);
this.$('.js-datepicker').datepicker('setStartDate', this.card.getReceived());
}
}
@@ -52,12 +130,12 @@ Template.dateBadge.helpers({
_deleteDate() {
this.card.setStart(null);
}
}.register('editCardStartDatePopup'));
}).register('editCardStartDatePopup');
// editCardDueDatePopup
(class extends DatePicker {
onCreated() {
super.onCreated('1970-01-01 17:00:00');
super.onCreated();
this.data().getDue() && this.date.set(moment(this.data().getDue()));
}
@@ -75,12 +153,12 @@ Template.dateBadge.helpers({
_deleteDate() {
this.card.setDue(null);
}
}.register('editCardDueDatePopup'));
}).register('editCardDueDatePopup');
// editCardEndDatePopup
(class extends DatePicker {
onCreated() {
super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
super.onCreated();
this.data().getEnd() && this.date.set(moment(this.data().getEnd()));
}
@@ -98,7 +176,8 @@ Template.dateBadge.helpers({
_deleteDate() {
this.card.setEnd(null);
}
}.register('editCardEndDatePopup'));
}).register('editCardEndDatePopup');
// Display received, start, due & end dates
const CardDate = BlazeComponent.extendComponent({
@@ -145,20 +224,17 @@ class CardReceivedDate extends CardDate {
const startAt = this.data().getStart();
const theDate = this.date.get();
// if dueAt, endAt and startAt exist & are > receivedAt, receivedAt doesn't need to be flagged
if (
(startAt && theDate.isAfter(startAt)) ||
(endAt && theDate.isAfter(endAt)) ||
(dueAt && theDate.isAfter(dueAt))
)
if (((startAt) && (theDate.isAfter(dueAt))) ||
((endAt) && (theDate.isAfter(endAt))) ||
((dueAt) && (theDate.isAfter(dueAt))))
classes += 'long-overdue';
else classes += 'current';
else
classes += 'current';
return classes;
}
showTitle() {
return `${TAPi18n.__('card-received-on')} ${this.date
.get()
.format('LLLL')}`;
return `${TAPi18n.__('card-received-on')} ${this.date.get().format('LLLL')}`;
}
events() {
@@ -185,10 +261,13 @@ class CardStartDate extends CardDate {
const theDate = this.date.get();
const now = this.now.get();
// if dueAt or endAt exist & are > startAt, startAt doesn't need to be flagged
if ((endAt && theDate.isAfter(endAt)) || (dueAt && theDate.isAfter(dueAt)))
if (((endAt) && (theDate.isAfter(endAt))) ||
((dueAt) && (theDate.isAfter(dueAt))))
classes += 'long-overdue';
else if (theDate.isAfter(now)) classes += '';
else classes += 'current';
else if (theDate.isBefore(now, 'minute'))
classes += 'almost-due';
else
classes += 'current';
return classes;
}
@@ -219,12 +298,17 @@ class CardDueDate extends CardDate {
const theDate = this.date.get();
const now = this.now.get();
// if the due date is after the end date, green - done early
if (endAt && theDate.isAfter(endAt)) classes += 'current';
if ((endAt) && (theDate.isAfter(endAt)))
classes += 'current';
// if there is an end date, don't need to flag the due date
else if (endAt) classes += '';
else if (now.diff(theDate, 'days') >= 2) classes += 'long-overdue';
else if (now.diff(theDate, 'minute') >= 0) classes += 'due';
else if (now.diff(theDate, 'days') >= -1) classes += 'almost-due';
else if (endAt)
classes += '';
else if (now.diff(theDate, 'days') >= 2)
classes += 'long-overdue';
else if (now.diff(theDate, 'minute') >= 0)
classes += 'due';
else if (now.diff(theDate, 'days') >= -1)
classes += 'almost-due';
return classes;
}
@@ -253,9 +337,12 @@ class CardEndDate extends CardDate {
let classes = 'end-date' + ' ';
const dueAt = this.data().getDue();
const theDate = this.date.get();
if (!dueAt) classes += '';
else if (theDate.isBefore(dueAt)) classes += 'current';
else if (theDate.isAfter(dueAt)) classes += 'due';
if (theDate.diff(dueAt, 'days') >= 2)
classes += 'long-overdue';
else if (theDate.diff(dueAt, 'days') >= 0)
classes += 'due';
else if (theDate.diff(dueAt, 'days') >= -2)
classes += 'almost-due';
return classes;
}
@@ -271,113 +358,26 @@ class CardEndDate extends CardDate {
}
CardEndDate.register('cardEndDate');
class CardCustomFieldDate extends CardDate {
template() {
return 'dateCustomField';
}
onCreated() {
super.onCreated();
const self = this;
self.autorun(() => {
self.date.set(moment(self.data().value));
});
}
classes() {
return 'customfield-date';
}
showTitle() {
return '';
}
events() {
return [];
}
}
CardCustomFieldDate.register('cardCustomFieldDate');
(class extends CardReceivedDate {
showDate() {
return this.date.get().format('l');
}
}.register('minicardReceivedDate'));
}).register('minicardReceivedDate');
(class extends CardStartDate {
showDate() {
return this.date.get().format('l');
}
}.register('minicardStartDate'));
}).register('minicardStartDate');
(class extends CardDueDate {
showDate() {
return this.date.get().format('l');
}
}.register('minicardDueDate'));
}).register('minicardDueDate');
(class extends CardEndDate {
showDate() {
return this.date.get().format('l');
}
}.register('minicardEndDate'));
(class extends CardCustomFieldDate {
showDate() {
return this.date.get().format('l');
}
}.register('minicardCustomFieldDate'));
class VoteEndDate extends CardDate {
onCreated() {
super.onCreated();
const self = this;
self.autorun(() => {
self.date.set(moment(self.data().getVoteEnd()));
});
}
classes() {
const classes = 'end-date' + ' ';
return classes;
}
showDate() {
return this.date.get().format('l LT');
}
showTitle() {
return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`;
}
events() {
return super.events().concat({
'click .js-edit-date': Popup.open('editVoteEndDate'),
});
}
}
VoteEndDate.register('voteEndDate');
class PokerEndDate extends CardDate {
onCreated() {
super.onCreated();
const self = this;
self.autorun(() => {
self.date.set(moment(self.data().getPokerEnd()));
});
}
classes() {
const classes = 'end-date' + ' ';
return classes;
}
showDate() {
return this.date.get().format('l LT');
}
showTitle() {
return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`;
}
events() {
return super.events().concat({
'click .js-edit-date': Popup.open('editPokerEndDate'),
});
}
}
PokerEndDate.register('pokerEndDate');
}).register('minicardEndDate');
+5 -9
View File
@@ -2,11 +2,11 @@
display: block
border-radius: 4px
padding: 1px 3px
background-color: #dbdbdb
&:hover, &.is-active
background-color: #b3b3b3
&.current, &.almost-due, &.due, &.long-overdue
color: #fff
@@ -14,17 +14,17 @@
background-color: #5ba639
&:hover, &.is-active
background-color: darken(#5ba639, 10)
&.almost-due
background-color: #edc909
&:hover, &.is-active
background-color: darken(#edc909, 10)
&.due
background-color: #fa3f00
&:hover, &.is-active
background-color: darken(#fa3f00, 10)
&.long-overdue
background-color: #fd5d47
&:hover, &.is-active
@@ -57,7 +57,3 @@
-webkit-font-smoothing: antialiased
margin-right: 0.3em
.customfield-date
display: block
border-radius: 4px
padding: 1px 3px
@@ -1,7 +0,0 @@
template(name="descriptionForm")
.new-description.js-new-description(
class="{{#if descriptionFormIsOpen}}is-open{{/if}}")
form.js-new-description-form
+editor(class="js-new-description-input" autofocus="autofocus")
| {{getUnsavedValue 'cardDescription' _id getDescription}}
@@ -1,34 +0,0 @@
const descriptionFormIsOpen = new ReactiveVar(false);
BlazeComponent.extendComponent({
onDestroyed() {
descriptionFormIsOpen.set(false);
$('.note-popover').hide();
},
descriptionFormIsOpen() {
return descriptionFormIsOpen.get();
},
getInput() {
return this.$('.js-new-description-input');
},
events() {
return [
{
'submit .js-card-description'(event) {
event.preventDefault();
const description = this.currentComponent().getValue();
this.data().setDescription(description);
},
// Pressing Ctrl+Enter should submit the form
'keydown form textarea'(evt) {
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
this.find('button[type=submit]').click();
}
},
},
];
},
}).register('descriptionForm');
@@ -1,59 +0,0 @@
@import 'nib'
.new-description
position: relative
margin: 0 0 20px 0
&.is-open
.helper
display: inline-block
textarea
min-height: 100px
color: #4d4d4d
cursor: auto
overflow: hidden
word-wrap: break-word
.too-long
margin-top: 8px
textarea
background-color: #fff
border: 0
box-shadow: 0 1px 2px rgba(0, 0, 0, .23)
height: 36px
margin: 4px 4px 6px 0
padding: 9px 11px
width: 100%
&:hover,
&:is-open
background-color: #fff
box-shadow: 0 1px 3px rgba(0, 0, 0, .33)
border: 0
cursor: pointer
&:is-open
cursor: auto
.description-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-description
display: flex
margin: 5px
a
display: block
margin: auto
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+25 -330
View File
@@ -1,95 +1,9 @@
@import 'nib'
// Assignee, code copied from wekan/client/users/userAvatar.styl
avatar-radius = 50%
#cardURL_copy
// Have clipboard text not visible by moving it to far left
position: absolute
left: -2000px
top: 0px
#clipboard
white-space: normal
.assignee
border-radius: 3px
display: block
position: relative
float: left
height: 30px
width: @height
margin: 0 4px 4px 0
cursor: pointer
user-select: none
z-index: 1
text-decoration: none
border-radius: avatar-radius
.avatar
overflow: hidden
border-radius: avatar-radius
&.avatar-assignee-initials
height: 70%
width: @height
padding: 15%
background-color: #dbdbdb
color: #444444
position: absolute
&.avatar-image
object-fit: cover;
object-position: center;
height: 100%
width: @height
.assignee-presence-status
background-color: #b3b3b3
border: 1px solid #fff
border-radius: 50%
height: 7px
width: @height
position: absolute
right: -1px
bottom: -1px
border: 1px solid white
z-index: 15
&.active
background: #64c464
border-color: #daf1da
&.idle
background: #e4e467
border-color: #f7f7d4
&.disconnected
background: #bdbdbd
border-color: #ededed
&.pending
background: #e44242
border-color: #f1dada
&.add-assignee
display: flex
align-items: center
justify-content: center
box-shadow: 0 0 0 2px darken(white, 25%) inset
&:hover, &.is-active
box-shadow: 0 0 0 2px darken(white, 60%) inset
// Other card details
.card-details
padding: 0
flex-shrink: 0
flex-basis: 600px
flex-basis: 510px
will-change: flex-basis
overflow-y: scroll
overflow-x: hidden
@@ -99,62 +13,37 @@ avatar-radius = 50%
animation: flexGrowIn 0.1s
box-shadow: 0 0 7px 0 darken(white, 30%)
transition: flex-basis 0.1s
box-sizing: border-box
.mCustomScrollBox
padding-left: 0
.ps-scrollbar-y-rail
pointer-event: all
position: absolute;
.card-details-canvas
width: auto
padding: 0 20px
width: 470px
padding-left: 20px;
.card-details-header
margin: 0 -20px 5px
padding: 7px 20px
padding 7px 16px
background: darken(white, 7%)
border-bottom: 1px solid darken(white, 14%)
.close-card-details,
.maximize-card-details,
.minimize-card-details,
.card-details-menu,
.card-copy-button,
.card-copy-mobile-button,
.close-card-details-mobile-web,
.card-details-menu-mobile-web
.card-details-menu
float: right
.close-card-details,
.maximize-card-details,
.minimize-card-details
font-size: 24px
padding: 5px 10px 5px 10px
margin-right: -8px
.close-card-details-mobile-web
.close-card-details
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
margin-right: -8px
.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
@@ -197,38 +86,30 @@ avatar-radius = 50%
display: flex
flex-wrap: wrap
margin: 15px 0
& ~ .js-open-inlined-form
.viewer
background-color #ffffff !important
padding 15px !important
border 1px solid #000000 !important
.card-details-item
margin-right: 0.5em
&:last-child
margin-right: 0
&.card-details-item-labels
display: block
word-wrap: break-word
max-width: 95%
flex-grow: 1
&.card-details-item-labels,
&.card-details-item-members,
&.card-details-item-assignees,
&.card-details-item-customfield,
&.card-details-item-name
display: block
word-wrap: break-word
max-width: 36%
flex-grow: 1
&.card-details-item-creator,
&.card-details-item-received,
&.card-details-item-start,
&.card-details-item-due,
&.card-details-item-end
display: block
word-wrap: break-word
max-width: 28%
&.card-details-item-end,
&.card-details-item-customfield,
&.card-details-item-name
max-width: 50%
flex-grow: 1
.card-details-item-title
font-size: 16px
font-weight: bold
color: #4d4d4d
color: #000
.card-label
padding-top: 5px
@@ -237,43 +118,6 @@ avatar-radius = 50%
.activities
padding-top: 10px
.card-details-maximized
padding: 0
flex-shrink: 0
flex-basis: calc(100% - 20px)
will-change: flex-basis
overflow-y: scroll
overflow-x: scroll
background: darken(white, 3%)
border-radius: bottom 3px
z-index: 1000 !important
animation: flexGrowIn 0.1s
box-shadow: 0 0 7px 0 darken(white, 30%)
transition: flex-basis 0.1s
box-sizing: border-box
position: absolute
top: 0
left: 0
height: calc(100% - 20px)
width: calc(100% - 20px)
float: left
.card-details-left
position: absolute
float: left
top: 60px
left: 20px
width: 47%
.card-details-right
position: absolute
float: right
top: 20px
left: 50%
.card-details-header
width: 47%
input[type="text"].attachment-add-link-input
float: left
margin: 0 0 8px
@@ -287,27 +131,21 @@ input[type="submit"].attachment-add-link-submit
@media screen and (max-width: 800px)
.card-details
width: calc(100% - 1px)
width: calc(100% - 40px)
padding: 0px 20px 0px 20px
margin: 0px
transition: none
.card-details-canvas
width: 100%
padding-left: 0px
padding-left: 0px;
.card-details-header
.close-card-details
margin-right: 0px
.card-details-menu
margin-right: 40px
.maximize-card-details
margin-right: 40px
.minimize-card-details
margin-right: 40px
margin-right: 10px
card-details-color(background, color...)
background: background !important
@@ -389,146 +227,3 @@ card-details-color(background, color...)
.card-details-indigo
card-details-color(#4b0082, #ffffff) //White text for better visibility
.voted
opacity: .7
.vote-title
display: flex
justify-content: space-between
.js-edit-date
align-self: baseline
margin-left: 5px
.vote-result
display: flex
.js-show-positive-votes
cursor: pointer
.poker-voted
opacity: .7
.poker-title
display: flex
justify-content: space-between
.js-edit-date
align-self: baseline
margin-left: 5px
.poker-result
display: flex
flex-flow: row wrap
.js-show-positive-poker-votes
cursor: pointer
.poker-deck
display: grid
flex-direction: column
text-align: center
.poker-card-result
width: 32px
font-size: 1em
font-weight: bold
padding: 4px 2px 4px 2px
cursor: default
.winner
font-weight: bold
outline: #2d2d2d solid 2px
.loser
opacity: .5
.responsive-table
overflow-x: auto
.poker-table
display: table
width: 100%
padding-top: 10px
.poker-table-row
display: table-row
.poker-table-heading
background-color: #EEE
display: table-header-group
.poker-table-cell
display: table-cell
padding: 0 0 5px 2px
border-bottom: 1px solid #d2d0d0
text-align: center
min-width: 45px
.poker-table-cell-who
width: 150px
vertical-align: middle
.poker-table-heading-left,
.poker-table-heading-right
display: table-header-group
font-weight: bold
border-top: 1px solid #808080
@media (max-width: 400px)
.poker-table-heading-right
display: none
.poker-table-body
display: table-row-group
.poker-table-side-left,
.poker-table-side-right
display: inline-block
.poker-table-side-right
padding-left: 10px
@media (max-width: 400px)
.poker-table-side-right
padding-left: 0px
.estimation-add
display: block
overflow: auto
margin-top: 15px
margin-bottom: 5px
input
display: inline-block
float: right
margin: auto
margin-right: 10px
width: 100px
button
display: inline-block
float: right
margin: auto
.poker-card
width:48px
height:72px
float:left
background:#fff
border-radius:5px
display:table
box-sizing:border-box
padding:5px
margin:3px
font-size:20px
font-weight: bold
text-shadow: #2d2d2d 1px 1px 0
box-shadow:0 0 5px #aaaaaa
text-align:center
position:relative
cursor: pointer
.inner
display:table-cell
vertical-align:middle
border-radius:5px
overflow:hidden
background-color: #cecece
+25 -37
View File
@@ -20,31 +20,29 @@ BlazeComponent.extendComponent({
this.card.setSpentTime(null);
},
events() {
return [
{
//TODO : need checking this portion
'submit .edit-time'(evt) {
evt.preventDefault();
return [{
//TODO : need checking this portion
'submit .edit-time'(evt) {
evt.preventDefault();
const spentTime = parseFloat(evt.target.time.value);
const isOvertime = this.card.getIsOvertime();
const spentTime = parseFloat(evt.target.time.value);
const isOvertime = this.card.getIsOvertime();
if (spentTime >= 0) {
this.storeTime(spentTime, isOvertime);
Popup.close();
} else {
this.error.set('invalid-time');
evt.target.time.focus();
}
},
'click .js-delete-time'(evt) {
evt.preventDefault();
this.deleteTime();
if (spentTime >= 0) {
this.storeTime(spentTime, isOvertime);
Popup.close();
},
'click a.js-toggle-overtime': this.toggleOvertime,
} else {
this.error.set('invalid-time');
evt.target.time.focus();
}
},
];
'click .js-delete-time'(evt) {
evt.preventDefault();
this.deleteTime();
Popup.close();
},
'click a.js-toggle-overtime': this.toggleOvertime,
}];
},
}).register('editCardSpentTimePopup');
@@ -58,33 +56,23 @@ BlazeComponent.extendComponent({
},
showTitle() {
if (this.data().getIsOvertime()) {
return `${TAPi18n.__(
'overtime',
)} ${this.data().getSpentTime()} ${TAPi18n.__('hours')}`;
return `${TAPi18n.__('overtime')} ${this.data().getSpentTime()} ${TAPi18n.__('hours')}`;
} else {
return `${TAPi18n.__(
'card-spent',
)} ${this.data().getSpentTime()} ${TAPi18n.__('hours')}`;
return `${TAPi18n.__('card-spent')} ${this.data().getSpentTime()} ${TAPi18n.__('hours')}`;
}
},
showTime() {
return this.data().getSpentTime();
},
events() {
return [
{
'click .js-edit-time': Popup.open('editCardSpentTime'),
},
];
return [{
'click .js-edit-time': Popup.open('editCardSpentTime'),
}];
},
}).register('cardSpentTime');
Template.timeBadge.helpers({
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly()
);
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});
+7 -54
View File
@@ -1,17 +1,5 @@
template(name="checklists")
.checklists-title
h3.card-details-item-title
i.fa.fa-check
| {{_ 'checklists'}}
if currentUser.isBoardMember
.material-toggle-switch(title="{{_ 'hide-checked-items'}}")
//span.toggle-switch-title
if hideCheckedItems
input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton" checked="checked")
else
input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton")
label.toggle-label(for="toggleHideCheckedItemsButton")
h3 {{_ 'checklists'}}
if toggleDeleteDialog.get
.board-overlay#card-details-overlay
+checklistDeleteDialog(checklist = checklistToDelete)
@@ -25,8 +13,9 @@ template(name="checklists")
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId)
+addChecklistItemForm
else
a.js-open-inlined-form(title="{{_ 'add-checklist'}}")
a.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-checklist'}}...
template(name="checklistDetail")
.js-checklist.checklist
@@ -40,8 +29,6 @@ template(name="checklistDetail")
if canModifyCard
h2.title.js-open-inlined-form.is-editable
if isMiniScreenOrShowDesktopDragHandles
span.fa.checklist-handle(class="fa-arrows" title="{{_ 'dragChecklist'}}")
+viewer
= checklist.title
else
@@ -80,9 +67,6 @@ template(name="editChecklistItemForm")
span(title=createdAt) {{ moment createdAt }}
if canModifyCard
a.js-delete-checklist-item {{_ "delete"}}...
a.js-convert-checklist-item-to-card
i.fa.fa-copy
| {{_ 'convertChecklistItemToCardPopup-title'}}
template(name="checklistItems")
.checklist-items.js-checklist-items
@@ -95,16 +79,14 @@ template(name="checklistItems")
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist)
+addChecklistItemForm
else
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
a.add-checklist-item.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-checklist-item'}}...
template(name='checklistItemDetail')
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if hideCheckedItems}} invisible{{/if}}{{/if}}")
.js-checklist-item.checklist-item
if canModifyCard
.check-box-container
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
if isMiniScreenOrShowDesktopDragHandles
span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}")
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
@@ -113,32 +95,3 @@ template(name='checklistItemDetail')
.item-title(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
template(name="convertChecklistItemToCardPopup")
label(for='convert-checklist-item-to-card-title') {{_ 'title'}}:
textarea#copy-card-title.minicard-composer-textarea.js-card-title(autofocus)
= item.title
+boardsSwimlanesAndLists
template(name="boardsSwimlanesAndLists")
unless currentUser.isWorker
label {{_ 'boards'}}:
select.js-select-boards(autofocus)
each boards
if $eq _id currentBoard._id
option(value="{{_id}}" selected) {{_ 'current'}}
else
option(value="{{_id}}") {{title}}
label {{_ 'swimlanes'}}:
select.js-select-swimlanes
each swimlanes
option(value="{{_id}}") {{title}}
label {{_ 'lists'}}:
select.js-select-lists
each aBoardLists
option(value="{{_id}}") {{title}}
.edit-controls.clearfix
button.primary.confirm.js-done {{_ 'done'}}
+33 -139
View File
@@ -1,8 +1,4 @@
import Cards from '/models/cards';
import Boards from '/models/boards';
const subManager = new SubsManager();
const { calculateIndexData, capitalize } = Utils;
const { calculateIndexData, enableClickOnTouch } = Utils;
function initSorting(items) {
items.sortable({
@@ -10,7 +6,7 @@ function initSorting(items) {
helper: 'clone',
items: '.js-checklist-item:not(.placeholder)',
connectWith: '.js-checklist-items',
appendTo: 'parent',
appendTo: '.board-canvas',
distance: 7,
placeholder: 'checklist-item placeholder',
scroll: false,
@@ -40,6 +36,9 @@ function initSorting(items) {
checklistItem.move(checklistId, sortIndex.base);
},
});
// ugly touch event hotfix
enableClickOnTouch('.js-checklist-item:not(.placeholder)');
}
BlazeComponent.extendComponent({
@@ -55,38 +54,30 @@ BlazeComponent.extendComponent({
return Meteor.user() && Meteor.user().isBoardMember();
}
// Disable sorting if the current user is not a board member or is a miniscreen
// Disable sorting if the current user is not a board member
self.autorun(() => {
const $itemsDom = $(self.itemsDom);
if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
if ($itemsDom.data('sortable')) {
$(self.itemsDom).sortable('option', 'disabled', !userIsMember());
if (Utils.isMiniScreenOrShowDesktopDragHandles()) {
$(self.itemsDom).sortable({
handle: 'span.fa.checklistitem-handle',
});
}
}
});
},
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
}).register('checklistDetail');
BlazeComponent.extendComponent({
addChecklist(event) {
event.preventDefault();
const textarea = this.find('textarea.js-add-checklist-item');
const title = textarea.value.trim();
let cardId = this.currentData().cardId;
const card = Cards.findOne(cardId);
if (card.isLinked()) cardId = card.linkedId;
if (card.isLinked())
cardId = card.linkedId;
if (title) {
Checklists.insert({
@@ -95,9 +86,7 @@ BlazeComponent.extendComponent({
sort: card.checklists().count(),
});
setTimeout(() => {
this.$('.add-checklist-item')
.last()
.click();
this.$('.add-checklist-item').last().click();
}, 100);
}
textarea.value = '';
@@ -115,7 +104,7 @@ BlazeComponent.extendComponent({
title,
checklistId: checklist._id,
cardId: checklist.cardId,
sort: Utils.calculateIndexData(checklist.lastItem()).base,
sort: checklist.itemCount(),
});
}
// We keep the form opened, empty it.
@@ -124,12 +113,7 @@ BlazeComponent.extendComponent({
},
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
deleteChecklist() {
@@ -180,118 +164,40 @@ BlazeComponent.extendComponent({
}
},
focusChecklistItem(event) {
// If a new checklist is created, pre-fill the title and select it.
const checklist = this.currentData().checklist;
if (!checklist) {
const textarea = event.target;
textarea.value = capitalize(TAPi18n.__('r-checklist'));
textarea.select();
}
},
events() {
const events = {
'click .toggle-delete-checklist-dialog'(event) {
if ($(event.target).hasClass('js-delete-checklist')) {
if($(event.target).hasClass('js-delete-checklist')){
this.checklistToDelete = this.currentData().checklist; //Store data context
}
this.toggleDeleteDialog.set(!this.toggleDeleteDialog.get());
},
'click #toggleHideCheckedItemsButton'() {
Meteor.call('toggleHideCheckedItems');
},
};
return [
{
...events,
'submit .js-add-checklist': this.addChecklist,
'submit .js-edit-checklist-title': this.editChecklist,
'submit .js-add-checklist-item': this.addChecklistItem,
'submit .js-edit-checklist-item': this.editChecklistItem,
'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'),
'click .js-delete-checklist-item': this.deleteItem,
'click .confirm-checklist-delete': this.deleteChecklist,
'focus .js-add-checklist-item': this.focusChecklistItem,
keydown: this.pressKey,
},
];
return [{
...events,
'submit .js-add-checklist': this.addChecklist,
'submit .js-edit-checklist-title': this.editChecklist,
'submit .js-add-checklist-item': this.addChecklistItem,
'submit .js-edit-checklist-item': this.editChecklistItem,
'click .js-delete-checklist-item': this.deleteItem,
'click .confirm-checklist-delete': this.deleteChecklist,
keydown: this.pressKey,
}];
},
}).register('checklists');
BlazeComponent.extendComponent({
onCreated() {
subManager.subscribe('board', Session.get('currentBoard'), false);
this.selectedBoardId = new ReactiveVar(Session.get('currentBoard'));
},
boards() {
return Boards.find(
{
archived: false,
'members.userId': Meteor.userId(),
_id: { $ne: Meteor.user().getTemplatesBoardId() },
},
{
sort: { sort: 1 /* boards default sorting */ },
},
);
},
swimlanes() {
const board = Boards.findOne(this.selectedBoardId.get());
return board.swimlanes();
},
aBoardLists() {
const board = Boards.findOne(this.selectedBoardId.get());
return board.lists();
},
events() {
return [
{
'change .js-select-boards'(event) {
this.selectedBoardId.set($(event.currentTarget).val());
subManager.subscribe('board', this.selectedBoardId.get(), false);
},
},
];
},
}).register('boardsSwimlanesAndLists');
Template.checklists.helpers({
hideCheckedItems() {
const currentUser = Meteor.user();
if (currentUser) return currentUser.hasHideCheckedItems();
return false;
},
});
Template.addChecklistItemForm.onRendered(() => {
autosize($('textarea.js-add-checklist-item'));
});
Template.editChecklistItemForm.onRendered(() => {
autosize($('textarea.js-edit-checklist-item'));
});
Template.checklistDeleteDialog.onCreated(() => {
const $cardDetails = this.$('.card-details');
this.scrollState = {
position: $cardDetails.scrollTop(), //save current scroll position
this.scrollState = { position: $cardDetails.scrollTop(), //save current scroll position
top: false, //required for smooth scroll animation
};
//Callback's purpose is to only prevent scrolling after animation is complete
$cardDetails.animate({ scrollTop: 0 }, 500, () => {
this.scrollState.top = true;
});
$cardDetails.animate({ scrollTop: 0 }, 500, () => { this.scrollState.top = true; });
//Prevent scrolling while dialog is open
$cardDetails.on('scroll', () => {
if (this.scrollState.top) {
//If it's already in position, keep it there. Otherwise let animation scroll
if(this.scrollState.top) { //If it's already in position, keep it there. Otherwise let animation scroll
$cardDetails.scrollTop(0);
}
});
@@ -300,22 +206,12 @@ Template.checklistDeleteDialog.onCreated(() => {
Template.checklistDeleteDialog.onDestroyed(() => {
const $cardDetails = this.$('.card-details');
$cardDetails.off('scroll'); //Reactivate scrolling
$cardDetails.animate({ scrollTop: this.scrollState.position });
$cardDetails.animate( { scrollTop: this.scrollState.position });
});
Template.checklistItemDetail.helpers({
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
},
hideCheckedItems() {
const user = Meteor.user();
if (user) return user.hasHideCheckedItems();
return false;
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});
@@ -328,10 +224,8 @@ BlazeComponent.extendComponent({
}
},
events() {
return [
{
'click .js-checklist-item .check-box-container': this.toggleItem,
},
];
return [{
'click .js-checklist-item .check-box': this.toggleItem,
}];
},
}).register('checklistItemDetail');
+2 -33
View File
@@ -16,10 +16,6 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
&:hover
color: inherit
.checklists-title
display: flex
justify-content: space-between
.checklist-title
.checkbox
float: left
@@ -42,11 +38,6 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
.js-delete-checklist
@extends .delete-text
span.fa.checklist-handle
padding-right: 20px
padding-top: 3px
float: left
.js-confirm-checklist-delete
background-color: darken(white, 3%)
@@ -108,17 +99,6 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
margin-top: 3px
display: flex
background: darken(white, 3%)
opacity: 1
transition: height 0ms 400ms, opacity 400ms 0ms
height: auto
overflow: hidden
&.is-checked.invisible
opacity: 0
height: 0
transition: height 0ms 0ms, opacity 600ms 0ms
margin-top: 0
margin-bottom: 0
&.placeholder
background: darken(white, 20%)
@@ -133,9 +113,6 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
&:hover
background-color: darken(white, 8%)
.check-box-container
padding-right: 10px;
.check-box
margin: 0.1em 0 0 0;
&.is-checked
@@ -144,23 +121,15 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
.item-title
flex: 1
padding-left: 10px;
&.is-checked
color: #8c8c8c
font-style: italic
text-decoration: line-through
& .viewer
p
margin-bottom: 2px
display: block
word-wrap: break-word
max-width: 420px
span.fa.checklistitem-handle
padding-top: 2px
padding-right: 10px;
.js-delete-checklist-item,
.js-convert-checklist-item-to-card
.js-delete-checklist-item
margin: 0 0 0.5em 1.33em
@extends .delete-text
padding: 12px 0 0 0
+17 -25
View File
@@ -9,7 +9,7 @@ BlazeComponent.extendComponent({
},
labels() {
return labelColors.map(color => ({ color, name: '' }));
return labelColors.map((color) => ({ color, name: '' }));
},
isSelected(color) {
@@ -17,13 +17,11 @@ BlazeComponent.extendComponent({
},
events() {
return [
{
'click .js-palette-color'() {
this.currentColor.set(this.currentData().color);
},
return [{
'click .js-palette-color'() {
this.currentColor.set(this.currentData().color);
},
];
}];
},
}).register('formLabel');
@@ -40,19 +38,19 @@ Template.createLabelPopup.helpers({
});
Template.cardLabelsPopup.events({
'click .js-select-label'(event) {
'click .js-select-label'(evt) {
const card = Cards.findOne(Session.get('currentCard'));
const labelId = this._id;
card.toggleLabel(labelId);
event.preventDefault();
evt.preventDefault();
},
'click .js-edit-label': Popup.open('editLabel'),
'click .js-add-label': Popup.open('createLabel'),
});
Template.formLabel.events({
'click .js-palette-color'(event) {
const $this = $(event.currentTarget);
'click .js-palette-color'(evt) {
const $this = $(evt.currentTarget);
// hide selected ll colors
$('.js-palette-select').addClass('hide');
@@ -64,14 +62,11 @@ Template.formLabel.events({
Template.createLabelPopup.events({
// Create the new label
'submit .create-label'(event, templateInstance) {
event.preventDefault();
'submit .create-label'(evt, tpl) {
evt.preventDefault();
const board = Boards.findOne(Session.get('currentBoard'));
const name = templateInstance
.$('#labelName')
.val()
.trim();
const color = Blaze.getData(templateInstance.find('.fa-check')).color;
const name = tpl.$('#labelName').val().trim();
const color = Blaze.getData(tpl.find('.fa-check')).color;
board.addLabel(name, color);
Popup.back();
},
@@ -83,14 +78,11 @@ Template.editLabelPopup.events({
board.removeLabel(this._id);
Popup.back(2);
}),
'submit .edit-label'(event, templateInstance) {
event.preventDefault();
'submit .edit-label'(evt, tpl) {
evt.preventDefault();
const board = Boards.findOne(Session.get('currentBoard'));
const name = templateInstance
.$('#labelName')
.val()
.trim();
const color = Blaze.getData(templateInstance.find('.fa-check')).color;
const name = tpl.$('#labelName').val().trim();
const color = Blaze.getData(tpl.find('.fa-check')).color;
board.editLabel(this._id, name, color);
Popup.back();
},
+1 -15
View File
@@ -10,10 +10,9 @@
margin-right: 4px
margin-bottom: 5px
padding: 3px 8px
max-width: 210px
max-width: 100%
min-width: 8px
overflow: ellipsis
word-wrap: break-word
height: 18px
vertical-align: bottom
@@ -44,20 +43,9 @@
align-items: center
justify-content: center
.card-label-white
background-color: #ffffff
color: #000000 //Black text for better visibility
border: 1px solid #c0c0c0
.card-label-white:hover
color: #aaaaaa //grey text for better visibility
.card-label-green
background-color: #3cb500
.card-label-green:hover
color: #000000 //Black hover text for better visibility
.card-label-yellow
background-color: #fad900
color: #000000 //Black text for better visibility
@@ -169,8 +157,6 @@
.edit-labels-pop-over
margin-bottom: 8px
.card-label .viewer p
margin: 0
.edit-labels-pop-over .shortcut
display: inline-block
+10 -64
View File
@@ -3,25 +3,15 @@ template(name="minicard")
class="{{#if isLinkedCard}}linked-card{{/if}}"
class="{{#if isLinkedBoard}}linked-board{{/if}}"
class="minicard-{{colorClass}}")
if isMiniScreen
.handle
.fa.fa-arrows
unless isMiniScreen
if showDesktopDragHandles
.handle
.fa.fa-arrows
if cover
.minicard-cover(style="background-image: url('{{cover.url}}');")
if labels
.minicard-labels
each labels
unless hiddenMinicardLabelText
span.card-label(class="card-label-{{color}}" title=name)
+viewer
= name
if hiddenMinicardLabelText
.minicard-label(class="card-label-{{color}}" title="{{name}}")
.minicard-label(class="card-label-{{color}}" title="{{name}}")
.minicard-title
.handle
.fa.fa-arrows
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
.parent-prefix
| {{ parentString ' > ' }}
@@ -58,8 +48,6 @@ template(name="minicard")
if getDue
.date
+minicardDueDate
if getEnd
+minicardEndDate
if getSpentTime
.date
+cardSpentTime
@@ -67,42 +55,20 @@ template(name="minicard")
.minicard-custom-fields
each customFieldsWD
if definition.showOnCard
if trueValue
.minicard-custom-field
if definition.showLabelOnMiniCard
.minicard-custom-field-item
+viewer
= definition.name
.minicard-custom-field
if definition.showLabelOnMiniCard
.minicard-custom-field-item
if $eq definition.type "currency"
+viewer
= formattedCurrencyCustomFieldValue(definition)
else if $eq definition.type "date"
.date
+minicardCustomFieldDate
else if $eq definition.type "checkbox"
.materialCheckBox(class="{{#if value }}is-checked{{/if}}")
else if $eq definition.type "stringtemplate"
+viewer
= formattedStringtemplateCustomFieldValue(definition)
else
+viewer
= trueValue
if getAssignees
.minicard-assignees.js-minicard-assignees
each getAssignees
+userAvatar(userId=this)
+viewer
= definition.name
.minicard-custom-field-item
+viewer
= trueValue
if getMembers
.minicard-members.js-minicard-members
each getMembers
+userAvatar(userId=this)
if showCreator
.minicard-creator
+userAvatar(userId=this.userId noRemove=true)
.badges
unless currentUser.isNoComments
if comments.count
@@ -115,17 +81,6 @@ template(name="minicard")
if getDescription
.badge.badge-state-image-only(title=getDescription)
span.badge-icon.fa.fa-align-left
if getVoteQuestion
.badge.badge-state-image-only(title=getVoteQuestion)
span.badge-icon.fa.fa-thumbs-up(class="{{#if voteState}}text-green{{/if}}")
span.badge-text {{ voteCountPositive }}
span.badge-icon.fa.fa-thumbs-down(class="{{#if $eq voteState false}}text-red{{/if}}")
span.badge-text {{ voteCountNegative }}
if getPokerQuestion
.badge.badge-state-image-only(title=getPokerQuestion)
span.badge-icon.fa.fa-check(class="{{#if pokerState}}text-green{{/if}}")
if expiredPoker
span.badge-text {{ getPokerEstimation }}
if attachments.count
.badge
span.badge-icon.fa.fa-paperclip
@@ -134,12 +89,3 @@ template(name="minicard")
.badge(class="{{#if checklistFinished}}is-finished{{/if}}")
span.badge-icon.fa.fa-check-square-o
span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}}
if allSubtasks.count
.badge
span.badge-icon.fa.fa-sitemap
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
if currentBoard.allowsCardSortingByNumber
.badge
span.badge-icon.fa.fa-sort
span.badge-text {{ sort }}
+7 -82
View File
@@ -7,89 +7,14 @@ BlazeComponent.extendComponent({
return 'minicard';
},
formattedCurrencyCustomFieldValue(definition) {
const customField = this.data()
.customFieldsWD()
.find(f => f._id === definition._id);
const customFieldTrueValue =
customField && customField.trueValue ? customField.trueValue : '';
const locale = TAPi18n.getLanguage();
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: definition.settings.currencyCode,
}).format(customFieldTrueValue);
},
formattedStringtemplateCustomFieldValue(definition) {
const customField = this.data()
.customFieldsWD()
.find(f => f._id === definition._id);
const customFieldTrueValue =
customField && customField.trueValue ? customField.trueValue : [];
return customFieldTrueValue
.filter(value => !!value.trim())
.map(value =>
definition.settings.stringtemplateFormat.replace(/%\{value\}/gi, value),
)
.join(definition.settings.stringtemplateSeparator ?? '');
},
showCreator() {
if (this.data().board()) {
return (
this.data().board.allowsCreator === null ||
this.data().board().allowsCreator === undefined ||
this.data().board().allowsCreator
);
// return this.data().board().allowsCreator;
}
return false;
},
events() {
return [
{
'click .js-linked-link'() {
if (this.data().isLinkedCard()) Utils.goCardId(this.data().linkedId);
else if (this.data().isLinkedBoard())
Utils.goBoardId(this.data().linkedId);
},
return [{
'click .js-linked-link' () {
if (this.data().isLinkedCard())
Utils.goCardId(this.data().linkedId);
else if (this.data().isLinkedBoard())
Utils.goBoardId(this.data().linkedId);
},
{
'click .js-toggle-minicard-label-text'() {
if (window.localStorage.getItem('hiddenMinicardLabelText')) {
window.localStorage.removeItem('hiddenMinicardLabelText'); //true
} else {
window.localStorage.setItem('hiddenMinicardLabelText', 'true'); //true
}
},
},
];
}];
},
}).register('minicard');
Template.minicard.helpers({
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
hiddenMinicardLabelText() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).hiddenMinicardLabelText;
} else if (window.localStorage.getItem('hiddenMinicardLabelText')) {
return true;
} else {
return false;
}
},
});
+18 -41
View File
@@ -79,27 +79,21 @@
border-radius: top 2px
.minicard-labels
float: none
float: right
display: flex
flex-wrap: wrap
.minicard-label
width: 11px
height: @width
border-radius: 2px
margin-right: 3px
margin-bottom: 3px
margin-left: 3px
.minicard-custom-fields
display:block;
.minicard-custom-field
display:flex;
.minicard-custom-field-item
flex-grow: 1
display: block
word-wrap: break-word
max-width: 100px
margin-right: 4px
max-width:50%;
flex-grow:1;
.handle
width: 20px;
height: 20px;
@@ -107,7 +101,7 @@
right: 5px;
top: 5px;
display:none;
@media only screen {
@media only screen and (max-width: 1199px) {
display:block;
}
.fa-arrows
@@ -117,9 +111,7 @@
p:last-child
margin-bottom: 0
.viewer
display: block
word-wrap: break-word
max-width: 230px
display: inline-block
.dates
display: flex;
flex-direction: row;
@@ -152,7 +144,14 @@
.badge-text
vertical-align: middle
&.badge-comment
margin-bottom: 0.1rem
display: block
border-radius: 4px
padding: 1px 3px
margin-bottom: 0.3rem
color: #ff0000
background-color: #ffffff
font-weight: bold
font-size: 11pt
.badge-text
font-size: 0.9em
@@ -162,37 +161,20 @@
padding-left: 0px
line-height: 12px
.minicard-members,
.minicard-assignees,
.minicard-creator
.minicard-members
float: right
margin-left: 5px
margin-bottom: 4px
margin: 2px -8px -2px 0
.member
float: right
border-radius: 50%
height: 28px
width: @height
margin-bottom: 4px
.assignee
float: right
border-radius: 50%
height: 28px
width: @height
+ .badges
margin-top: 5px
margin-top: 10px
.minicard-assignees
border-bottom: 1px solid red
.minicard-creator
border-bottom: 1px solid green
.minicard-members:empty,
.minicard-assignees:empty
.minicard-members:empty
display: none
&.minicard-composer
@@ -310,8 +292,3 @@ minicard-color(background, color...)
.minicard-indigo
minicard-color(#4b0082, #ffffff) //White text for better visibility
.text-red
color:red
.text-green
color:green
-44
View File
@@ -1,44 +0,0 @@
template(name="resultCard")
.result-card-wrapper
a.minicard-wrapper.card-title(href=originRelativeUrl)
+minicard(this)
//= card.title
ul.result-card-context-list
li.result-card-context(title="{{_ 'board'}}")
.result-card-block-wrapper
if boardId
+viewer
= getBoard.title
else
.broken-cards-null
| NULL
if getBoard.archived
i.fa.fa-archive
li.result-card-context.result-card-context-separator
= ' '
| {{_ 'context-separator'}}
= ' '
li.result-card-context(title="{{_ 'swimlane'}}")
.result-card-block-wrapper
if swimlaneId
+viewer
= getSwimlane.title
else
.broken-cards-null
| NULL
if getSwimlane.archived
i.fa.fa-archive
li.result-card-context.result-card-context-separator
= ' '
| {{_ 'context-separator'}}
= ' '
li.result-card-context(title="{{_ 'list'}}")
.result-card-block-wrapper
if listId
+viewer
= getList.title
else
.broken-cards-null
| NULL
if getList.archived
i.fa.fa-archive
-11
View File
@@ -1,11 +0,0 @@
Template.resultCard.helpers({
userId() {
return Meteor.userId();
},
});
BlazeComponent.extendComponent({
events() {
return [{}];
},
}).register('resultCard');
-24
View File
@@ -1,24 +0,0 @@
.result-card-list-wrapper
margin: 1rem
border-radius: 5px
padding: 1.5rem
padding-top: 0.75rem
display: inline-block
min-width: 250px
max-width: 350px
.result-card-wrapper
margin-top: 0
margin-bottom: 10px
.result-card-context
display: inline-block
.result-card-context-separator
font-weight: bold
.result-card-context-list
margin-bottom: 0.7rem
.result-card-block-wrapper
display: inline-block
+9 -12
View File
@@ -1,11 +1,9 @@
template(name="subtasks")
h3.card-details-item-title
i.fa.fa-sitemap
| {{_ 'subtasks'}}
if currentUser.isBoardAdmin
if toggleDeleteDialog.get
.board-overlay#card-details-overlay
+subtaskDeleteDialog(subtask = subtaskToDelete)
h3 {{_ 'subtasks'}}
if toggleDeleteDialog.get
.board-overlay#card-details-overlay
+subtaskDeleteDialog(subtask = subtaskToDelete)
.card-subtasks-items
each subtask in currentCard.subtasks
@@ -15,8 +13,9 @@ template(name="subtasks")
+inlinedForm(autoclose=false classNames="js-add-subtask" cardId = cardId)
+addSubtaskItemForm
else
a.js-open-inlined-form(title="{{_ 'add-subtask'}}")
a.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-subtask'}}...
template(name="subtaskDetail")
.js-subtasks.subtask
@@ -27,8 +26,7 @@ template(name="subtaskDetail")
span
a.js-view-subtask(title="{{ subtask.title }}") {{_ "view-it"}}
if canModifyCard
if currentUser.isBoardAdmin
a.js-delete-subtask.toggle-delete-subtask-dialog {{_ "delete"}}...
a.js-delete-subtask.toggle-delete-subtask-dialog {{_ "delete"}}...
if canModifyCard
h2.title.js-open-inlined-form.is-editable
@@ -68,8 +66,7 @@ template(name="editSubtaskItemForm")
a.fa.fa-times-thin.js-close-inlined-form
span(title=createdAt) {{ moment createdAt }}
if canModifyCard
if currentUser.isBoardAdmin
a.js-delete-subtask-item {{_ "delete"}}...
a.js-delete-subtask-item {{_ "delete"}}...
template(name="subtasksItems")
.subtasks-items.js-subtasks-items
+20 -56
View File
@@ -1,15 +1,11 @@
BlazeComponent.extendComponent({
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
}).register('subtaskDetail');
BlazeComponent.extendComponent({
addSubtask(event) {
event.preventDefault();
const textarea = this.find('textarea.js-add-subtask-item');
@@ -20,22 +16,7 @@ BlazeComponent.extendComponent({
const crtBoard = Boards.findOne(card.boardId);
const targetBoard = crtBoard.getDefaultSubtasksBoard();
const listId = targetBoard.getDefaultSubtasksListId();
//Get the full swimlane data for the parent task.
const parentSwimlane = Swimlanes.findOne({
boardId: crtBoard._id,
_id: card.swimlaneId,
});
//find the swimlane of the same name in the target board.
const targetSwimlane = Swimlanes.findOne({
boardId: targetBoard._id,
title: parentSwimlane.title,
});
//If no swimlane with a matching title exists in the target board, fall back to the default swimlane.
const swimlaneId =
targetSwimlane === undefined
? targetBoard.getDefaultSwimline()._id
: targetSwimlane._id;
const swimlaneId = targetBoard.getDefaultSwimline()._id;
if (title) {
const _id = Cards.insert({
@@ -57,10 +38,9 @@ BlazeComponent.extendComponent({
// See https://github.com/wekan/wekan/issues/80
Filter.addException(_id);
setTimeout(() => {
this.$('.add-subtask-item')
.last()
.click();
this.$('.add-subtask-item').last().click();
}, 100);
}
textarea.value = '';
@@ -68,12 +48,7 @@ BlazeComponent.extendComponent({
},
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
deleteSubtask() {
@@ -110,13 +85,13 @@ BlazeComponent.extendComponent({
events() {
const events = {
'click .toggle-delete-subtask-dialog'(event) {
if ($(event.target).hasClass('js-delete-subtask')) {
if($(event.target).hasClass('js-delete-subtask')){
this.subtaskToDelete = this.currentData().subtask; //Store data context
}
this.toggleDeleteDialog.set(!this.toggleDeleteDialog.get());
},
'click .js-view-subtask'(event) {
if ($(event.target).hasClass('js-view-subtask')) {
if($(event.target).hasClass('js-view-subtask')){
const subtask = this.currentData().subtask;
const board = subtask.board();
FlowRouter.go('card', {
@@ -128,33 +103,27 @@ BlazeComponent.extendComponent({
},
};
return [
{
...events,
'submit .js-add-subtask': this.addSubtask,
'submit .js-edit-subtask-title': this.editSubtask,
'click .confirm-subtask-delete': this.deleteSubtask,
keydown: this.pressKey,
},
];
return [{
...events,
'submit .js-add-subtask': this.addSubtask,
'submit .js-edit-subtask-title': this.editSubtask,
'click .confirm-subtask-delete': this.deleteSubtask,
keydown: this.pressKey,
}];
},
}).register('subtasks');
Template.subtaskDeleteDialog.onCreated(() => {
const $cardDetails = this.$('.card-details');
this.scrollState = {
position: $cardDetails.scrollTop(), //save current scroll position
this.scrollState = { position: $cardDetails.scrollTop(), //save current scroll position
top: false, //required for smooth scroll animation
};
//Callback's purpose is to only prevent scrolling after animation is complete
$cardDetails.animate({ scrollTop: 0 }, 500, () => {
this.scrollState.top = true;
});
$cardDetails.animate({ scrollTop: 0 }, 500, () => { this.scrollState.top = true; });
//Prevent scrolling while dialog is open
$cardDetails.on('scroll', () => {
if (this.scrollState.top) {
//If it's already in position, keep it there. Otherwise let animation scroll
if(this.scrollState.top) { //If it's already in position, keep it there. Otherwise let animation scroll
$cardDetails.scrollTop(0);
}
});
@@ -163,17 +132,12 @@ Template.subtaskDeleteDialog.onCreated(() => {
Template.subtaskDeleteDialog.onDestroyed(() => {
const $cardDetails = this.$('.card-details');
$cardDetails.off('scroll'); //Reactivate scrolling
$cardDetails.animate({ scrollTop: this.scrollState.position });
$cardDetails.animate( { scrollTop: this.scrollState.position });
});
Template.subtaskItemDetail.helpers({
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});
+6 -6
View File
@@ -86,7 +86,7 @@ select
margin-bottom: 8px
&.inline
width: 100%
width: 100%
option[disabled]
color: #8c8c8c
@@ -242,11 +242,11 @@ textarea
margin: 3px 4px
// Material Design checkboxes
[type="checkbox"]:not(:checked),
[type="checkbox"]:checked
position: absolute
left: -9999px
visibility: hidden
[type="checkbox"]:not(:checked),
[type="checkbox"]:checked
position: absolute
left: -9999px
visibility: hidden
.materialCheckBox
position: relative
@@ -1,37 +0,0 @@
export function csvGetMembersToMap(data) {
// we will work on the list itself (an ordered array of objects) when a
// mapping is done, we add a 'wekan' field to the object representing the
// imported member
const membersToMap = [];
const importedMembers = [];
let membersIndex;
for (let i = 0; i < data[0].length; i++) {
if (data[0][i].toLowerCase() === 'members') {
membersIndex = i;
}
}
for (let i = 1; i < data.length; i++) {
if (data[i][membersIndex]) {
for (const importedMember of data[i][membersIndex].split(' ')) {
if (importedMember && importedMembers.indexOf(importedMember) === -1) {
importedMembers.push(importedMember);
}
}
}
}
for (let importedMember of importedMembers) {
importedMember = {
username: importedMember,
id: importedMember,
};
const wekanUser = Users.findOne({ username: importedMember.username });
if (wekanUser) importedMember.wekanId = wekanUser._id;
membersToMap.push(importedMember);
}
return membersToMap;
}
+30 -32
View File
@@ -13,43 +13,41 @@ template(name="import")
template(name="importTextarea")
form
p: label(for='import-textarea') {{_ instruction}} {{_ 'import-board-instruction-about-errors'}}
textarea.js-import-json(id='import-textarea' placeholder="{{_ importPlaceHolder}}" autofocus)
textarea.js-import-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus)
| {{jsonText}}
if isSandstorm
h1.warning {{_ 'import-sandstorm-backup-warning'}}
p.warning {{_ 'import-sandstorm-warning'}}
input.primary.wide(type="submit" value="{{_ 'import'}}")
template(name="importMapMembers")
h2 {{_ 'import-map-members'}}
if usersLoaded.get
.map-members
p {{_ 'import-members-map'}}
p.import-members-map-note
| {{_ 'import-members-map-note' }}
.mapping-list
each members
a.mapping-item.js-select-member(class="{{#if wekanId}}filled{{/if}}")
.profile-source
.full-name= fullName
.username
| ({{username}})
.wekan
if wekanId
+userAvatar(userId=wekanId)
else
a.member.add-member
i.fa.fa-plus
//-
Due to the way the flewbox layout is working, we need to set some
invisible items so that the last row items have a consistent width.
See http://jsfiddle.net/Ln4h3c4n/ for an minimal example of the issue.
.mapping-item.ghost-item
.mapping-item.ghost-item
.mapping-item.ghost-item
.mapping-item.ghost-item
.mapping-item.ghost-item
form
input.primary.wide(type="submit" value="{{_ 'done'}}")
else
+spinner
.map-members
p {{_ 'import-members-map'}}
.mapping-list
each members
a.mapping-item.js-select-member(class="{{#if wekanId}}filled{{/if}}")
.profile-source
.full-name= fullName
.username
| ({{username}})
.wekan
if wekanId
+userAvatar(userId=wekanId)
else
a.member.add-member
i.fa.fa-plus
//-
Due to the way the flewbox layout is working, we need to set some
invisible items so that the last row items have a consistent width.
See http://jsfiddle.net/Ln4h3c4n/ for an minimal example of the issue.
.mapping-item.ghost-item
.mapping-item.ghost-item
.mapping-item.ghost-item
.mapping-item.ghost-item
.mapping-item.ghost-item
form
input.primary.wide(type="submit" value="{{_ 'done'}}")
template(name="importMapMembersAddPopup")
.select-member
+60 -123
View File
@@ -1,8 +1,5 @@
import { trelloGetMembersToMap } from './trelloMembersMapper';
import { wekanGetMembersToMap } from './wekanMembersMapper';
import { csvGetMembersToMap } from './csvMembersMapper';
const Papa = require('papaparse');
import trelloMembersMapper from './trelloMembersMapper';
import wekanMembersMapper from './wekanMembersMapper';
BlazeComponent.extendComponent({
title() {
@@ -33,30 +30,20 @@ BlazeComponent.extendComponent({
}
},
importData(evt, dataSource) {
importData(evt) {
evt.preventDefault();
const input = this.find('.js-import-json').value;
if (dataSource === 'csv') {
const csv = input.indexOf('\t') > 0 ? input.replace(/(\t)/g, ',') : input;
const ret = Papa.parse(csv);
if (ret && ret.data && ret.data.length) this.importedData.set(ret.data);
else throw new Meteor.Error('error-csv-schema');
const membersToMap = this._prepareAdditionalData(ret.data);
const dataJson = this.find('.js-import-json').value;
try {
const dataObject = JSON.parse(dataJson);
this.setError('');
this.importedData.set(dataObject);
const membersToMap = this._prepareAdditionalData(dataObject);
// store members data and mapping in Session
// (we go deep and 2-way, so storing in data context is not a viable option)
this.membersToMap.set(membersToMap);
this.nextStep();
} else {
try {
const dataObject = JSON.parse(input);
this.setError('');
this.importedData.set(dataObject);
const membersToMap = this._prepareAdditionalData(dataObject);
// store members data and mapping in Session
// (we go deep and 2-way, so storing in data context is not a viable option)
this.membersToMap.set(membersToMap);
this.nextStep();
} catch (e) {
this.setError('error-json-malformed');
}
} catch (e) {
this.setError('error-json-malformed');
}
},
@@ -69,7 +56,7 @@ BlazeComponent.extendComponent({
const membersMapping = this.membersToMap.get();
if (membersMapping) {
const mappingById = {};
membersMapping.forEach(member => {
membersMapping.forEach((member) => {
if (member.wekanId) {
mappingById[member.id] = member.wekanId;
}
@@ -77,8 +64,7 @@ BlazeComponent.extendComponent({
additionalData.membersMapping = mappingById;
}
this.membersToMap.set([]);
Meteor.call(
'importBoard',
Meteor.call('importBoard',
this.importedData.get(),
additionalData,
this.importSource,
@@ -90,7 +76,7 @@ BlazeComponent.extendComponent({
Session.set('fromBoard', null);
Utils.goBoardId(res);
}
},
}
);
},
@@ -98,15 +84,12 @@ BlazeComponent.extendComponent({
const importSource = Session.get('importSource');
let membersToMap;
switch (importSource) {
case 'trello':
membersToMap = trelloGetMembersToMap(dataObject);
break;
case 'wekan':
membersToMap = wekanGetMembersToMap(dataObject);
break;
case 'csv':
membersToMap = csvGetMembersToMap(dataObject);
break;
case 'trello':
membersToMap = trelloMembersMapper.getMembersToMap(dataObject);
break;
case 'wekan':
membersToMap = wekanMembersMapper.getMembersToMap(dataObject);
break;
}
return membersToMap;
},
@@ -125,66 +108,22 @@ BlazeComponent.extendComponent({
return `import-board-instruction-${Session.get('importSource')}`;
},
importPlaceHolder() {
const importSource = Session.get('importSource');
if (importSource === 'csv') {
return 'import-csv-placeholder';
} else {
return 'import-json-placeholder';
}
},
events() {
return [
{
submit(evt) {
return this.parentComponent().importData(
evt,
Session.get('importSource'),
);
},
return [{
submit(evt) {
return this.parentComponent().importData(evt);
},
];
}];
},
}).register('importTextarea');
BlazeComponent.extendComponent({
onCreated() {
this.usersLoaded = new ReactiveVar(false);
this.autorun(() => {
const handle = this.subscribe(
'user-miniprofile',
this.members().map(member => {
return member.username;
}),
);
Tracker.nonreactive(() => {
Tracker.autorun(() => {
if (
handle.ready() &&
!this.usersLoaded.get() &&
this.members().length
) {
this._refreshMembers(
this.members().map(member => {
if (!member.wekanId) {
let user = Users.findOne({ username: member.username });
if (!user) {
user = Users.findOne({ importUsernames: member.username });
}
if (user) {
// eslint-disable-next-line no-console
// console.log('found username:', user.username);
member.wekanId = user._id;
}
}
return member;
}),
);
}
this.usersLoaded.set(handle.ready());
});
this.parentComponent().membersToMap.get().forEach(({ wekanId }) => {
if (wekanId) {
this.subscribe('user-miniprofile', wekanId);
}
});
});
},
@@ -210,23 +149,23 @@ BlazeComponent.extendComponent({
_setPropertyForMember(property, value, memberId, unset = false) {
const listOfMembers = this.members();
let finder = null;
if (memberId) {
finder = member => member.id === memberId;
if(memberId) {
finder = (member) => member.id === memberId;
} else {
finder = member => member.selected;
finder = (member) => member.selected;
}
listOfMembers.forEach(member => {
if (finder(member)) {
if (value !== null) {
listOfMembers.forEach((member) => {
if(finder(member)) {
if(value !== null) {
member[property] = value;
} else {
delete member[property];
}
if (!unset) {
if(!unset) {
// we shortcut if we don't care about unsetting the others
return false;
}
} else if (unset) {
} else if(unset) {
delete member[property];
}
return true;
@@ -247,9 +186,9 @@ BlazeComponent.extendComponent({
const allMembers = this.members();
let finder = null;
if (memberId) {
finder = user => user.id === memberId;
finder = (user) => user.id === memberId;
} else {
finder = user => user.selected;
finder = (user) => user.selected;
}
return allMembers.find(finder);
},
@@ -258,7 +197,7 @@ BlazeComponent.extendComponent({
return this._setPropertyForMember('wekanId', wekanId, null);
},
unmapMember(memberId) {
unmapMember(memberId){
return this._setPropertyForMember('wekanId', null, memberId);
},
@@ -267,22 +206,22 @@ BlazeComponent.extendComponent({
this.parentComponent().nextStep();
},
onMapMember(evt) {
const memberToMap = this.currentData();
if(memberToMap.wekan) {
// todo xxx ask for confirmation?
this.unmapMember(memberToMap.id);
} else {
this.setSelectedMember(memberToMap.id);
Popup.open('importMapMembersAdd')(evt);
}
},
events() {
return [
{
submit: this.onSubmit,
'click .js-select-member'(evt) {
const memberToMap = this.currentData();
if (memberToMap.wekan) {
// todo xxx ask for confirmation?
this.unmapMember(memberToMap.id);
} else {
this.setSelectedMember(memberToMap.id);
Popup.open('importMapMembersAdd')(evt);
}
},
},
];
return [{
'submit': this.onSubmit,
'click .js-select-member': this.onMapMember,
}];
},
}).register('importMapMembers');
@@ -291,16 +230,14 @@ BlazeComponent.extendComponent({
this.find('.js-map-member input').focus();
},
onSelectUser() {
onSelectUser(){
Popup.getOpenerComponent().mapSelectedMember(this.currentData()._id);
Popup.back();
},
events() {
return [
{
'click .js-select-import': this.onSelectUser,
},
];
return [{
'click .js-select-import': this.onSelectUser,
}];
},
}).register('importMapMembersAddPopup');
-4
View File
@@ -47,7 +47,3 @@
a.show-mapping
text-decoration underline
.import-members-map-note
font-size: 90%
font-weight: bold
@@ -1,10 +1,10 @@
export function trelloGetMembersToMap(data) {
export function getMembersToMap(data) {
// we will work on the list itself (an ordered array of objects) when a
// mapping is done, we add a 'wekan' field to the object representing the
// imported member
const membersToMap = data.members;
// auto-map based on username
membersToMap.forEach(importedMember => {
membersToMap.forEach((importedMember) => {
const wekanUser = Users.findOne({ username: importedMember.username });
if (wekanUser) {
importedMember.wekanId = wekanUser._id;
@@ -1,14 +1,14 @@
export function wekanGetMembersToMap(data) {
export function getMembersToMap(data) {
// we will work on the list itself (an ordered array of objects) when a
// mapping is done, we add a 'wekan' field to the object representing the
// imported member
const membersToMap = data.members;
const users = data.users;
// auto-map based on username
membersToMap.forEach(importedMember => {
membersToMap.forEach((importedMember) => {
importedMember.id = importedMember.userId;
delete importedMember.userId;
const user = users.filter(user => {
const user = users.filter((user) => {
return user._id === importedMember.id;
})[0];
if (user.profile && user.profile.fullname) {
+25 -83
View File
@@ -1,4 +1,4 @@
const { calculateIndex } = Utils;
const { calculateIndex, enableClickOnTouch } = Utils;
BlazeComponent.extendComponent({
// Proxy
@@ -21,16 +21,18 @@ BlazeComponent.extendComponent({
const boardComponent = this.parentComponent().parentComponent();
function userIsMember() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly()
);
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
}
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
const $cards = this.$('.js-minicards');
if(window.matchMedia('(max-width: 1199px)').matches) {
$( '.js-minicards' ).sortable({
handle: '.handle',
});
}
$cards.sortable({
connectWith: '.js-minicards:not(.js-list-full)',
tolerance: 'pointer',
@@ -40,16 +42,10 @@ BlazeComponent.extendComponent({
if (MultiSelection.isActive()) {
const andNOthers = $cards.find('.js-minicard.is-checked').length - 1;
if (andNOthers > 0) {
helper.append(
$(
Blaze.toHTML(
HTML.DIV(
{ class: 'and-n-other' },
TAPi18n.__('and-n-other-card', { count: andNOthers }),
),
),
),
);
helper.append($(Blaze.toHTML(HTML.DIV(
{ 'class': 'and-n-other' },
TAPi18n.__('and-n-other-card', { count: andNOthers })
))));
}
}
return helper;
@@ -72,16 +68,12 @@ BlazeComponent.extendComponent({
const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards);
const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const defaultSwimlaneId = currentBoard.getDefaultSwimline()._id;
let targetSwimlaneId = null;
// only set a new swimelane ID if the swimlanes view is active
if (
Utils.boardView() === 'board-view-swimlanes' ||
currentBoard.isTemplatesBoard()
)
targetSwimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))
._id;
let swimlaneId = '';
const boardView = (Meteor.user().profile || {}).boardView;
if (boardView === 'board-view-swimlanes' || currentBoard.isTemplatesBoard())
swimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))._id;
else if ((boardView === 'board-view-lists') || (boardView === 'board-view-cal') || !boardView)
swimlaneId = currentBoard.getDefaultSwimline()._id;
// Normally the jquery-ui sortable library moves the dragged DOM element
// to its new position, which disrupts Blaze reactive updates mechanism
@@ -94,60 +86,23 @@ BlazeComponent.extendComponent({
if (MultiSelection.isActive()) {
Cards.find(MultiSelection.getMongoSelector()).forEach((card, i) => {
const newSwimlaneId = targetSwimlaneId
? targetSwimlaneId
: card.swimlaneId || defaultSwimlaneId;
card.move(
currentBoard._id,
newSwimlaneId,
listId,
sortIndex.base + i * sortIndex.increment,
);
card.move(currentBoard._id, swimlaneId, listId, sortIndex.base + i * sortIndex.increment);
});
} else {
const cardDomElement = ui.item.get(0);
const card = Blaze.getData(cardDomElement);
const newSwimlaneId = targetSwimlaneId
? targetSwimlaneId
: card.swimlaneId || defaultSwimlaneId;
card.move(currentBoard._id, newSwimlaneId, listId, sortIndex.base);
card.move(currentBoard._id, swimlaneId, listId, sortIndex.base);
}
boardComponent.setIsDragging(false);
},
});
// ugly touch event hotfix
enableClickOnTouch(itemsSelector);
// Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => {
let showDesktopDragHandles = false;
currentUser = Meteor.user();
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
} else if (window.localStorage.getItem('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('uiSortable') || $cards.data('sortable')) {
$cards.sortable(
'option',
'disabled',
// Disable drag-dropping when user is not member
!userIsMember(),
// Not disable drag-dropping while in multi-selection mode
// MultiSelection.isActive() || !userIsMember(),
);
}
$cards.sortable('option', 'disabled', !userIsMember());
});
// We want to re-run this function any time a card is added.
@@ -178,19 +133,6 @@ BlazeComponent.extendComponent({
},
}).register('list');
Template.list.helpers({
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
});
Template.miniList.events({
'click .js-select-list'() {
const listId = this._id;
+12 -27
View File
@@ -43,6 +43,9 @@
background: white
margin: -3px 0 8px
.list-header-card-count
height: 35px
.list-header-add
flex: 0 0 auto
padding: 20px 12px 4px
@@ -57,10 +60,6 @@
background-color: #e4e4e4;
border-bottom: 6px solid #e4e4e4;
&.list-header-card-count
min-height: 35px
height: auto
&.ui-sortable-handle
cursor: grab
@@ -84,16 +83,17 @@
padding-left: 10px
color: #a6a6a6
.list-header-menu
position: absolute
padding: 27px 19px
margin-top: 1px
top: -7px
right: 3px
right: -7px
.list-header-plus-icon
color: #a6a6a6
margin-right: 15px
margin-right: 10px
.highlight
color: #ce1414
@@ -120,6 +120,9 @@
form
margin-bottom: 9px
.ps-scrollbar-y-rail
transform: translateX(2px)
.open-minicard-composer
border-radius: 2px
color: #8c8c8c
@@ -161,16 +164,7 @@
@media screen and (max-width: 800px)
.list-header-menu
position: absolute
padding: 27px 19px
margin-top: 1px
top: -7px
margin-right: 7px
right: -3px
.list-header
.list-header-name
margin-left: 1.4rem
margin-right: 30px
.mini-list
flex: 0 0 60px
@@ -180,8 +174,7 @@
border-bottom: 1px solid darken(white, 20%)
.list
display: contents
flex-basis: auto
display: block
width: 100%
border-left: 0px
&:first-child
@@ -227,17 +220,9 @@
padding: 7px
top: 50%
transform: translateY(-50%)
right: 47px
right: 17px
font-size: 20px
.list-header-handle
position: absolute
padding: 7px
top: 50%
transform: translateY(-50%)
right: 10px
font-size: 24px
.link-board-wrapper
display: flex
align-items: baseline
+12 -9
View File
@@ -1,11 +1,11 @@
template(name="listBody")
.list-body
.list-body.js-perfect-scrollbar
.minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}")
if cards.count
+inlinedForm(autoclose=false position="top")
+addCardForm(listId=_id position="top")
each (cardsWithLimit (idOrNull ../../_id))
a.minicard-wrapper.js-minicard(href=originRelativeUrl
a.minicard-wrapper.js-minicard(href=absoluteUrl
class="{{#if cardIsSelected}}is-selected{{/if}}"
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
if MultiSelection.isActive
@@ -19,14 +19,19 @@ template(name="listBody")
+inlinedForm(autoclose=false position="bottom")
+addCardForm(listId=_id position="bottom")
else
a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}")
a.open-minicard-composer.js-card-composer.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-card'}}
template(name="spinnerList")
.sk-spinner.sk-spinner-list(
class="{{currentBoard.colorClass}} {{getSkSpinnerName}}"
.sk-spinner.sk-spinner-wave.sk-spinner-list(
class=currentBoard.colorClass
id="showMoreResults")
+spinnerRaw
.sk-rect1
.sk-rect2
.sk-rect3
.sk-rect4
.sk-rect5
template(name="addCardForm")
.minicard.minicard-composer.js-composer
@@ -100,10 +105,8 @@ template(name="searchElementPopup")
each boards
option(value="{{_id}}") {{title}}
form.js-search-term-form
label
| {{_ 'template'}}
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto")
.list-body.search-card-results
.list-body.js-perfect-scrollbar.search-card-results
.minicards.clearfix.js-minicards
if isBoardTemplateSearch
each results
+263 -397
View File
@@ -1,5 +1,3 @@
import { Spinner } from '/client/lib/spinner';
const subManager = new SubsManager();
const InfiniteScrollIter = 10;
@@ -10,7 +8,7 @@ BlazeComponent.extendComponent({
},
mixins() {
return [];
return [Mixins.PerfectScrollbar];
},
openForm(options) {
@@ -18,7 +16,7 @@ BlazeComponent.extendComponent({
options.position = options.position || 'top';
const forms = this.childComponents('inlinedForm');
let form = forms.find(component => {
let form = forms.find((component) => {
return component.data().position === options.position;
});
if (!form && forms.length > 0) {
@@ -50,15 +48,15 @@ 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()) {
swimlaneId = this.parentComponent()
.parentComponent()
.data()._id; // Always swimlanes view
swimlaneId = this.parentComponent().parentComponent().data()._id; // Always swimlanes view
const swimlane = Swimlanes.findOne(swimlaneId);
// If this is the card templates swimlane, insert a card template
if (swimlane.isCardTemplatesSwimlane()) cardType = 'template-card';
if (swimlane.isCardTemplatesSwimlane())
cardType = 'template-card';
// If this is the board templates swimlane, insert a board template and a linked card
else if (swimlane.isBoardTemplatesSwimlane()) {
linkedId = Boards.insert({
@@ -72,15 +70,9 @@ BlazeComponent.extendComponent({
});
cardType = 'cardType-linkedBoard';
}
} else if (Utils.boardView() === 'board-view-swimlanes')
swimlaneId = this.parentComponent()
.parentComponent()
.data()._id;
else if (
Utils.boardView() === 'board-view-lists' ||
Utils.boardView() === 'board-view-cal' ||
!Utils.boardView()
)
} else if (boardView === 'board-view-swimlanes')
swimlaneId = this.parentComponent().parentComponent().data()._id;
else if ((boardView === 'board-view-lists') || (boardView === 'board-view-cal') || !boardView)
swimlaneId = board.getDefaultSwimline()._id;
const _id = Cards.insert({
@@ -99,9 +91,7 @@ BlazeComponent.extendComponent({
// if the displayed card count is less than the total cards in the list,
// we need to increment the displayed card count to prevent the spinner
// to appear
const cardCount = this.data()
.cards(this.idOrNull(swimlaneId))
.count();
const cardCount = this.data().cards(this.idOrNull(swimlaneId)).count();
if (this.cardlimit.get() < cardCount) {
this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter);
}
@@ -118,6 +108,8 @@ BlazeComponent.extendComponent({
if (position === 'bottom') {
this.scrollToBottom();
}
formComponent.reset();
}
},
@@ -156,28 +148,23 @@ BlazeComponent.extendComponent({
},
idOrNull(swimlaneId) {
if (
Utils.boardView() === 'board-view-swimlanes' ||
this.data()
.board()
.isTemplatesBoard()
)
const currentUser = Meteor.user();
if ((currentUser.profile || {}).boardView === 'board-view-swimlanes' ||
this.data().board().isTemplatesBoard())
return swimlaneId;
return undefined;
},
cardsWithLimit(swimlaneId) {
const limit = this.cardlimit.get();
const defaultSort = { sort: 1 };
const sortBy = Session.get('sortBy') ? Session.get('sortBy') : defaultSort;
const selector = {
listId: this.currentData()._id,
archived: false,
};
if (swimlaneId) selector.swimlaneId = swimlaneId;
if (swimlaneId)
selector.swimlaneId = swimlaneId;
return Cards.find(Filter.mongoSelector(selector), {
// sort: ['sort'],
sort: sortBy,
sort: ['sort'],
limit,
});
},
@@ -188,33 +175,21 @@ BlazeComponent.extendComponent({
},
canSeeAddCard() {
return (
!this.reachedWipLimit() &&
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
return !this.reachedWipLimit() && Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
reachedWipLimit() {
const list = Template.currentData();
return (
!list.getWipLimit('soft') &&
list.getWipLimit('enabled') &&
list.getWipLimit('value') <= list.cards().count()
);
return !list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') <= list.cards().count();
},
events() {
return [
{
'click .js-minicard': this.clickOnMiniCard,
'click .js-toggle-multi-selection': this.toggleMultiSelection,
'click .open-minicard-composer': this.scrollToBottom,
submit: this.addCard,
},
];
return [{
'click .js-minicard': this.clickOnMiniCard,
'click .js-toggle-multi-selection': this.toggleMultiSelection,
'click .open-minicard-composer': this.scrollToBottom,
submit: this.addCard,
}];
},
}).register('listBody');
@@ -237,15 +212,10 @@ BlazeComponent.extendComponent({
const currentBoardId = Session.get('currentBoard');
arr = [];
_.forEach(
Boards.findOne(currentBoardId)
.customFields()
.fetch(),
function(field) {
if (field.automaticallyOnCard || field.alwaysOnCard)
arr.push({ _id: field._id, value: null });
},
);
_.forEach(Boards.findOne(currentBoardId).customFields().fetch(), function(field){
if(field.automaticallyOnCard)
arr.push({_id: field._id, value: null});
});
this.customFields.set(arr);
},
@@ -257,7 +227,7 @@ BlazeComponent.extendComponent({
getLabels() {
const currentBoardId = Session.get('currentBoard');
return Boards.findOne(currentBoardId).labels.filter(label => {
return Boards.findOne(currentBoardId).labels.filter((label) => {
return this.labels.get().indexOf(label._id) > -1;
});
},
@@ -287,20 +257,18 @@ BlazeComponent.extendComponent({
}
BlazeComponent.getComponentForElement(nextList).openForm({
position: this.data().position,
position:this.data().position,
});
}
},
events() {
return [
{
keydown: this.pressKey,
'click .js-link': Popup.open('linkCard'),
'click .js-search': Popup.open('searchElement'),
'click .js-card-template': Popup.open('searchElement'),
},
];
return [{
keydown: this.pressKey,
'click .js-link': Popup.open('linkCard'),
'click .js-search': Popup.open('searchElement'),
'click .js-card-template': Popup.open('searchElement'),
}];
},
onRendered() {
@@ -309,75 +277,66 @@ BlazeComponent.extendComponent({
autosize($textarea);
$textarea.escapeableTextComplete(
[
// User mentions
{
match: /\B@([\w.]*)$/,
search(term, callback) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
callback(
$.map(currentBoard.activeMembers(), member => {
const user = Users.findOne(member.userId);
return user.username.indexOf(term) === 0 ? user : null;
}),
);
},
template(user) {
return user.username;
},
replace(user) {
toggleValueInReactiveArray(editor.members, user._id);
return '';
},
index: 1,
},
// Labels
{
match: /\B#(\w*)$/,
search(term, callback) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
callback(
$.map(currentBoard.labels, label => {
if (
label.name.indexOf(term) > -1 ||
label.color.indexOf(term) > -1
) {
return label;
}
return null;
}),
);
},
template(label) {
return Blaze.toHTMLWithData(Template.autocompleteLabelLine, {
hasNoName: !label.name,
colorName: label.color,
labelName: label.name || label.color,
});
},
replace(label) {
toggleValueInReactiveArray(editor.labels, label._id);
return '';
},
index: 1,
},
],
$textarea.escapeableTextComplete([
// User mentions
{
// When the autocomplete menu is shown we want both a press of both `Tab`
// or `Enter` to validation the auto-completion. We also need to stop the
// event propagation to prevent the card from submitting (on `Enter`) or
// going on the next column (on `Tab`).
onKeydown(evt, commands) {
if (evt.keyCode === 9 || evt.keyCode === 13) {
evt.stopPropagation();
return commands.KEY_ENTER;
}
return null;
match: /\B@([\w.]*)$/,
search(term, callback) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
callback($.map(currentBoard.activeMembers(), (member) => {
const user = Users.findOne(member.userId);
return user.username.indexOf(term) === 0 ? user : null;
}));
},
template(user) {
return user.username;
},
replace(user) {
toggleValueInReactiveArray(editor.members, user._id);
return '';
},
index: 1,
},
);
// Labels
{
match: /\B#(\w*)$/,
search(term, callback) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
callback($.map(currentBoard.labels, (label) => {
if (label.name.indexOf(term) > -1 ||
label.color.indexOf(term) > -1) {
return label;
}
return null;
}));
},
template(label) {
return Blaze.toHTMLWithData(Template.autocompleteLabelLine, {
hasNoName: !label.name,
colorName: label.color,
labelName: label.name || label.color,
});
},
replace(label) {
toggleValueInReactiveArray(editor.labels, label._id);
return '';
},
index: 1,
},
], {
// When the autocomplete menu is shown we want both a press of both `Tab`
// or `Enter` to validation the auto-completion. We also need to stop the
// event propagation to prevent the card from submitting (on `Enter`) or
// going on the next column (on `Tab`).
onKeydown(evt, commands) {
if (evt.keyCode === 9 || evt.keyCode === 13) {
evt.stopPropagation();
return commands.KEY_ENTER;
}
return null;
},
});
},
}).register('addCardForm');
@@ -395,28 +354,24 @@ BlazeComponent.extendComponent({
const list = $(Popup._getTopStack().openerElement).closest('.js-list');
this.listId = Blaze.getData(list[0])._id;
// Swimlane where to insert card
const swimlane = $(Popup._getTopStack().openerElement).closest(
'.js-swimlane',
);
const swimlane = $(Popup._getTopStack().openerElement).closest('.js-swimlane');
this.swimlaneId = '';
if (Utils.boardView() === 'board-view-swimlanes')
const boardView = (Meteor.user().profile || {}).boardView;
if (boardView === 'board-view-swimlanes')
this.swimlaneId = Blaze.getData(swimlane[0])._id;
else if (Utils.boardView() === 'board-view-lists' || !Utils.boardView)
this.swimlaneId = Swimlanes.findOne({ boardId: this.boardId })._id;
else if (boardView === 'board-view-lists' || !boardView)
this.swimlaneId = Swimlanes.findOne({boardId: this.boardId})._id;
},
boards() {
const boards = Boards.find(
{
archived: false,
'members.userId': Meteor.userId(),
_id: { $ne: Session.get('currentBoard') },
type: 'board',
},
{
sort: { sort: 1 /* boards default sorting */ },
},
);
const boards = Boards.find({
archived: false,
'members.userId': Meteor.userId(),
_id: {$ne: Session.get('currentBoard')},
type: 'board',
}, {
sort: ['title'],
});
return boards;
},
@@ -424,7 +379,7 @@ BlazeComponent.extendComponent({
if (!this.selectedBoardId.get()) {
return [];
}
const swimlanes = Swimlanes.find({ boardId: this.selectedBoardId.get() });
const swimlanes = Swimlanes.find({boardId: this.selectedBoardId.get()});
if (swimlanes.count())
this.selectedSwimlaneId.set(swimlanes.fetch()[0]._id);
return swimlanes;
@@ -434,8 +389,9 @@ BlazeComponent.extendComponent({
if (!this.selectedBoardId.get()) {
return [];
}
const lists = Lists.find({ boardId: this.selectedBoardId.get() });
if (lists.count()) this.selectedListId.set(lists.fetch()[0]._id);
const lists = Lists.find({boardId: this.selectedBoardId.get()});
if (lists.count())
this.selectedListId.set(lists.fetch()[0]._id);
return lists;
},
@@ -443,107 +399,87 @@ BlazeComponent.extendComponent({
if (!this.board) {
return [];
}
const ownCardsIds = this.board.cards().map(card => {
return card.linkedId || card._id;
});
const ownCardsIds = this.board.cards().map((card) => { return card.linkedId || card._id; });
return Cards.find({
boardId: this.selectedBoardId.get(),
swimlaneId: this.selectedSwimlaneId.get(),
listId: this.selectedListId.get(),
archived: false,
linkedId: { $nin: ownCardsIds },
_id: { $nin: ownCardsIds },
type: { $nin: ['template-card'] },
linkedId: {$nin: ownCardsIds},
_id: {$nin: ownCardsIds},
type: {$nin: ['template-card']},
});
},
events() {
return [
{
'change .js-select-boards'(evt) {
subManager.subscribe('board', $(evt.currentTarget).val(), false);
this.selectedBoardId.set($(evt.currentTarget).val());
},
'change .js-select-swimlanes'(evt) {
this.selectedSwimlaneId.set($(evt.currentTarget).val());
},
'change .js-select-lists'(evt) {
this.selectedListId.set($(evt.currentTarget).val());
},
'click .js-done'(evt) {
// LINK CARD
evt.stopPropagation();
evt.preventDefault();
const linkedId = $('.js-select-cards option:selected').val();
if (!linkedId) {
Popup.close();
return;
}
const _id = Cards.insert({
title: $('.js-select-cards option:selected').text(), //dummy
listId: this.listId,
swimlaneId: this.swimlaneId,
boardId: this.boardId,
sort: Lists.findOne(this.listId)
.cards()
.count(),
type: 'cardType-linkedCard',
linkedId,
});
Filter.addException(_id);
Popup.close();
},
'click .js-link-board'(evt) {
//LINK BOARD
evt.stopPropagation();
evt.preventDefault();
const impBoardId = $('.js-select-boards option:selected').val();
if (
!impBoardId ||
Cards.findOne({ linkedId: impBoardId, archived: false })
) {
Popup.close();
return;
}
const _id = Cards.insert({
title: $('.js-select-boards option:selected').text(), //dummy
listId: this.listId,
swimlaneId: this.swimlaneId,
boardId: this.boardId,
sort: Lists.findOne(this.listId)
.cards()
.count(),
type: 'cardType-linkedBoard',
linkedId: impBoardId,
});
Filter.addException(_id);
Popup.close();
},
return [{
'change .js-select-boards'(evt) {
subManager.subscribe('board', $(evt.currentTarget).val(), false);
this.selectedBoardId.set($(evt.currentTarget).val());
},
];
'change .js-select-swimlanes'(evt) {
this.selectedSwimlaneId.set($(evt.currentTarget).val());
},
'change .js-select-lists'(evt) {
this.selectedListId.set($(evt.currentTarget).val());
},
'click .js-done' (evt) {
// LINK CARD
evt.stopPropagation();
evt.preventDefault();
const linkedId = $('.js-select-cards option:selected').val();
if (!linkedId) {
Popup.close();
return;
}
const _id = Cards.insert({
title: $('.js-select-cards option:selected').text(), //dummy
listId: this.listId,
swimlaneId: this.swimlaneId,
boardId: this.boardId,
sort: Lists.findOne(this.listId).cards().count(),
type: 'cardType-linkedCard',
linkedId,
});
Filter.addException(_id);
Popup.close();
},
'click .js-link-board' (evt) {
//LINK BOARD
evt.stopPropagation();
evt.preventDefault();
const impBoardId = $('.js-select-boards option:selected').val();
if (!impBoardId || Cards.findOne({linkedId: impBoardId, archived: false})) {
Popup.close();
return;
}
const _id = Cards.insert({
title: $('.js-select-boards option:selected').text(), //dummy
listId: this.listId,
swimlaneId: this.swimlaneId,
boardId: this.boardId,
sort: Lists.findOne(this.listId).cards().count(),
type: 'cardType-linkedBoard',
linkedId: impBoardId,
});
Filter.addException(_id);
Popup.close();
},
}];
},
}).register('linkCardPopup');
BlazeComponent.extendComponent({
mixins() {
return [];
return [Mixins.PerfectScrollbar];
},
onCreated() {
this.isCardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass(
'js-card-template',
);
this.isListTemplateSearch = $(Popup._getTopStack().openerElement).hasClass(
'js-list-template',
);
this.isSwimlaneTemplateSearch = $(
Popup._getTopStack().openerElement,
).hasClass('js-open-add-swimlane-menu');
this.isBoardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass(
'js-add-board',
);
this.isTemplateSearch =
this.isCardTemplateSearch ||
this.isCardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-card-template');
this.isListTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-list-template');
this.isSwimlaneTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-open-add-swimlane-menu');
this.isBoardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass('js-add-board');
this.isTemplateSearch = this.isCardTemplateSearch ||
this.isListTemplateSearch ||
this.isSwimlaneTemplateSearch ||
this.isBoardTemplateSearch;
@@ -552,15 +488,10 @@ BlazeComponent.extendComponent({
board = Boards.findOne((Meteor.user().profile || {}).templatesBoardId);
} else {
// Prefetch first non-current board id
board = Boards.find({
board = Boards.findOne({
archived: false,
'members.userId': Meteor.userId(),
_id: {
$nin: [
Session.get('currentBoard'),
(Meteor.user().profile || {}).templatesBoardId,
],
},
_id: {$nin: [Session.get('currentBoard'), (Meteor.user().profile || {}).templatesBoardId]},
});
}
if (!board) {
@@ -578,12 +509,11 @@ BlazeComponent.extendComponent({
subManager.subscribe('board', this.boardId, false);
this.swimlaneId = '';
// Swimlane where to insert card
const swimlane = $(Popup._getTopStack().openerElement).parents(
'.js-swimlane',
);
if (Utils.boardView() === 'board-view-swimlanes')
const swimlane = $(Popup._getTopStack().openerElement).parents('.js-swimlane');
if ((Meteor.user().profile || {}).boardView === 'board-view-swimlanes')
this.swimlaneId = Blaze.getData(swimlane[0])._id;
else this.swimlaneId = Swimlanes.findOne({ boardId: this.boardId })._id;
else
this.swimlaneId = Swimlanes.findOne({boardId: this.boardId})._id;
// List where to insert card
const list = $(Popup._getTopStack().openerElement).closest('.js-list');
this.listId = Blaze.getData(list[0])._id;
@@ -592,17 +522,14 @@ BlazeComponent.extendComponent({
},
boards() {
const boards = Boards.find(
{
archived: false,
'members.userId': Meteor.userId(),
_id: { $ne: Session.get('currentBoard') },
type: 'board',
},
{
sort: { sort: 1 /* boards default sorting */ },
},
);
const boards = Boards.find({
archived: false,
'members.userId': Meteor.userId(),
_id: {$ne: Session.get('currentBoard')},
type: 'board',
}, {
sort: ['title'],
});
return boards;
},
@@ -619,7 +546,7 @@ BlazeComponent.extendComponent({
return board.searchSwimlanes(this.term.get());
} else if (this.isBoardTemplateSearch) {
const boards = board.searchBoards(this.term.get());
boards.forEach(board => {
boards.forEach((board) => {
subManager.subscribe('board', board.linkedId, false);
});
return boards;
@@ -629,170 +556,109 @@ BlazeComponent.extendComponent({
},
events() {
return [
{
'change .js-select-boards'(evt) {
subManager.subscribe('board', $(evt.currentTarget).val(), false);
this.selectedBoardId.set($(evt.currentTarget).val());
},
'submit .js-search-term-form'(evt) {
evt.preventDefault();
this.term.set(evt.target.searchTerm.value);
},
'click .js-minicard'(evt) {
// 0. Common
const title = $('.js-element-title')
.val()
.trim();
if (!title) return;
const element = Blaze.getData(evt.currentTarget);
element.title = title;
let _id = '';
if (!this.isTemplateSearch || this.isCardTemplateSearch) {
// Card insertion
// 1. Common
element.sort = Lists.findOne(this.listId)
.cards()
.count();
// 1.A From template
if (this.isTemplateSearch) {
element.type = 'cardType-card';
element.linkedId = '';
_id = element.copy(this.boardId, this.swimlaneId, this.listId);
// 1.B Linked card
} else {
_id = element.link(this.boardId, this.swimlaneId, this.listId);
}
Filter.addException(_id);
// List insertion
} else if (this.isListTemplateSearch) {
element.sort = Swimlanes.findOne(this.swimlaneId)
.lists()
.count();
element.type = 'list';
_id = element.copy(this.boardId, this.swimlaneId);
} else if (this.isSwimlaneTemplateSearch) {
element.sort = Boards.findOne(this.boardId)
.swimlanes()
.count();
element.type = 'swimlane';
_id = element.copy(this.boardId);
} else if (this.isBoardTemplateSearch) {
Meteor.call(
'copyBoard',
element.linkedId,
{
sort: Boards.find({ archived: false }).count(),
type: 'board',
title: element.title,
},
(err, data) => {
_id = data;
},
);
}
Popup.close();
},
return [{
'change .js-select-boards'(evt) {
subManager.subscribe('board', $(evt.currentTarget).val(), false);
this.selectedBoardId.set($(evt.currentTarget).val());
},
];
'submit .js-search-term-form'(evt) {
evt.preventDefault();
this.term.set(evt.target.searchTerm.value);
},
'click .js-minicard'(evt) {
// 0. Common
const title = $('.js-element-title').val().trim();
if (!title)
return;
const element = Blaze.getData(evt.currentTarget);
element.title = title;
let _id = '';
if (!this.isTemplateSearch || this.isCardTemplateSearch) {
// Card insertion
// 1. Common
element.sort = Lists.findOne(this.listId).cards().count();
// 1.A From template
if (this.isTemplateSearch) {
element.type = 'cardType-card';
element.linkedId = '';
_id = element.copy(this.boardId, this.swimlaneId, this.listId);
// 1.B Linked card
} else {
delete element._id;
element.type = 'cardType-linkedCard';
element.linkedId = element.linkedId || element._id;
_id = Cards.insert(element);
}
Filter.addException(_id);
// List insertion
} else if (this.isListTemplateSearch) {
element.sort = Swimlanes.findOne(this.swimlaneId).lists().count();
element.type = 'list';
_id = element.copy(this.boardId, this.swimlaneId);
} else if (this.isSwimlaneTemplateSearch) {
element.sort = Boards.findOne(this.boardId).swimlanes().count();
element.type = 'swimlalne';
_id = element.copy(this.boardId);
} else if (this.isBoardTemplateSearch) {
board = Boards.findOne(element.linkedId);
board.sort = Boards.find({archived: false}).count();
board.type = 'board';
board.title = element.title;
delete board.slug;
_id = board.copy();
}
Popup.close();
},
}];
},
}).register('searchElementPopup');
(class extends Spinner {
BlazeComponent.extendComponent({
onCreated() {
this.cardlimit = this.parentComponent().cardlimit;
this.listId = this.parentComponent().data()._id;
this.swimlaneId = '';
const isSandstorm =
Meteor.settings &&
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm) {
const user = Meteor.user();
if (user) {
if (Utils.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;
}
}
const boardView = (Meteor.user().profile || {}).boardView;
if (boardView === 'board-view-swimlanes')
this.swimlaneId = this.parentComponent().parentComponent().parentComponent().data()._id;
},
onRendered() {
this.spinner = this.find('.sk-spinner-list');
this.container = this.$(this.spinner).parents('.list-body')[0];
this.container = this.$(this.spinner).parents('.js-perfect-scrollbar')[0];
$(this.container).on(
`scroll.spinner_${this.swimlaneId}_${this.listId}`,
() => this.updateList(),
);
$(window).on(`resize.spinner_${this.swimlaneId}_${this.listId}`, () =>
this.updateList(),
);
$(this.container).on(`scroll.spinner_${this.swimlaneId}_${this.listId}`, () => this.updateList());
$(window).on(`resize.spinner_${this.swimlaneId}_${this.listId}`, () => this.updateList());
this.updateList();
}
},
onDestroyed() {
$(this.container).off(`scroll.spinner_${this.swimlaneId}_${this.listId}`);
$(window).off(`resize.spinner_${this.swimlaneId}_${this.listId}`);
}
checkIdleTime() {
return window.requestIdleCallback ||
function(handler) {
const startTime = Date.now();
return setTimeout(function() {
handler({
didTimeout: false,
timeRemaining() {
return Math.max(0, 50.0 - (Date.now() - startTime));
},
});
}, 1);
};
}
},
updateList() {
// Use fallback when requestIdleCallback is not available on iOS and Safari
// https://www.afasterweb.com/2017/11/20/utilizing-idle-moments/
if (this.spinnerInView()) {
this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter);
this.checkIdleTime(() => this.updateList());
window.requestIdleCallback(() => this.updateList());
}
}
},
spinnerInView() {
const parentViewHeight = this.container.clientHeight;
const bottomViewPosition = this.container.scrollTop + parentViewHeight;
const threshold = this.spinner.offsetTop;
// spinner deleted
if (!this.spinner.offsetTop) {
return false;
}
const parentViewHeight = this.container.clientHeight;
const bottomViewPosition = this.container.scrollTop + parentViewHeight;
return bottomViewPosition > threshold;
},
let spinnerOffsetTop = this.spinner.offsetTop;
const addCard = $(this.container).find("a.open-minicard-composer").first()[0];
if (addCard !== undefined) {
spinnerOffsetTop -= addCard.clientHeight;
}
return bottomViewPosition > spinnerOffsetTop;
}
getSkSpinnerName() {
return "sk-spinner-" + super.getSpinnerName().toLowerCase();
}
}.register('spinnerList'));
}).register('spinnerList');
+25 -56
View File
@@ -1,7 +1,7 @@
template(name="listHeader")
.list-header.js-list-header(
class="{{#if limitToShowCardsCount}}list-header-card-count{{/if}}"
class=colorClass)
class="{{#if colorClass}}list-header-{{colorClass}}{{/if}}")
+inlinedForm
+editListTitleForm
else
@@ -9,13 +9,12 @@ template(name="listHeader")
if currentList
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}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
+viewer
= title
if wipLimit.enabled
|&nbsp;(
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.count}}
span(class="{{#if reachedWipLimit}}highlight{{/if}}") {{cards.count}}
|/#{wipLimit.value})
if showCardsCountForList cards.count
@@ -28,24 +27,18 @@ template(name="listHeader")
div.list-header-menu
unless currentUser.isCommentOnly
if canSeeAddCard
a.js-add-card.fa.fa-plus.list-header-plus-icon(title="{{_ 'add-card-to-top-of-list'}}")
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
a.js-add-card.fa.fa-plus.list-header-plus-icon
a.fa.fa-navicon.js-open-list-menu
else
a.list-header-menu-icon.fa.fa-angle-right.js-select-list
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 canSeeAddCard
a.js-add-card.fa.fa-plus.list-header-plus-icon(title="{{_ 'add-card-to-top-of-list'}}")
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
if currentUser.isBoardAdmin
if showDesktopDragHandles
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
a.js-add-card.fa.fa-plus.list-header-plus-icon
a.fa.fa-navicon.js-open-list-menu
template(name="editListTitleForm")
.list-composer
@@ -56,47 +49,25 @@ template(name="editListTitleForm")
template(name="listActionPopup")
ul.pop-over-list
li
a.js-toggle-watch-list
if isWatching
i.fa.fa-eye
| {{_ 'unwatch'}}
else
i.fa.fa-eye-slash
| {{_ 'watch'}}
li: a.js-toggle-watch-list {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
unless currentUser.isCommentOnly
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
i.fa.fa-check-square
| {{_ 'list-select-cards'}}
if currentUser.isBoardAdmin
ul.pop-over-list
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
i.fa.fa-arrow-right
i.fa.fa-archive
| {{_ 'archive-list'}}
hr
ul.pop-over-list
li
a.js-more
i.fa.fa-link
| {{_ 'listMorePopup-title'}}
li: a.js-set-color-list {{_ 'set-color-list'}}
hr
ul.pop-over-list
if cards.count
li: a.js-select-cards {{_ 'list-select-cards'}}
hr
if currentUser.isBoardAdmin
ul.pop-over-list
li: a.js-set-wip-limit {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}}
hr
ul.pop-over-list
li: a.js-close-list {{_ 'archive-list'}}
hr
ul.pop-over-list
li: a.js-more {{_ 'listMorePopup-title'}}
template(name="boardLists")
ul.pop-over-list
@@ -116,9 +87,7 @@ template(name="listMorePopup")
input.inline-input(type="text" readonly value="{{ rootUrl }}")
| {{_ 'added'}}
span.date(title=list.createdAt) {{ moment createdAt 'LLL' }}
//unless currentUser.isWorker
// if currentUser.isBoardAdmin
// a.js-delete {{_ 'delete'}}
a.js-delete {{_ 'delete'}}
template(name="listDeletePopup")
p {{_ "list-delete-pop"}}
@@ -153,7 +122,7 @@ template(name="setListColorPopup")
form.edit-label
.palette-colors: each colors
// note: we use the swimlane palette to have more than just the border
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
span.card-label.palette-color.js-palette-color(class="swimlane-{{color}}")
if(isSelected color)
i.fa.fa-check
button.primary.confirm.js-submit {{_ 'save'}}
+58 -179
View File
@@ -6,33 +6,12 @@ Meteor.startup(() => {
BlazeComponent.extendComponent({
canSeeAddCard() {
const list = Template.currentData();
return (
(!list.getWipLimit('enabled') ||
list.getWipLimit('soft') ||
!this.reachedWipLimit()) &&
!Meteor.user().isWorker()
);
return !list.getWipLimit('enabled') || list.getWipLimit('soft') || !this.reachedWipLimit();
},
isBoardAdmin() {
return Meteor.user().isBoardAdmin();
},
starred(check = undefined) {
const list = Template.currentData();
const status = list.isStarred();
if (check === undefined) {
// just check
return status;
} else {
list.star(!status);
return !status;
}
},
editTitle(event) {
event.preventDefault();
const newTitle = this.childComponents('inlinedForm')[0]
.getValue()
.trim();
editTitle(evt) {
evt.preventDefault();
const newTitle = this.childComponents('inlinedForm')[0].getValue().trim();
const list = this.currentData();
if (newTitle) {
list.rename(newTitle.trim());
@@ -45,94 +24,48 @@ BlazeComponent.extendComponent({
},
limitToShowCardsCount() {
const currentUser = Meteor.user();
if (currentUser) {
return Meteor.user().getLimitToShowCardsCount();
} else {
return false;
}
return Meteor.user().getLimitToShowCardsCount();
},
cardsCount() {
const list = Template.currentData();
let swimlaneId = '';
if (Utils.boardView() === 'board-view-swimlanes')
swimlaneId = this.parentComponent()
.parentComponent()
.data()._id;
const boardView = (Meteor.user().profile || {}).boardView;
if (boardView === 'board-view-swimlanes')
swimlaneId = this.parentComponent().parentComponent().data()._id;
return list.cards(swimlaneId).count();
},
reachedWipLimit() {
const list = Template.currentData();
return (
list.getWipLimit('enabled') &&
list.getWipLimit('value') <= list.cards().count()
);
},
exceededWipLimit() {
const list = Template.currentData();
return (
list.getWipLimit('enabled') &&
list.getWipLimit('value') < list.cards().count()
);
return list.getWipLimit('enabled') && list.getWipLimit('value') <= list.cards().count();
},
showCardsCountForList(count) {
const limit = this.limitToShowCardsCount();
return limit >= 0 && count >= limit;
return limit > 0 && count > limit;
},
events() {
return [
{
'click .js-list-star'(event) {
event.preventDefault();
this.starred(!this.starred());
},
'click .js-open-list-menu': Popup.open('listAction'),
'click .js-add-card'(event) {
const listDom = $(event.target).parents(
`#js-list-${this.currentData()._id}`,
)[0];
const listComponent = BlazeComponent.getComponentForElement(listDom);
listComponent.openForm({
position: 'top',
});
},
'click .js-unselect-list'() {
Session.set('currentList', null);
},
submit: this.editTitle,
return [{
'click .js-open-list-menu': Popup.open('listAction'),
'click .js-add-card' (evt) {
const listDom = $(evt.target).parents(`#js-list-${this.currentData()._id}`)[0];
const listComponent = BlazeComponent.getComponentForElement(listDom);
listComponent.openForm({
position: 'top',
});
},
];
'click .js-unselect-list'() {
Session.set('currentList', null);
},
submit: this.editTitle,
}];
},
}).register('listHeader');
Template.listHeader.helpers({
isBoardAdmin() {
return Meteor.user().isBoardAdmin();
},
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
});
Template.listActionPopup.helpers({
isBoardAdmin() {
return Meteor.user().isBoardAdmin();
},
isWipLimitEnabled() {
return Template.currentData().getWipLimit('enabled');
},
@@ -143,22 +76,22 @@ Template.listActionPopup.helpers({
});
Template.listActionPopup.events({
'click .js-list-subscribe'() {},
'click .js-list-subscribe' () {},
'click .js-set-color-list': Popup.open('setListColor'),
'click .js-select-cards'() {
const cardIds = this.allCards().map(card => card._id);
'click .js-select-cards' () {
const cardIds = this.allCards().map((card) => card._id);
MultiSelection.add(cardIds);
Popup.close();
},
'click .js-toggle-watch-list'() {
'click .js-toggle-watch-list' () {
const currentList = this;
const level = currentList.findWatcher(Meteor.userId()) ? null : 'watching';
Meteor.call('watch', 'list', currentList._id, level, (err, ret) => {
if (!err && ret) Popup.close();
});
},
'click .js-close-list'(event) {
event.preventDefault();
'click .js-close-list' (evt) {
evt.preventDefault();
this.archive();
Popup.close();
},
@@ -169,17 +102,10 @@ Template.listActionPopup.events({
BlazeComponent.extendComponent({
applyWipLimit() {
const list = Template.currentData();
const limit = parseInt(
Template.instance()
.$('.wip-limit-value')
.val(),
10,
);
const limit = parseInt(Template.instance().$('.wip-limit-value').val(), 10);
if (limit < list.cards().count() && !list.getWipLimit('soft')) {
Template.instance()
.$('.wip-limit-error')
.click();
if(limit < list.cards().count() && !list.getWipLimit('soft')){
Template.instance().$('.wip-limit-error').click();
} else {
Meteor.call('applyWipLimit', list._id, limit);
Popup.back();
@@ -189,10 +115,7 @@ BlazeComponent.extendComponent({
enableSoftLimit() {
const list = Template.currentData();
if (
list.getWipLimit('soft') &&
list.getWipLimit('value') < list.cards().count()
) {
if(list.getWipLimit('soft') && list.getWipLimit('value') < list.cards().count()){
list.setWipLimit(list.cards().count());
}
Meteor.call('enableSoftLimit', Template.currentData()._id);
@@ -201,10 +124,7 @@ BlazeComponent.extendComponent({
enableWipLimit() {
const list = Template.currentData();
// Prevent user from using previously stored wipLimit.value if it is less than the current number of cards in the list
if (
!list.getWipLimit('enabled') &&
list.getWipLimit('value') < list.cards().count()
) {
if(!list.getWipLimit('enabled') && list.getWipLimit('value') < list.cards().count()){
list.setWipLimit(list.cards().count());
}
Meteor.call('enableWipLimit', list._id);
@@ -218,64 +138,29 @@ BlazeComponent.extendComponent({
return Template.currentData().getWipLimit('enabled');
},
wipLimitValue() {
wipLimitValue(){
return Template.currentData().getWipLimit('value');
},
events() {
return [
{
'click .js-enable-wip-limit': this.enableWipLimit,
'click .wip-limit-apply': this.applyWipLimit,
'click .wip-limit-error': Popup.open('wipLimitError'),
'click .materialCheckBox': this.enableSoftLimit,
},
];
return [{
'click .js-enable-wip-limit': this.enableWipLimit,
'click .wip-limit-apply': this.applyWipLimit,
'click .wip-limit-error': Popup.open('wipLimitError'),
'click .materialCheckBox': this.enableSoftLimit,
}];
},
}).register('setWipLimitPopup');
Template.listMorePopup.events({
'click .js-delete': Popup.afterConfirm('listDelete', function() {
'click .js-delete': Popup.afterConfirm('listDelete', function () {
Popup.close();
// TODO how can we avoid the fetch call?
const allCards = this.allCards().fetch();
const allCardIds = _.pluck(allCards, '_id');
// it's okay if the linked cards are on the same list
if (
Cards.find({
$and: [
{ listId: { $ne: this._id } },
{ linkedId: { $in: allCardIds } },
],
}).count() === 0
) {
allCardIds.map(_id => Cards.remove(_id));
Lists.remove(this._id);
} else {
// TODO: Figure out more informative message.
// Popup with a hint that the list cannot be deleted as there are
// linked cards. We can adapt the query above so we can list the linked
// cards.
// Related:
// client/components/cards/cardDetails.js about line 969
// https://github.com/wekan/wekan/issues/2785
const message = `${TAPi18n.__(
'delete-linked-cards-before-this-list',
)} linkedId: ${
this._id
} at client/components/lists/listHeader.js and https://github.com/wekan/wekan/issues/2785`;
alert(message);
}
this.allCards().map((card) => Cards.remove(card._id));
Lists.remove(this._id);
Utils.goBoardId(this.boardId);
}),
});
Template.listHeader.helpers({
isBoardAdmin() {
return Meteor.user().isBoardAdmin();
},
});
BlazeComponent.extendComponent({
onCreated() {
this.currentList = this.currentData();
@@ -283,32 +168,26 @@ BlazeComponent.extendComponent({
},
colors() {
return listsColors.map(color => ({ color, name: '' }));
return listsColors.map((color) => ({ color, name: '' }));
},
isSelected(color) {
if (this.currentColor.get() === null) {
return color === 'white';
} else {
return this.currentColor.get() === color;
}
return this.currentColor.get() === color;
},
events() {
return [
{
'click .js-palette-color'() {
this.currentColor.set(this.currentData().color);
},
'click .js-submit'() {
this.currentList.setColor(this.currentColor.get());
Popup.close();
},
'click .js-remove-color'() {
this.currentList.setColor(null);
Popup.close();
},
return [{
'click .js-palette-color'() {
this.currentColor.set(this.currentData().color);
},
];
'click .js-submit' () {
this.currentList.setColor(this.currentColor.get());
Popup.close();
},
'click .js-remove-color'() {
this.currentList.setColor(null);
Popup.close();
},
}];
},
}).register('setListColorPopup');
-17
View File
@@ -1,17 +0,0 @@
template(name="brokenCardsHeaderBar")
h1
| {{_ 'broken-cards'}}
template(name="brokenCards")
if currentUser
if searching.get
+spinner
else if hasResults.get
.global-search-results-list-wrapper
if hasQueryErrors.get
div
each msg in errorMessages
span.global-search-error-messages
= msg
else
+resultsPaged(this)
-18
View File
@@ -1,18 +0,0 @@
import { CardSearchPagedComponent } from '../../lib/cardSearch';
BlazeComponent.extendComponent({}).register('brokenCardsHeaderBar');
Template.brokenCards.helpers({
userId() {
return Meteor.userId();
},
});
class BrokenCardsComponent extends CardSearchPagedComponent {
onCreated() {
super.onCreated();
Meteor.subscribe('brokenCards', this.sessionId);
}
}
BrokenCardsComponent.register('brokenCards');
-31
View File
@@ -1,31 +0,0 @@
.broken-cards-card-wrapper
margin-top: 0
margin-bottom: 10px
border-width: 3px !important
border-color: grey !important
border-style: solid
border-radius: 5px
padding: 1.5rem
background-color: white
.broken-cards-wrapper
max-width: 500px
margin-right: auto
margin-left: auto
.broken-cards-card-title
font-weight: bold
//padding: 10px
.broken-cards-context
display: inline-block
.broken-cards-context-separator
font-weight: bold
.broken-cards-context-list
//margin-bottom: 0.7rem
.broken-cards-null
color: darkred
font-style: italic
-57
View File
@@ -1,57 +0,0 @@
template(name="dueCardsHeaderBar")
if currentUser
h1
i.fa.fa-calendar
| {{_ 'dueCards-title'}}
.board-header-btns.left
a.board-header-btn.js-due-cards-view-change(title="{{_ 'dueCardsViewChange-title'}}")
i.fa.fa-caret-down
if $eq dueCardsView 'me'
i.fa.fa-user
| {{_ 'dueCardsViewChange-choice-me'}}
if $eq dueCardsView 'all'
i.fa.fa-users
| {{_ 'dueCardsViewChange-choice-all'}}
template(name="dueCardsModalTitle")
if currentUser
h2
i.fa.fa-keyboard-o
| {{_ 'dueCards-title'}}
template(name="dueCards")
if currentUser
if searching.get
+spinner
else if hasResults.get
.global-search-results-list-wrapper
if hasQueryErrors.get
div
each msg in errorMessages
span.global-search-error-messages
= msg
else
+resultsPaged(this)
template(name="dueCardsViewChangePopup")
if currentUser
ul.pop-over-list
li
with "dueCardsViewChange-choice-me"
a.js-due-cards-view-me
i.fa.fa-user.colorful
| {{_ 'dueCardsViewChange-choice-me'}}
if $eq Utils.dueCardsView "me"
i.fa.fa-check
hr
li
with "dueCardsViewChange-choice-all"
a.js-due-cards-view-all
i.fa.fa-users.colorful
| {{_ 'dueCardsViewChange-choice-all'}}
span.sub-name
+viewer
| {{_ 'dueCardsViewChange-choice-all-description' }}
if $eq Utils.dueCardsView "all"
i.fa.fa-check
-111
View File
@@ -1,111 +0,0 @@
import { CardSearchPagedComponent } from '../../lib/cardSearch';
import {
OPERATOR_HAS,
OPERATOR_SORT,
OPERATOR_USER,
ORDER_ASCENDING,
PREDICATE_DUE_AT,
} from '../../../config/search-const';
import { QueryParams } from '../../../config/query-classes';
// const subManager = new SubsManager();
BlazeComponent.extendComponent({
dueCardsView() {
// eslint-disable-next-line no-console
// console.log('sort:', Utils.dueCardsView());
return Utils.dueCardsView();
},
events() {
return [
{
'click .js-due-cards-view-change': Popup.open('dueCardsViewChange'),
},
];
},
}).register('dueCardsHeaderBar');
Template.dueCards.helpers({
userId() {
return Meteor.userId();
},
});
BlazeComponent.extendComponent({
events() {
return [
{
'click .js-due-cards-view-me'() {
Utils.setDueCardsView('me');
Popup.close();
},
'click .js-due-cards-view-all'() {
Utils.setDueCardsView('all');
Popup.close();
},
},
];
},
}).register('dueCardsViewChangePopup');
class DueCardsComponent extends CardSearchPagedComponent {
onCreated() {
super.onCreated();
const queryParams = new QueryParams();
queryParams.addPredicate(OPERATOR_HAS, {
field: PREDICATE_DUE_AT,
exists: true,
});
// queryParams[OPERATOR_LIMIT] = 5;
queryParams.addPredicate(OPERATOR_SORT, {
name: PREDICATE_DUE_AT,
order: ORDER_ASCENDING,
});
if (Utils.dueCardsView() !== 'all') {
queryParams.addPredicate(OPERATOR_USER, Meteor.user().username);
}
this.runGlobalSearch(queryParams);
}
dueCardsView() {
// eslint-disable-next-line no-console
//console.log('sort:', Utils.dueCardsView());
return Utils.dueCardsView();
}
sortByBoard() {
return this.dueCardsView() === 'board';
}
dueCardsList() {
const results = this.getResults();
console.log('results:', results);
const cards = [];
if (results) {
results.forEach(card => {
cards.push(card);
});
}
cards.sort((a, b) => {
const x = a.dueAt === null ? new Date('2100-12-31') : a.dueAt;
const y = b.dueAt === null ? new Date('2100-12-31') : b.dueAt;
if (x > y) return 1;
else if (x < y) return -1;
return 0;
});
// eslint-disable-next-line no-console
console.log('cards:', cards);
return cards;
}
}
DueCardsComponent.register('dueCards');
Loaded 100 of 1006 files, more files were not shown because too many files have changed in this diff. Show more