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
1112 changed files with 62617 additions and 224218 deletions

No files matched your search

-12
View File
@@ -1,12 +0,0 @@
{
"presets": [
"@babel/preset-stage-3"
],
"env": {
"COVERAGE": {
"plugins": [
"istanbul"
]
}
}
}
-278
View File
@@ -1,278 +0,0 @@
FROM ubuntu:21.10
LABEL maintainer="sgr"
# 2022-04-25:
# - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling,
# so changing to 21.10. https://github.com/wekan/wekan/issues/4488
ENV BUILD_DEPS="gnupg gosu libarchive-tools wget curl bzip2 g++ build-essential python3 git ca-certificates iproute2"
ENV DEBIAN_FRONTEND=noninteractive
ENV \
DEBUG=false \
NODE_VERSION=v14.21.2 \
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 \
ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS=90 \
RICHER_CARD_COMMENT_EDITOR=false \
CARD_OPENED_WEBHOOK_ENABLED=false \
ATTACHMENTS_STORE_PATH="" \
ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM="" \
ATTACHMENTS_UPLOAD_MIME_TYPES="" \
ATTACHMENTS_UPLOAD_MAX_SIZE=0 \
AVATARS_UPLOAD_EXTERNAL_PROGRAM="" \
AVATARS_UPLOAD_MIME_TYPES="" \
AVATARS_UPLOAD_MAX_SIZE=0 \
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="" \
S3=""
# \
# NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------------------------------
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
# Add more Node heap:
# NODE_OPTIONS="--max_old_space_size=4096"
# Add more stack:
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
#---------------------------------------------------------------------
# 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 \
tests \
/home/wekan/app/tests/
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 && \
# 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 && \
cd node_modules/fibers && \
node build.js
USER root
# Cleanup
RUN \
set -o xtrace && \
apt-get clean -y && \
apt-get autoremove -y && \
rm -Rf /tmp/* && \
rm -Rf /home/wekan/app_build && \
rm -Rf /var/cache/apt /var/lib/apt/lists && \
rm -Rf /var/lib/apt/lists/*
USER wekan
ENV PORT=3000
EXPOSE $PORT
WORKDIR /home/wekan/app
#---------------------------------------------------------------------
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
# Add more Node heap:
# NODE_OPTIONS="--max_old_space_size=4096"
# Add more stack:
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
#---------------------------------------------------------------------
#TODO:
#CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /build/main.js"]
CMD ["/home/wekan/.meteor/meteor", "run", "--verbose", "--settings", "settings.json"]
-14
View File
@@ -1,14 +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 node_modules/fibers
node build.js
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"
]
}
-51
View File
@@ -1,51 +0,0 @@
version: '3.7'
services:
wekandb-dev:
image: mongo:6
container_name: wekan-dev-db
restart: unless-stopped
command: mongod --oplogSize 128
networks:
- wekan-dev-tier
expose:
- 27017
volumes:
- /etc/localtime:/etc/localtime:ro
- ./volumes/wekan-db:/data/db
- ./volumes/wekan-db-dump:/dump
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
- WRITABLE_PATH=/data
depends_on:
- wekandb-dev
volumes:
- /etc/localtime:/etc/localtime:ro
- ./volumes/data:/data
- ../client:/home/wekan/app/client
- ../models:/home/wekan/app/models
- ../config:/home/wekan/app/config
- ../imports:/home/wekan/app/imports
- ../server:/home/wekan/app/server
- ../public:/home/wekan/app/public
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 -39
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,
@@ -122,9 +99,8 @@
"Activities": true,
"Attachments": true,
"Boards": true,
"CardCommentReactions": true,
"CardComments": true,
"DatePicker": true,
"DatePicker" : true,
"Cards": true,
"CustomFields": true,
"Lists": true,
@@ -147,7 +123,6 @@
"allowIsBoardMemberByCard": true,
"allowIsBoardMemberCommentOnly": true,
"allowIsBoardMemberNoComments": true,
"allowIsBoardMemberWorker": true,
"Emoji": true,
"Checklists": true,
"Settings": true,
@@ -157,7 +132,6 @@
"Integrations": true,
"HTTP": true,
"AccountSettings": true,
"TableVisibilityModeSettings": true,
"Announcements": true,
"Swimlanes": true,
"ChecklistItems": 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: 14.21.2
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: 14.19.0
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
-183
View File
@@ -1,183 +0,0 @@
name: wekan
version: '6.21'
base: core20
summary: 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:
- build-on: amd64
run-on: amd64
- build-on: arm64
run-on: arm64
- build-on: ppc64el
run-on: ppc64el
- build-on: s390x
run-on: s390x
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, mount-observe, system-observe, bluetooth-control]
restart-condition: on-failure
mongodb:
command: mongodb-control
daemon: simple
plugs: [network, network-bind, mount-observe, system-observe, bluetooth-control]
restart-condition: on-failure
caddy:
command: caddy-control
daemon: simple
plugs: [network, network-bind]
help:
command: wekan-help
database-backup:
command: mongodb-backup
plugs: [network, network-bind, mount-observe, system-observe, bluetooth-control]
database-list-backups:
command: ls -al $SNAP_COMMON/db-backups/
database-restore:
command: mongodb-restore
plugs: [network, network-bind, mount-observe, system-observe, bluetooth-control]
parts:
mongodb:
plugin: dump
source:
- on amd64: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-amd64/mongodb-org-server_4.4.13_amd64.deb
- on arm64: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-arm64/mongodb-org-server_4.4.13_arm64.deb
- on ppc64el: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-ppc64el/mongodb-org-server_4.4.13_ppc64el.deb
- on s390x: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-s390x/mongodb-org-server_4.4.13_s390x.deb
stage-packages:
- libssl1.1
- libcurl3-dev
- libcurl4-openssl-dev
filesets:
mongo:
- usr
- bin
- lib
stage:
- $mongo
prime:
- $mongo
wekan:
#plugin: npm
plugin: dump
source:
# Fixed URLs to some allowed GitHub releases URL.
# Non-GitHub build server file urls are not allowed at 2022-03-02 and later.
- on amd64: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-amd64.zip
- on arm64: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-arm64.zip
- on ppc64el: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-ppc64el.zip
- on s390x: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-s390x.zip
# npm-node-version: 14.19.1
# node-packages:
# - node-gyp
# - node-pre-gyp
# - fibers
# build-packages:
# - npm
# - build-essential
# - ca-certificates
# - apt-utils
# - python
# - python3
# - g++
# - capnproto
# - curl
# - execstack
# - nodejs
# - npm
# - p7zip-full
# stage-packages:
# - libfontconfig1
override-build: |
cp -r bundle/* $SNAPCRAFT_PART_INSTALL/
cp bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
snapcraftctl build
organize:
README: README.wekan
prime:
- -lib/node_modules/node-pre-gyp/node_modules/tar/lib/.unpack.js.swp
- -lib/node_modules/weka*
helpers:
source: snap-src
plugin: dump
caddy:
plugin: dump
## Caddy v1 is not developed anymore. TODO: Sometime migrate to Caddy v2.
## https://caddy.community/t/caddyfile-v1-adapter/9129
## https://github.com/caddyserver/caddy/tree/v1
#source: https://caddyserver.com/download/linux/amd64?license=personal&telemetry=off
#source-type: tar
# Using last working binary that was downloaded from above URL to Wekan Snap,
# and .txt files from https://github.com/caddyserver/caddy/tree/v1/dist
source: https://wekan.github.io/caddy-v1-linux-amd64.7z
source-type: 7z
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
caddy2:
plugin: dump
source:
# Fixed URLs to some allowed GitHub releases URL.
# Non-GitHub build server file urls are not allowed at 2022-03-02 and later.
- on amd64: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-amd64.zip
- on arm64: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-arm64.zip
- on ppc64el: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-ppc64el.zip
- on s390x: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-s390x.zip
source-type: zip
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/']
+18 -26
View File
@@ -1,35 +1,27 @@
## Issue
<!--
**[PLEASE UPGRADE](https://github.com/wekan/wekan/wiki/Backup)** to the newest WeKan ® before reporting an issue.
Please search existing Open and Closed issues, most questions have already been answered.
If you can not login for any reason: https://github.com/wekan/wekan/wiki/Forgot-Password
Email settings, only SMTP MAIL_URL and MAIL_FROM are in use: https://github.com/wekan/wekan/wiki/Troubleshooting-Mail
Add these issues to elsewhere:
- Snap: https://github.com/wekan/wekan-snap/issues
The following types of issues should be reported separately:
- SECURITY ISSUES: https://github.com/wekan/wekan/blob/master/SECURITY.md
- UCS: https://github.com/wekan/univention/issues
-->
Other Wekan issues can be added here.
**Server Setup Information**:
### Server Setup Information
<!-- Please anonymize info, and do not any of your Wekan board URLs, passwords, API tokens etc to this public issue. -->
* Did you test in newest Wekan?:
* Did you configure root-url correctly so Wekan cards open correctly (see 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:
* Deployment Method (Snap/Docker/Sandstorm/bundle/source):
* Deployment Method(snap/docker/sandstorm/mongodb bundle/source):
* Http frontend if any (Caddy, Nginx, Apache, see config examples from Wekan GitHub wiki first):
* Node.js Version:
* Node Version:
* MongoDB Version:
* What webbrowser version are you using (Wekan should work on all modern browsers that support Javascript)?
* ROOT_URL environment variable http(s)://(subdomain).example.com(/suburl):
### Problem description
<!-- Add a recorded animated gif (e.g. with https://github.com/phw/peek) about how it works currently, and screenshot mockups how it should work. -->
#### Reproduction Steps
#### Logs
<!-- Check Right Click>Inspect>Console in you browser - generally Chrome shows more detailed info than Firefox. -->
<!-- Please anonymize logs.
Snap: sudo snap logs wekan.wekan
Docker: sudo docker logs wekan-app
If logs are very long, attach them in .zip file -->
**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` ?*
- *If using Docker, what does show command `sudo docker logs wekan-app` ?*
- *If logs are very long, attach them in .zip file*
-6
View File
@@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
-69
View File
@@ -1,69 +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'
permissions:
contents: read
jobs:
analyze:
permissions:
actions: read # for github/codeql-action/init to get workflow details
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/autobuild to send a status report
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@v3
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@v2
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@v2
# ℹ️ 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@v2
-14
View File
@@ -1,14 +0,0 @@
name: 'Dependency Review'
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
- name: 'Dependency Review'
uses: actions/dependency-review-action@v3
-63
View File
@@ -1,63 +0,0 @@
name: Docker
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
schedule:
- cron: '28 23 * * *'
push:
branches: [ master ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ master ]
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@57396166ad8aefe6098280995947635806a0e6ea
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
-20
View File
@@ -1,20 +0,0 @@
name: Docker Image CI
on:
push:
branches:
- master
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build the Docker image
run: docker build . --file Dockerfile --tag wekan:$(date +%s)
-30
View File
@@ -1,30 +0,0 @@
name: Release Charts
on:
push:
branches:
- master
permissions:
contents: read
jobs:
release:
permissions:
contents: write # for helm/chart-releaser-action to push chart release and create a release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.4.1
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-163
View File
@@ -1,163 +0,0 @@
name: Test suite
on:
push:
branches:
- master
pull_request:
permissions:
contents: read # to fetch code (actions/checkout)
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@v3
#
# - 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@v3
#
# - 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@v3
#
# - 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@v3
# CACHING
- name: Install Meteor
id: cache-meteor-install
uses: actions/cache@v3
with:
path: ~/.meteor
key: v1-meteor-${{ hashFiles('.meteor/versions') }}
restore-keys: |
v1-meteor-
- name: Cache NPM dependencies
id: cache-meteor-npm
uses: actions/cache@v3
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@v3
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-keys: |
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@v3
with:
name: coverage-folder
path: .coverage/
coverage:
name: Coverage report
runs-on: ubuntu-latest
needs: [tests]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Download coverage
uses: actions/download-artifact@v3
with:
name: coverage-folder
path: .coverage/
- name: Coverage Report
uses: VeryGoodOpenSource/very_good_coverage@v2.1.0
with:
path: ".coverage/lcov.info"
min_coverage: 1 # TODO add tests and increase to 95!
+2 -12
View File
@@ -1,14 +1,14 @@
*~
*.sw*
*.swp
.meteor-spk
*.sublime-workspace
tmp/
node_modules/
npm-debug.log
.gitmodules
.vscode/
.idea/
.build/*
package-lock.json
**/parts/
**/stage
**/prime
@@ -29,13 +29,3 @@ versions.json
.Trashes
Thumbs.db
ehthumbs.db
.eslintcache
.meteor/local
.devcontainer/docker-compose.extend.yml
.devcontainer/volumes*/
.coverage
# Helm chart
# Chart dependencies
/helm/wekan/**/*.tgz
/helm/wekan/charts
-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
+46 -96
View File
@@ -3,16 +3,17 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base@1.5.1
meteor-base@1.2.0
# Build system
ecmascript@0.16.4
standard-minifier-js@2.8.1
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
@@ -20,125 +21,74 @@ cfs:standard-packages
cottz:publish-relations
dburles:collection-helpers
idmontie:migrations
easy:search
mongo@1.16.3
matb33:collection-hooks
matteodem:easy-search
mongo@1.3.1
mquandalle:collection-mutations
# Account system
accounts-password@2.3.2
useraccounts:core
useraccounts:flow-routing
kenton:accounts-sandstorm
service-configuration@1.0.11
useraccounts:unstyled
simple:rest-accounts-password
useraccounts:flow-routing
wekan-ldap
wekan-accounts-cas
wekan-accounts-sandstorm
wekan-accounts-lockout
wekan-oidc
wekan-accounts-oidc
# Utilities
check@1.3.2
check@1.2.5
jquery@1.11.10
random@1.2.1
reactive-dict@1.3.1
session@1.2.1
tracker@1.2.1
underscore@1.0.11
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
http@2.0.0! # force new http package
tap:i18n
http@1.3.0
# UI components
blaze
ostrio:i18n
reactive-var@1.0.12
reactive-var@1.0.11
fortawesome:fontawesome
mousetrap:mousetrap
mquandalle:jquery-textcomplete
mquandalle:jquery-ui-drag-drop-sort
mquandalle:mousetrap-bindglobal
peerlibrary:blaze-components
peerlibrary:blaze-components@=0.15.1
templates:tabs
meteor-autosize
verron:autosize
simple:json-routes
rajit:bootstrap3-datepicker
shell-server@0.5.0
email@2.2.3
shell-server@0.3.0
simple:rest-accounts-password
useraccounts:core
email@1.2.3
horka:swipebox
dynamic-import@0.7.2
dynamic-import@0.2.0
staringatlights:fast-render
mixmax:smart-disconnect
accounts-password@1.5.0
cfs:gridfs
rzymek:fullcalendar
momentjs:moment@2.22.2
browser-policy-framing
mquandalle:moment
msavin:usercache
# Keep stylus in 1.1.0, because building v2 takes extra 52 minutes.
meteorhacks:subs-manager
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
cfs:filesystem
ostrio:cookies
ostrio:files@2.3.0
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
easylogic:summernote
pascoual:pdfkit
lmieulet:meteor-coverage
meteortesting:mocha@2.0.3
aldeed:simple-schema
matb33:collection-hooks
simple:json-routes
kadira:flow-router
spacebars
service-configuration@1.3.1
communitypackages:picker
minifier-css@1.6.2
+1 -1
View File
@@ -1 +1 @@
METEOR@2.9.0
METEOR@1.6.0.1
+130 -176
View File
@@ -1,112 +1,113 @@
accounts-base@2.2.6
accounts-oauth@1.4.2
accounts-password@2.3.3
3stack:presence@1.1.2
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
allow-deny@1.1.1
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.8.0
babel-compiler@7.10.1
babel-runtime@1.5.1
base64@1.0.12
binary-heap@1.0.11
blaze@2.6.1
blaze-tools@1.1.3
boilerplate-generator@1.7.1
caching-compiler@1.2.2
caching-html-compiler@1.2.1
callback-hook@1.4.0
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.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:filesystem@0.1.2
cfs:gridfs@0.0.34
cfs:http-methods@0.0.32
cfs:http-publish@0.0.13
cfs:power-queue@0.9.11
cfs:reactive-list@0.0.9
cfs:reactive-property@0.0.4
cfs:standard-packages@0.5.10
cfs:storage-adapter@0.2.4
cfs:tempstore@0.1.6
cfs: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.5
check@1.3.2
coffeescript@2.4.1
coffeescript-compiler@2.4.1
communitypackages:picker@1.1.0
cfs:worker@0.1.4
check@1.2.5
chuangbo:cookie@1.1.0
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.1
ddp-client@2.6.1
ddp-common@1.4.0
ddp-rate-limiter@1.1.1
ddp-server@2.6.0
ddp@1.4.0
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.2
dynamic-import@0.7.2
easy:search@2.2.1
easylogic:summernote@0.8.8
easysearch:components@2.2.2
easysearch:core@2.2.2
ecmascript@0.16.4
ecmascript-runtime@0.8.0
ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0
ejson@1.1.3
email@2.2.3
es5-shim@4.8.0
fetch@0.1.3
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
fortawesome:fontawesome@4.7.0
geojson-utils@1.0.11
geojson-utils@1.0.10
horka:swipebox@1.0.2
hot-code-push@1.0.4
html-tools@1.1.3
htmljs@1.1.1
http@2.0.0
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
konecty:mongo-counter@0.0.5_3
kenton:accounts-sandstorm@0.7.0
launch-screen@1.1.1
livedata@1.0.18
lmieulet:meteor-coverage@1.1.4
localstorage@1.2.0
logging@1.3.1
matb33:collection-hooks@1.1.4
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.10.4
meteor-autosize@5.0.1
meteor-base@1.5.1
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
meteortesting:browser-tests@1.3.5
meteortesting:mocha@2.0.3
meteortesting:mocha-core@8.0.1
minifier-css@1.6.2
minifier-js@2.7.5
meteorhacks:unblock@1.1.0
meteorspark:util@0.2.0
minifier-css@1.2.16
minifier-js@2.2.2
minifiers@1.1.8-faster-rebuild.0
minimongo@1.9.1
modern-browsers@0.1.9
modules@0.19.0
modules-runtime@0.13.1
momentjs:moment@2.29.3
mongo@1.16.3
mongo-decimal@0.1.3
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
@@ -114,121 +115,74 @@ mquandalle:collection-mutations@0.1.0
mquandalle:jade@0.4.9
mquandalle:jade-compiler@0.4.5
mquandalle:jquery-textcomplete@0.8.0_1
mquandalle:jquery-ui-drag-drop-sort@0.2.0
mquandalle:moment@1.0.1
mquandalle:mousetrap-bindglobal@0.0.1
msavin:usercache@1.8.0
npm-mongo@4.12.1
oauth@2.1.3
oauth2@1.3.2
observe-sequence@1.0.20
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
ostrio:cookies@2.7.2
ostrio:cstorage@4.0.1
ostrio:files@2.3.2
ostrio:i18n@3.2.0
pascoual:pdfkit@1.0.7
peerlibrary:assert@0.3.0
peerlibrary:base-component@0.17.1
peerlibrary:blaze-components@0.23.0
peerlibrary:computed-field@0.10.0
peerlibrary:data-lookup@0.3.0
peerlibrary:reactive-field@0.6.0
percolate:synced-cron@1.3.2
promise@0.12.2
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.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.1
rate-limit@1.0.9
react-fast-refresh@0.2.3
reactive-dict@1.3.1
reactive-var@1.0.12
reload@1.3.1
retry@1.1.0
routepolicy@1.1.1
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.1.11
retry@1.0.9
routepolicy@1.0.12
rzymek:fullcalendar@3.8.0
service-configuration@1.3.1
session@1.2.1
service-configuration@1.0.11
session@1.1.7
sha@1.0.9
shell-server@0.5.0
simple:authenticate-user-by-token@1.2.1
simple:json-routes@2.3.1
simple:rest-accounts-password@1.2.1
simple:rest-bearer-token-parser@1.1.1
simple:rest-json-error-handler@1.1.1
socket-stream-client@0.5.0
spacebars@1.3.0
spacebars-compiler@1.3.1
standard-minifier-js@2.8.1
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
softwarerero:accounts-t9n@1.3.11
spacebars@1.0.15
spacebars-compiler@1.1.3
srp@1.0.10
standard-minifier-css@1.3.5
standard-minifier-js@2.2.3
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.1
templating-compiler@1.4.1
templating-runtime@1.5.0
templating-tools@1.2.2
tracker@1.2.1
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.11
url@1.3.2
useraccounts:core@1.16.2
useraccounts:flow-routing@1.15.0
underscore@1.0.10
url@1.1.0
useraccounts:core@1.14.2
useraccounts:flow-routing@1.14.2
useraccounts:unstyled@1.14.2
webapp@1.13.2
webapp-hashing@1.1.1
wekan-accounts-cas@0.1.0
wekan-accounts-lockout@1.0.0
verron:autosize@3.0.8
webapp@1.4.0
webapp-hashing@1.0.9
wekan-accounts-oidc@1.0.10
wekan-accounts-sandstorm@0.8.0
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: 14.21.2
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
+2 -2
View File
@@ -39,10 +39,10 @@ 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_DZ:ar-DZ, ar_EG:ar-EG, az_AZ:az-AZ, az@latin:az-LA, bg_BG:bg, cs_CZ:cs-CZ, cy_GB:cy-GB, de_AT:de-AT, de_CH:de-CH, en_BR:en-BR, en_DE:en-DE, en_IT:en-IT, en_GB:en-GB, en_MY:en-MY, et_EE:et-EE, ast_ES:ast-ES, es_AR:es-AR, en@ysv:en-YS, 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-GR, fa_IR:fa-IR, ja-Hira:ja-HI, ja_JP:ja, fi_FI:fi, fr_FR:fr-FR, fr_CH:fr-CH, fy_NL:fy-NL, gl_ES:gl-ES, gu_IN:gu-IN, he_IL:he-IL, hi_IN:hi-IN, hu_HU:hu, id_ID:id, mn_MN:mn, ms_MY:ms-MY, nl_NL:nl-NL, ko_KR:ko-KR, lv_LV:lv, pl_PL:pl-PL, pt_BR:pt-BR, ro_RO:ro-RO, ru_UA:ru-UA, sl_SI:sl, uk_UA:uk-UA, uz@Arab:uz-AR, uz@Latn:uz-LA, uz_UZ:uz-UZ, vec:ve-CC, vep:ve-PP, vls:vl-SS, war:wa-RR, vi_VN:vi-VN, zh_CN:zh-CN, zh_TW:zh-TW, zh_Hans:zh-Hans, zh_HK:zh-HK, zu_ZA:zu-ZA
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 = imports/i18n/data/<lang>.i18n.json
file_filter = i18n/<lang>.i18n.json
source_lang = en
type = KEYVALUEJSON
-45
View File
@@ -1,45 +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
},
{
"type": "node",
"request": "launch",
"name": "Test: Node",
"runtimeExecutable": "meteor",
"runtimeArgs": [
"test",
"--inspect-brk=9229",
"--port=4040",
"--exclude-archs=web.browser.legacy,web.cordova",
"--driver-package=meteortesting:mocha",
"--settings=settings.json"
],
"outputCapture": "std",
"port": 9229,
"timeout": 60000
}
],
"compounds": [
{
"name": "Meteor: All",
"configurations": ["Meteor: Node", "Meteor: Chrome"]
}
]
}
+12 -6832
View File
File diff suppressed because it is too large. Load diff
+2 -33
View File
@@ -1,35 +1,4 @@
## Contributing to wiki
To get started, [please sign the Contributor License Agreement](https://www.clahub.com/agreements/wekan/wekan).
Please clone wiki:
```
git clone https://github.com/wekan/wekan.wiki
```
Edit .md files, and add changed files in .zip attachment
directly to comment of new issue at
https://github.com/wekan/wekan/issues
[Then, please read documentation at wiki](https://github.com/wekan/wekan/wiki).
## Contributing code
[Building WeKan and sending PR](https://github.com/wekan/wekan/wiki/Emoji).
Also see other docs at wiki.
Do not use code formattung or linting like eslist
or prettier.
WeKan code has MIT license.
## Contributing translations
Non-English translations are contributed only at
https://transifex.com/wekan/wekan
When adding new features, in your PR to
https://github.com/wekan/wekan/pulls
only add new English source language strings
to https://github.com/wekan/wekan/blob/master/imports/i18n/data/en.i18n.json
Maintainer of WeKan xet7 downloads all newest
translations from Transifex and adds
them to WeKan repo before making
new release.
+103 -110
View File
@@ -1,70 +1,35 @@
FROM --platform=linux/amd64 ubuntu:22.10 as wekan
FROM ubuntu:disco
LABEL maintainer="wekan"
# 2022-09-04:
# - above "--platform=linux/amd64 ubuntu:22.04 as wekan" is needed to build Dockerfile
# correctly on Mac M1 etc, to not get this error:
# https://stackoverflow.com/questions/71040681/qemu-x86-64-could-not-open-lib64-ld-linux-x86-64-so-2-no-such-file-or-direc
# 2022-04-25:
# - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling,
# so changing to 21.10. https://github.com/wekan/wekan/issues/4488
# 2021-09-18:
# - Above Ubuntu base image copied from Docker Hub ubuntu:hirsute-20210825
# 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=v14.21.2 \
METEOR_RELEASE=2.8.1 \
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 \
ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS=90 \
ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM="" \
ATTACHMENTS_UPLOAD_MIME_TYPES="" \
ATTACHMENTS_UPLOAD_MAX_SIZE=0 \
AVATARS_UPLOAD_EXTERNAL_PROGRAM="" \
AVATARS_UPLOAD_MIME_TYPES="" \
AVATARS_UPLOAD_MAX_SIZE=72000 \
RICHER_CARD_COMMENT_EDITOR=false \
CARD_OPENED_WEBHOOK_ENABLED=false \
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 \
METRICS_ALLOWED_IP_ADDRESSES="" \
BROWSER_POLICY_ENABLED=true \
TRUSTED_URL="" \
WEBHOOKS_ATTRIBUTES="" \
OAUTH2_ENABLED=false \
OIDC_REDIRECTION_ENABLED=false \
OAUTH2_CA_CERT="" \
OAUTH2_ADFS_ENABLED=false \
OAUTH2_LOGIN_STYLE=redirect \
OAUTH2_CLIENT_ID="" \
OAUTH2_SECRET="" \
@@ -81,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 \
@@ -95,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="" \
@@ -138,35 +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="" \
WRITABLE_PATH=/data \
S3=""
# NODE_OPTIONS="--max_old_space_size=4096" \
#---------------------------------------------
# == at docker-compose.yml: AUTOLOGIN WITH OIDC/OAUTH2 ====
# https://github.com/wekan/wekan/wiki/autologin
#- OIDC_REDIRECTION_ENABLED=true
#---------------------------------------------------------------------
DEFAULT_AUTHENTICATION_METHOD=""
# Copy the app to the image
COPY ${SRC_PATH} /home/wekan/app
@@ -178,6 +113,7 @@ RUN \
\
# OS dependencies
apt-get update -y && apt-get install -y --no-install-recommends ${BUILD_DEPS} && \
pip3 install -U pip setuptools wheel && \
\
# Meteor installer doesn't work with the default tar binary, so using bsdtar while installing.
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
@@ -188,9 +124,43 @@ RUN \
wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
#---------------------------------------------------------------------------------------------
# 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 that has fix included, => Official 8.12.0 has been released
# Description at https://releases.wekan.team/node.txt
#wget https://releases.wekan.team/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
#echo "1ed54adb8497ad8967075a0b5d03dd5d0a502be43d4a4d84e5af489c613d7795 node-v8.12.0-linux-x64.tar.gz" >> SHASUMS256.txt.asc && \
\
# Verify nodejs authenticity
grep ${NODE_VERSION}-${ARCHITECTURE}.tar.gz SHASUMS256.txt.asc | shasum -a 256 -c - && \
#export GNUPGHOME="$(mktemp -d)" && \
#\
# Try other key servers if ha.pool.sks-keyservers.net is unreachable
# Code from https://github.com/chorrell/docker-node/commit/2b673e17547c34f17f24553db02beefbac98d23c
# gpg keys listed at https://github.com/nodejs/node#release-team
# and keys listed here from previous version of this Dockerfile
#for key in \
#9554F04D7259F04124DE6B476D5A82AC7E37093B \
#94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
#FD3A5288F042B6850C66B31F09FE44734EB7990E \
#71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
#DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
#C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
#B9AE9905FFD7803F25714661B63B535A4C206CA9 \
#; do \
#gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key" || \
#gpg --keyserver pgp.mit.edu --recv-keys "$key" || \
#gpg --keyserver keyserver.pgp.com --recv-keys "$key" ; \
#done && \
#gpg --verify SHASUMS256.txt.asc && \
# Ignore socket files then delete files then delete directories
#find "$GNUPGHOME" -type f | xargs rm -f && \
#find "$GNUPGHOME" -type d | xargs rm -fR && \
rm -f SHASUMS256.txt.asc && \
\
# Install Node
@@ -199,42 +169,81 @@ 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/${NODE_VERSION} /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 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 && \
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 && \
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; \
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 && \
# REPOS BELOW ARE INCLUDED TO WEKAN REPO
#cd /home/wekan/app/packages && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git && \
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git && \
#gosu wekan:wekan git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git && \
#gosu wekan:wekan mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan-accounts-oidc && \
#gosu wekan:wekan mv meteor-accounts-oidc/packages/switch_oidc wekan-oidc && \
#gosu wekan:wekan rm -rf meteor-accounts-oidc && \
sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js && \
cd /home/wekan/.meteor && \
gosu wekan:wekan /home/wekan/.meteor/meteor -- help; \
\
# extract the OpenAPI specification
npm install -g api2html@0.3.3 && \
mkdir -p /home/wekan/python && \
chown wekan:wekan --recursive /home/wekan/python && \
cd /home/wekan/python && \
gosu wekan:wekan git clone --depth 1 -b master https://github.com/Kronuz/esprima-python && \
cd /home/wekan/python/esprima-python && \
python3 setup.py install --record files.txt && \
cd /home/wekan/app &&\
gosu wekan:wekan mkdir -p ./public/api && \
gosu wekan:wekan python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml && \
gosu wekan:wekan /opt/nodejs/bin/api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml; \
# Build app
cd /home/wekan/app && \
mkdir -p /home/wekan/.npm && \
chown wekan --recursive /home/wekan/.npm /home/wekan/.config /home/wekan/.meteor && \
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: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 && \
cd /home/wekan/app_build/bundle/programs/server/ && \
chmod u+w *.json && \
gosu wekan:wekan npm install && \
cd node_modules/fibers && \
node build.js && \
cd ../.. && \
# 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 && \
#gosu wekan:wekan npm install bcrypt && \
mv /home/wekan/app_build/bundle /build && \
\
# Put back the original tar
@@ -244,32 +253,16 @@ RUN \
apt-get remove --purge -y ${BUILD_DEPS} && \
apt-get autoremove -y && \
npm uninstall -g api2html &&\
rm -R /tmp/* && \
rm -R /var/lib/apt/lists/* && \
rm -R /home/wekan/.meteor && \
rm -R /home/wekan/app && \
rm -R /home/wekan/app_build && \
mkdir /data && \
chown wekan --recursive /data
#cat /home/wekan/python/esprima-python/files.txt | xargs rm -R && \
#rm -R /home/wekan/python
#rm /home/wekan/install_meteor.sh
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
USER wekan
STOPSIGNAL SIGKILL
#---------------------------------------------------------------------
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
# Add more Node heap:
# NODE_OPTIONS="--max_old_space_size=4096"
# Add more stack:
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
#---------------------------------------------------------------------
#
# CMD ["node", "/build/main.js"]
#CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /build/main.js"]
CMD ["bash", "-c", "ulimit -s 65500; exec node /build/main.js"]
CMD ["node", "/build/main.js"]
-98
View File
@@ -1,98 +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=v14.21.2 \
WEKAN_VERSION=latest \
WEKAN_ARCHITECTURE=arm64 \
NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------------------------------
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
# Add more Node heap:
# NODE_OPTIONS="--max_old_space_size=4096"
# Add more stack:
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
#---------------------------------------------------------------------
# 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=v14.21.2 \
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
#---------------------------------------------------------------------
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
# Add more Node heap:
# NODE_OPTIONS="--max_old_space_size=4096"
# Add more stack:
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
#---------------------------------------------------------------------
#
#CMD ["node", "/home/wekan/bundle/main.js"]
#CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /home/wekan/bundle/main.js"]
CMD ["bash", "-c", "ulimit -s 65500; exec node /home/wekan/bundle/main.js"]
+77 -60
View File
@@ -1,51 +1,42 @@
[Gitpod Ready-to-Code](https://gitpod.io/#https://github.com/wekan/wekan)
# Wekan - Open Source kanban
# WeKan ® - Open Source kanban
[![Contributors](https://img.shields.io/github/contributors/wekan/wekan.svg "Contributors")](https://github.com/wekan/wekan/graphs/contributors)
[![Docker Repository on Quay](https://quay.io/repository/wekan/wekan/status "Docker Repository on Quay")](https://quay.io/repository/wekan/wekan)
[![Docker Hub container status](https://img.shields.io/docker/build/wekanteam/wekan.svg "Docker Hub container status")](https://hub.docker.com/r/wekanteam/wekan)
[![Docker Hub pulls](https://img.shields.io/docker/pulls/wekanteam/wekan.svg "Docker Hub Pulls")](https://hub.docker.com/r/wekanteam/wekan)
[![Wekan Build Status][travis_badge]][travis_status]
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/02137ecec4e34c5aa303f57637196a93 "Codacy Badge")](https://www.codacy.com/app/xet7/wekan?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=wekan/wekan&amp;utm_campaign=Badge_Grade)
[![Code Climate](https://codeclimate.com/github/wekan/wekan/badges/gpa.svg "Code Climate")](https://codeclimate.com/github/wekan/wekan)
[![Project Dependencies](https://david-dm.org/wekan/wekan.svg "Project Dependencies")](https://david-dm.org/wekan/wekan)
[![Code analysis at Open Hub](https://img.shields.io/badge/code%20analysis-at%20Open%20Hub-brightgreen.svg "Code analysis at Open Hub")](https://www.openhub.net/p/wekan)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwekan%2Fwekan.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_shield)
## Docker Containers
- [Quay](https://quay.io/repository/wekan/wekan)
- [Docker Hub](https://hub.docker.com/r/wekanteam/wekan)
Other platforms and compatible software versions at https://wekan.github.io Download section.
## Code stats
- [Code Climate](https://codeclimate.com/github/wekan/wekan)
- [Open Hub](https://www.openhub.net/p/wekan)
- [OSS Insight](https://ossinsight.io/analyze/wekan/wekan)
- [CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4619)
## [Translate WeKan ® at Transifex](https://transifex.com/wekan/wekan)
## [Translate Wekan at Transifex](https://transifex.com/wekan/wekan)
Translations to non-English languages are accepted only at [Transifex](https://transifex.com/wekan/wekan) using webbrowser.
New English strings of new features can be added as PRs to master branch file wekan/imports/i18n/data/en.i18n.json .
New English strings of new features can be added as PRs to edge branch file wekan/i18n/en.i18n.json .
## [WeKan ® feature requests and bugs](https://github.com/wekan/wekan/issues)
## [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: Latest tag has newest release
You can use latest tag to get newest release tag.
See bottom of https://github.com/wekan/wekan/issues/3874
[Wekan IRC FAQ](https://github.com/wekan/wekan/wiki/IRC-FAQ)
## 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 ®
## About Wekan
WeKan ® is an completely [Open Source][open_source] and [Free software][free_software]
Wekan is an completely [Open Source][open_source] and [Free software][free_software]
collaborative kanban board application with MIT license.
Whether you’re maintaining a personal todo list, planning your holidays with some friends,
@@ -53,70 +44,96 @@ or working in a team on your next revolutionary idea, Kanban boards are an unbea
to keep your things organized. They give you a visual overview of the current state of your project,
and make you productive by allowing you to focus on the few items that matter the most.
Since WeKan ® is a free software, you don’t have to trust us with your data and can
Since Wekan is a free software, you don’t have to trust us with your data and can
install Wekan on your own computer or server. In fact we encourage you to do
that by providing one-click installation on various platforms.
- WeKan ® is used in [most countries of the world](https://snapcraft.io/wekan).
- WeKan ® largest user has 22k users using WeKan ® in their company.
- WeKan ® has been [translated](https://transifex.com/wekan/wekan) to about 105 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.
- Wekan is used in [most countries of the world](https://snapcraft.io/wekan).
- Wekan largest user has 13k users using Wekan in their company.
- Wekan has been [translated](https://transifex.com/wekan/wekan) to about 50 languages.
- [Features][features]: Wekan has real-time user interface.
- [Platforms][platforms]: Wekan supports many platforms.
Wekan is critical part of new platforms Wekan is currently being integrated to.
- [Integrations][integrations]: Current possible integrations and future plans.
## Requirements
- 64bit: Linux [Snap](https://github.com/wekan/wekan-snap/wiki/Install) or [Sandstorm](https://sandstorm.io) /
[Mac](https://github.com/wekan/wekan/wiki/Mac) / [Windows](https://github.com/wekan/wekan/wiki/Install-Wekan-from-source-on-Windows).
[More Platforms](https://github.com/wekan/wekan/wiki/Platforms), bundle for RasPi3 ARM and other CPUs where Node.js and MongoDB exists.
- 1 GB RAM minimum free for WeKan ®. Production server should have minimum total 4 GB RAM.
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/master/docker-compose.yml): 3 frontend servers,
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
[More Platforms](https://github.com/wekan/wekan/wiki/Platforms). [ARM progress](https://github.com/wekan/wekan/issues/1053#issuecomment-410919264).
- 1 GB RAM minimum free for Wekan. Production server should have minimum total 4 GB RAM.
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/devel/docker-compose.yml): 3 frontend servers,
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
- Enough disk space and alerts about low disk space. If you run out disk space, MongoDB database gets corrupted.
- SECURITY: Updating to newest WeKan ® version very often. Please check you do not have automatic updates of Sandstorm or Snap turned off.
Old versions have security issues because of old versions Node.js etc. Only newest WeKan ® is supported.
WeKan ® on Sandstorm is not usually affected by any Standalone WeKan ® (Snap/Docker/Source) security issues.
- SECURITY: Updating to newest Wekan version very often. Please check you do not have automatic updates of Sandstorm or Snap turned off.
Old versions have security issues because of old versions Node.js etc. Only newest Wekan is supported.
Wekan on Sandstorm is not usually affected by any Standalone Wekan (Snap/Docker/Source) security issues.
- [Reporting all new bugs immediately](https://github.com/wekan/wekan/issues).
New features and fixes are added to WeKan ® [many times a day](https://github.com/wekan/wekan/blob/master/CHANGELOG.md).
- [Backups](https://github.com/wekan/wekan/wiki/Backup) of WeKan ® database once a day miminum.
New features and fixes are added to Wekan [many times a day](https://github.com/wekan/wekan/blob/devel/CHANGELOG.md).
- [Backups](https://github.com/wekan/wekan/wiki/Backup) of Wekan database once a day miminum.
Bugs, updates, users deleting list or card, harddrive full, harddrive crash etc can eat your data. There is no undo yet.
Some bug can cause WeKan ® board to not load at all, requiring manual fixing of database content.
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/master/CHANGELOG.md).
- 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.
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
WeKan ® is released under the very permissive [MIT license](LICENSE), and made
Wekan is released under the very permissive [MIT license](LICENSE), and made
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
[wekan_wiki]: https://github.com/wekan/wekan/wiki
[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)
+7 -7
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?
@@ -51,8 +51,8 @@ This also means all Standalone Wekan functionality works in offline local networ
Wekan is used by companies that have [thousands of users](https://github.com/wekan/wekan/wiki/AWS) and at healthcare.
Wekan uses xss package for input fields like cards, as you can see from
[package.json](https://github.com/wekan/wekan/blob/master/package.json). Other used versions can be seen from
[Meteor versions file](https://github.com/wekan/wekan/blob/master/.meteor/versions).
[package.json](https://github.com/wekan/wekan/blob/devel/package.json). Other used versions can be seen from
[Meteor versions file](https://github.com/wekan/wekan/blob/devel/.meteor/versions).
Forms can include markdown links, html, image tags etc like you see at https://wekan.github.io .
It's possible to add attachments to cards, and markdown/html links to files.
@@ -69,7 +69,7 @@ access to outside of Wekan grain.
Standalone Wekan only has password auth currently, there is work in progress to add
[oauth2](https://github.com/wekan/wekan/pull/1578), [Openid](https://github.com/wekan/wekan/issues/538),
[LDAP](https://github.com/wekan/wekan/issues/119) etc. If you need more login security for Standalone Wekan now,
it's possible add additional [Google Auth proxybouncer](https://github.com/wekan/wekan/wiki/Let's-Encrypt-and-Google-Auth) in front of password auth, and then use Google Authenticator for Google Auth. Standalone Wekan does have [brute force protection with eluck:accounts-lockout and browser-policy clickjacking protection](https://github.com/wekan/wekan/blob/master/CHANGELOG.md#v080-2018-04-04-wekan-release). You can also optionally use some [WAF](https://en.wikipedia.org/wiki/Web_application_firewall)
it's possible add additional [Google Auth proxybouncer](https://github.com/wekan/wekan/wiki/Let's-Encrypt-and-Google-Auth) in front of password auth, and then use Google Authenticator for Google Auth. Standalone Wekan does have [brute force protection with eluck:accounts-lockout and browser-policy clickjacking protection](https://github.com/wekan/wekan/blob/devel/CHANGELOG.md#v080-2018-04-04-wekan-release). You can also optionally use some [WAF](https://en.wikipedia.org/wiki/Web_application_firewall)
like for example [AWS WAF](https://aws.amazon.com/waf/).
[All Wekan Platforms](https://github.com/wekan/wekan/wiki/Platforms)
@@ -106,7 +106,7 @@ a security issue, we'd like to know about it, and also how to fix it:
Typical already known or "no impact" bugs such as:
- Brute force password guessign. Currently there is
[brute force protection with eluck:accounts-lockout](https://github.com/wekan/wekan/blob/master/CHANGELOG.md#v080-2018-04-04-wekan-release).
[brute force protection with eluck:accounts-lockout](https://github.com/wekan/wekan/blob/devel/CHANGELOG.md#v080-2018-04-04-wekan-release).
- Security issues related to that Wekan uses Meteor 1.6.0.1 related packages, and upgrading to newer
Meteor 1.6.1 is complicated process that requires lots of changes to many dependency packages.
Upgrading [has been tried many times, spending a lot of time](https://github.com/meteor/meteor/issues/9609)
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v6.65.0"
appVersion: "v2.95.0"
files:
userUploads:
- README.md
-384
View File
@@ -1,384 +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
# TODO:
# addcustomfieldtoboard: There is error: Settings must be object. So adding does not work yet.
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 # All Public Boards")
print(" python3 api.py boards USERID # Boards of USERID")
print(" python3 api.py board BOARDID # Info of BOARDID")
print(" python3 api.py customfields BOARDID # Custom Fields of BOARDID")
print(" python3 api.py customfield BOARDID CUSTOMFIELDID # Info of CUSTOMFIELDID")
print(" python3 api.py addcustomfieldtoboard AUTHORID BOARDID NAME TYPE SETTINGS SHOWONCARD AUTOMATICALLYONCARD SHOWLABELONMINICARD SHOWSUMATTOPOFLIST # Add Custom Field to Board")
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")
print(" python3 api.py newuser USERNAME EMAIL PASSWORD")
# 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 customfields BOARDID # Custom Fields of BOARDID
python3 api.py customfield BOARDID CUSTOMFIELDID # Info of CUSTOMFIELDID
python3 api.py addcustomfieldtoboard AUTHORID BOARDID NAME TYPE SETTINGS SHOWONCARD AUTOMATICALLYONCARD SHOWLABELONMINICARD SHOWSUMATTOPOFLIST # Add Custom Field to Board
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
=== ADD CUSTOM FIELD TO BOARD ===
Type: text, number, date, dropdown, checkbox, currency, stringtemplate.
python3 api.py addcustomfieldtoboard cmx3gmHLKwAXLqjxz LcDW4QdooAx8hsZh8 "SomeField" "date" "" true true true true
=== 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'
cf = 'custom-fields'
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, json=data)
d = body.json()
apikey = d['token']
# ------- LOGIN TOKEN END -----------
if arguments == 10:
if sys.argv[1] == 'addcustomfieldtoboard':
# ------- ADD CUSTOM FIELD TO BOARD START -----------
authorid = sys.argv[2]
boardid = sys.argv[3]
name = sys.argv[4]
type1 = sys.argv[5]
settings = str(json.loads(sys.argv[6]))
# There is error: Settings must be object. So this does not work yet.
#settings = {'currencyCode': 'EUR'}
print(type(settings))
showoncard = sys.argv[7]
automaticallyoncard = sys.argv[8]
showlabelonminicard = sys.argv[9]
showsumattopoflist = sys.argv[10]
customfieldtoboard = wekanurl + apiboards + boardid + s + cf
# Add Custom Field to Board
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
post_data = {'authorId': '{}'.format(authorid), 'name': '{}'.format(name), 'type': '{}'.format(type1), 'settings': '{}'.format(settings), 'showoncard': '{}'.format(showoncard), 'automaticallyoncard': '{}'.format(automaticallyoncard), 'showlabelonminicard': '{}'.format(showlabelonminicard), 'showsumattopoflist': '{}'.format(showsumattopoflist)}
body = requests.post(customfieldtoboard, data=post_data, headers=headers)
print(body.text)
# ------- ADD CUSTOM FIELD TO BOARD END -----------
if arguments == 7:
if sys.argv[1] == 'addcard':
# ------- ADD 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
# Add 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)
# ------- ADD CARD END -----------
if arguments == 6:
if sys.argv[1] == 'editcard':
# ------- EDIT CARD 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)
# ------- EDIT CARD END -----------
if arguments == 4:
if sys.argv[1] == 'newuser':
# ------- CREATE NEW USER START -----------
username = sys.argv[2]
email = sys.argv[3]
password = sys.argv[4]
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
post_data = {'username': '{}'.format(username),'email': '{}'.format(email),'password': '{}'.format(password)}
body = requests.post(users, data=post_data, headers=headers)
print("=== CREATE NEW USER ===\n")
print(body.text)
# ------- CREATE NEW USER 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 sys.argv[1] == 'customfield':
# ------- INFO OF CUSTOM FIELD START -----------
boardid = sys.argv[2]
customfieldid = sys.argv[3]
customfieldone = wekanurl + apiboards + boardid + s + cf + s + customfieldid
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
print("=== INFO OF ONE CUSTOM FIELD ===\n")
body = requests.get(customfieldone, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- INFO OF CUSTOM FIELD 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] == 'customfields':
# ------- CUSTOM FIELDS OF BOARD START -----------
boardid = sys.argv[2]
boardcustomfields = wekanurl + apiboards + boardid + s + cf
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
body = requests.get(boardcustomfields, headers=headers)
print("=== CUSTOM FIELDS OF BOARD ===\n")
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- CUSTOM FIELDS OF BOARD 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 -----------
if sys.argv[1] == 'boards':
# ------- LIST OF PUBLIC BOARDS START -----------
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
print("=== PUBLIC BOARDS ===\n")
listpublicboards = wekanurl + apiboards
body = requests.get(listpublicboards, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- LIST OF PUBLIC BOARDS END -----------
-8
View File
@@ -1,8 +0,0 @@
# v1.0 2022-03-22 Perl Asana export to WeKan ® release
This release adds the following new features:
- [Added Perl scripts for Asana export to WeKan ®](https://github.com/wekan/wekan/commit/376bcbb373d16317060adc2b1154cc20496775cc).
Thanks to GeekRuthie.
Thanks to above GitHub users for their contributions and translators for their translations.
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2022 WeKan ® Team and GeekRuthie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-178
View File
@@ -1,178 +0,0 @@
#!/usr/local/perl-cbt/bin/perl
use Modern::Perl;
use Carp;
use Data::Dumper;
use HTTP::Request;
use JSON;
use LWP::UserAgent;
use MIME::Base64 qw/encode_base64/;
my $BASE_URL = 'https://app.asana.com/api/1.0';
my $ASANA_API_KEY = 'ASANA_PERSONAL_TOKEN';
my $ua = LWP::UserAgent->new();
open my $input_wekan, '<', 'template.json';
my $wekan_json = readline($input_wekan);
close $input_wekan;
my $wekan_board = decode_json($wekan_json);
my %users;
my %users_by_gid;
# get user IDs from template board
foreach my $user ( @{ $wekan_board->{users} } ) {
$users{ $user->{profile}->{fullname} } = $user->{_id};
}
# get list IDs from template (we ended up not using these)
my %lists;
foreach my $list ( @{ $wekan_board->{lists} } ) {
$lists{ $list->{title} } = $list->{_id};
}
my @headers;
push @headers, ( 'Accept', 'application/json' );
push @headers, ( 'Authorization', "Bearer $ASANA_API_KEY" );
my $projects_req = HTTP::Request->new( "GET", "$BASE_URL/projects", \@headers, );
my $projects_res = $ua->request($projects_req);
my $projects = decode_json( $projects_res->content )->{data};
foreach my $project (@$projects) {
say "Project: ".$project->{name};
my $tasks_url =
'/tasks?project='
. $project->{gid}
. '&opt_fields=completed,name,notes,assignee,created_by,memberships.project.name, memberships.section.name,due_on,created_at,custom_fields';
my $tasks_req = HTTP::Request->new( 'GET', "$BASE_URL$tasks_url", \@headers );
my $tasks_res = $ua->request($tasks_req);
my @output_tasks;
my $tasks = decode_json( $tasks_res->content )->{data};
foreach my $task (@$tasks) {
next if $task->{completed};
say ' - '.$task->{name};
my $git_branch;
my $prio;
foreach my $custom ( @{ $task->{custom_fields} } ) {
if ( $custom->{name} eq 'git branch' ) {
$git_branch = $custom->{text_value};
next;
}
# We ended up not importing these.
if ( $custom->{name} eq 'Priority' && defined $custom->{display_value} ) {
$prio =
$custom->{display_value} eq 'High' ? 'fwccC9'
: $custom->{display_value} eq 'Med' ? 'yPnaFa'
: $custom->{display_value} eq 'Low' ? 'W4vMvm'
: 'ML5drH';
next;
}
}
if ( !defined $users_by_gid{ $task->{created_by}->{gid} } ) {
my $user_req =
HTTP::Request->new( 'GET', "$BASE_URL/users/" . $task->{created_by}->{gid},
\@headers );
my $user_res = $ua->request($user_req);
my $user = decode_json( $user_res->content )->{data};
if ( defined $users{ $user->{name} } ) {
$users_by_gid{ $task->{created_by}->{gid} } = $users{ $user->{name} };
}
}
my $creator = $users_by_gid{ $task->{created_by}->{gid} } // undef;
if ( defined $task->{assignee} && !defined $users_by_gid{ $task->{assignee}->{gid} } ) {
my $user_req =
HTTP::Request->new( 'GET', "$BASE_URL/users/" . $task->{assignee}->{gid},
\@headers );
my $user_res = $ua->request($user_req);
my $user = decode_json( $user_res->content )->{data};
if ( defined $users{ $user->{name} } ) {
$users_by_gid{ $task->{assignee}->{gid} } = $users{ $user->{name} };
}
}
my $assignee = defined $task->{assignee} ? $users_by_gid{ $task->{assignee}->{gid} } : undef;
my $list;
foreach my $membership ( @{ $task->{memberships} } ) {
next if $membership->{project}->{name} ne $project->{name};
$list = $membership->{section}->{name};
}
# I was trying to create JSON that I could use on the import screen in Wekan,
# but for bigger boards, it was just *too* hefty, so I took that JSON and used
# APIs to import.
my %output_task = (
swimlaneId => 'As4SNerx4Y4mMnJ8n', # 'Bugs'
sort => 0,
type => 'cardType-card',
archived => JSON::false,
title => $task->{name},
description => $task->{notes},
createdAt => $task->{created_at},
dueAt => defined $task->{due_on} ? $task->{due_on} . 'T22:00:00.000Z' : undef,
customFields => [
{
_id => 'rL8BpFHp5xxSFbDdr',
value => $git_branch,
},
],
labelIds => [$prio],
listId => $list,
userId => $creator,
assignees => [$assignee],
);
my @final_comments;
my $comments_req =
HTTP::Request->new( 'GET', "$BASE_URL/tasks/" . $task->{gid} . '/stories',
\@headers );
my $comments_res = $ua->request($comments_req);
my $comments = decode_json( $comments_res->content )->{data};
foreach my $comment (@$comments) {
next if $comment->{type} ne 'comment';
if ( !defined $users_by_gid{ $comment->{created_by}->{gid} } ) {
my $user_req =
HTTP::Request->new( 'GET', "$BASE_URL/users/" . $comment->{created_by}->{gid},
\@headers );
my $user_res = $ua->request($user_req);
my $user = decode_json( $user_res->content )->{data};
if ( defined $users{ $user->{name} } ) {
$users_by_gid{ $comment->{created_bye}->{gid} } = $users{ $user->{name} };
}
}
my $commentor = $users_by_gid{ $comment->{created_by}->{gid} };
my %this_comment = (
text => $comment->{text},
createdAt => $comment->{created_at},
userId => $commentor,
);
push @final_comments, \%this_comment;
}
$output_task{comments} = \@final_comments;
my @final_attachments;
my $attachments_req =
HTTP::Request->new( 'GET', "$BASE_URL/tasks/" . $task->{gid} . '/attachments',
\@headers );
my $attachments_res = $ua->request($attachments_req);
my $attachments = decode_json( $attachments_res->content )->{data};
foreach my $attachment (@$attachments) {
my $att_req =
HTTP::Request->new( 'GET', "$BASE_URL/attachments/" . $attachment->{gid},
\@headers );
my $att_res = $ua->request($att_req);
my $att = decode_json( $att_res->content )->{data};
my $file_req=HTTP::Request->new('GET',$att->{download_url});
my $file_res=$ua->request($file_req);
my $file=encode_base64($file_res->content);
my %this_attachment = (
file => $file,
name => $att->{name},
createdAt => $att->{created_at},
);
push @final_attachments, \%this_attachment;
}
$output_task{attachments} = \@final_attachments;
push @output_tasks, \%output_task;
}
my $file_name = $project->{name};
$file_name =~ s/\//_/g;
open my $output_file, '>',$file_name.'_exported.json';
print $output_file encode_json(\@output_tasks);
close $output_file;
}
-122
View File
@@ -1,122 +0,0 @@
#!/usr/local/perl-cbt/bin/perl
use Modern::Perl;
use Carp;
use Data::Dumper;
use HTTP::Request;
use JSON;
use LWP::UserAgent;
use MIME::Base64 qw/decode_base64/;
use Try::Tiny;
my $BASE_URL = 'https://taskboard.example.com/api';
my $TOKEN = 'MY_TOKEN';
my $me = 'MY_USER_ID';
my $ua = LWP::UserAgent->new();
my @headers;
push @headers, ( 'Accept', 'application/json' );
push @headers, ( 'Authorization', "Bearer $TOKEN" );
my @form_headers;
push @form_headers, ( 'Content-Type', 'application/json' );
push @form_headers, ( 'Accept', 'application/json' );
push @form_headers, ( 'Authorization', "Bearer $TOKEN" );
# Prior to running this, I built all the boards, with labels and lists and swimlanes.
# Grabbed the IDs for the boards for each project, and put them here to match up with
# filenames from the Asana export script.
my %board_to_use = (
Project_1 => 'F5ZiCXnf4d7qNBRjp',
Project_2 => 'Shw3tyfC2JWCutBLj',
);
opendir my $files_dir, '.'
or croak "Cannot open input directory: $!";
my @files = readdir $files_dir;
closedir $files_dir;
foreach my $tasks_file (@files) {
next if $tasks_file !~ /_exported.json/;
my $project = $tasks_file;
$project =~ s/_exported.json//;
say "Project - $project";
my $board;
if ( $board_to_use{$project} ) {
$board = $board_to_use{$project};
}
say ' No board!' if !$board;
next if !$board;
my $labels_req = HTTP::Request->new( 'GET', "$BASE_URL/boards/$board", \@headers );
my $labels_res = $ua->request($labels_req);
my $board_data = decode_json( $labels_res->content);
my $labels = $board_data->{labels};
my $label_to_use;
# We're merging several Asana boards onto one Wekan board, with labels per project.
foreach my $label (@$labels) {
$label_to_use = $label->{_id} if $label->{name} eq $project;
}
my $lanes_req = HTTP::Request->new( 'GET', "$BASE_URL/boards/$board/swimlanes", \@headers );
my $lanes_res = $ua->request($lanes_req);
my $lanes = decode_json( $lanes_res->content );
my $lane_to_use;
foreach my $lane (@$lanes) {
# Our Asana didn't use swimlanes; all of our Wekan boards have a "Bugs" lane, so use that.
$lane_to_use = $lane->{_id} if $lane->{title} eq 'Bugs';
}
my $lists_req = HTTP::Request->new( 'GET', "$BASE_URL/boards/$board/lists", \@headers );
my $lists_res = $ua->request($lists_req);
my $lists = decode_json( $lists_res->content );
my %list_to_use;
foreach my $list (@$lists) {
$list_to_use{ $list->{title} } = $list->{_id};
}
open my $task_export_file, '<', $tasks_file;
my $tasks_json = readline($task_export_file);
close $task_export_file;
my $tasks = decode_json($tasks_json);
foreach my $task (@$tasks) {
say ' - ' . $task->{title};
my %body_info = (
swimlaneId => $lane_to_use,
authorId => $task->{userId},
assignees => $task->{assignees},
title => $task->{title},
description => $task->{description},
);
my $body = encode_json( \%body_info );
my $list = $list_to_use{ $task->{listId} } // $list_to_use{'Backlog'};
my $task_req = HTTP::Request->new( 'POST', "$BASE_URL/boards/$board/lists/$list/cards",
\@form_headers, $body );
my $task_res = $ua->request($task_req);
my $res;
try {
$res = decode_json( $task_res->content );
} catch {
# Did these manually afterward.
say "--->UNABLE TO LOAD TASK";
next;
};
my $card = $res->{_id};
if ($label_to_use) {
my $card_edit_body = encode_json( { labelIds => [ $label_to_use ]});
my $card_edit_req = HTTP::Request->new( 'PUT', "$BASE_URL/boards/$board/lists/$list/cards/$card",
\@form_headers, $card_edit_body );
my $card_edit_res = $ua->request($card_edit_req);
}
foreach my $comment ( @{ $task->{comments} } ) {
my $comment_body =
encode_json( { authorId => $comment->{userId}, comment => $comment->{text} } );
my $comment_req =
HTTP::Request->new( 'POST', "$BASE_URL/boards/$board/cards/$card/comments",
\@form_headers, $comment_body );
my $comment_res = $ua->request($comment_req);
}
}
}
-6
View File
@@ -1,6 +0,0 @@
// PWA
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/pwa-service-worker.js');
});
}
-101
View File
@@ -1,101 +0,0 @@
.activity-title {
margin: 0 0.5em 0.8em;
display: flex;
justify-content: space-between;
}
.reactions-popup .add-comment-reaction {
display: inline-block;
cursor: pointer;
border-radius: 5px;
font-size: 22px;
text-align: center;
line-height: 30px;
width: 40px;
}
.reactions-popup .add-comment-reaction:hover {
background-color: #b0c4de;
}
.activities {
clear: both;
}
.activities .activity {
margin: 0.5px 0;
padding: 6px 0;
display: flex;
}
.activities .activity .member {
width: 32px;
height: 32px;
}
.activities .activity .activity-member {
font-weight: 700;
}
.activities .activity .activity-desc {
word-wrap: break-word;
overflow: hidden;
flex: 1;
align-self: center;
margin: 0;
margin-left: 3px;
overflow: hidden;
word-break: break-word;
}
.activities .activity .activity-desc .activity-comment {
display: block;
border-radius: 3px;
background: #fff;
text-decoration: none;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
margin-top: 5px;
padding: 5px;
}
.activities .activity .activity-desc .reactions {
display: flex;
margin-top: 5px;
gap: 5px;
}
.activities .activity .activity-desc .reactions .open-comment-reaction-popup {
display: flex;
align-items: center;
text-decoration: none;
height: 24px;
}
.activities .activity .activity-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o {
font-size: 17px;
font-weight: 500;
margin-left: 2px;
}
.activities .activity .activity-desc .reactions .open-comment-reaction-popup i.fa.fa-plus {
font-size: 8px;
margin-top: -7px;
margin-left: 1px;
}
.activities .activity .activity-desc .reactions .reaction {
cursor: pointer;
border: 1px solid #808080;
border-radius: 15px;
display: flex;
padding: 2px 5px;
}
.activities .activity .activity-desc .reactions .reaction.selected {
background-color: #b0c4de;
}
.activities .activity .activity-desc .reactions .reaction:hover {
background-color: #b0c4de;
}
.activities .activity .activity-desc .reactions .reaction .reaction-count {
font-size: 12px;
}
.activities .activity .activity-desc .activity-checklist {
display: block;
border-radius: 3px;
background: #fff;
text-decoration: none;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
margin-top: 5px;
padding: 5px;
}
.activities .activity .activity-desc .activity-meta {
font-size: 0.8em;
color: #999;
}
+188 -211
View File
@@ -8,239 +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 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="deleteCommentPopup")
p {{_ "comment-delete"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
if($eq activityType 'removedLabel')
| {{{_ 'activity-removed-label-card' lastLabel }}}.
template(name="commentReactions")
.reactions
each reaction in reactions
span.reaction(class="{{#if isSelected reaction.userIds}}selected{{/if}}" data-codepoint="#{reaction.reactionCodepoint}" title="{{userNames reaction.userIds}}")
span.reaction-codepoint !{reaction.reactionCodepoint}
span.reaction-count #{reaction.userIds.length}
if (currentUser.isBoardMember)
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
i.fa.fa-smile-o
i.fa.fa-plus
if($eq activityType 'removeChecklist')
| {{{_ 'activity-checklist-removed' cardLabel}}}.
template(name="addReactionPopup")
.reactions-popup
each codepoint in codepoints
span.add-comment-reaction(data-codepoint="#{codepoint}") !{codepoint}
if($eq activityType 'checkedItem')
| {{{_ 'activity-checked-item-card' checkItem checklist.title }}}.
template(name="activity")
.activity(data-id=activity._id)
+userAvatar(userId=activity.user._id)
p.activity-desc
span.activity-member
+memberName(user=activity.user)
if($eq activityType 'uncheckedItem')
| {{{_ 'activity-unchecked-item-card' checkItem checklist.title }}}.
//- attachment activity -------------------------------------------------
if($eq activity.activityType 'deleteAttachment')
| {{{_ 'activity-delete-attach' cardLink}}}.
if($eq activityType 'checklistCompleted')
| {{{_ 'activity-checklist-completed-card' checklist.title }}}.
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 'checklistUncompleted')
| {{{_ 'activity-checklist-uncompleted-card' checklist.title }}}.
//- board activity ------------------------------------------------------
if($eq activity.activityType 'createBoard')
| {{{_ 'activity-created' boardLabelLink}}}.
if($eq activityType 'restoredCard')
| {{_ 'activity-sent' cardLabel boardLabel}}.
if($eq activityType 'moveCard')
| {{_ 'activity-moved' cardLabel oldList.title list.title}}.
if($eq activity.activityType 'importBoard')
| {{{_ 'activity-imported-board' boardLabelLink sourceLink}}}.
if($eq activityType 'moveCardBoard')
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
if($eq activity.activityType 'addBoardMember')
| {{{_ 'activity-added' memberLink boardLabelLink}}}.
if($eq activity.activityType 'removeBoardMember')
| {{{_ 'activity-excluded' memberLink boardLabelLink}}}.
//- card activity -------------------------------------------------------
if($eq activity.activityType 'createCard')
if($eq mode 'card')
| {{{_ 'activity-added' cardLabelLink (sanitize activity.listName)}}}.
else
| {{{_ 'activity-added' cardLabelLink boardLabelLink}}}.
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'}}
.fa.fa-times-thin.js-close-inlined-form
else
.activity-comment
+viewer
= activity.comment.text
+commentReactions(reactions=activity.comment.reactions commentId=activity.comment._id)
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' activity.commentId}}}.
if($eq activity.activityType 'editComment')
| {{{_ 'activity-editComment' activity.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 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 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($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 }}
+82 -282
View File
@@ -1,38 +1,33 @@
import DOMPurify from 'dompurify';
import { TAPi18n } from '/imports/i18n';
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 searchId;
let thisId, searchId;
if (mode === 'linkedcard' || mode === 'linkedboard') {
searchId = Utils.getCurrentCard().linkedId;
thisId = Session.get('currentCard');
searchId = Cards.findOne({_id: thisId}).linkedId;
mode = mode.replace('linked', '');
} else if (mode === 'card') {
searchId = Utils.getCurrentCardId();
} else {
searchId = Session.get(`current${capitalizedMode}`);
thisId = Session.get(`current${capitalizedMode}`);
searchId = thisId;
}
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
@@ -47,169 +42,83 @@ BlazeComponent.extendComponent({
});
});
},
loadNextPage() {
if (this.loadNextPageLocked === false) {
this.page.set(this.page.get() + 1);
this.loadNextPageLocked = true;
}
},
}).register('activities');
Template.activities.helpers({
activities() {
const ret = this.card.activities();
return ret;
},
});
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();
const currentBoardId = Session.get('currentBoard');
if (data.mode !== 'board') {
// data.mode: card, linkedcard, linkedboard
return createBoardLink(data.activity.board(), data.activity.listName ? data.activity.listName : null);
}
else if (currentBoardId != data.activity.boardId) {
// data.mode: board
// current activitie is linked
return createBoardLink(data.activity.board(), data.activity.listName ? data.activity.listName : null);
}
boardLabel() {
return TAPi18n.__('this-board');
},
cardLabelLink() {
const data = this.currentData();
const currentBoardId = Session.get('currentBoard');
if (data.mode == 'card') {
// data.mode: card
return TAPi18n.__('this-card');
}
else if (data.mode !== 'board') {
// data.mode: linkedcard, linkedboard
return createCardLink(data.activity.card(), null);
}
else if (currentBoardId != data.activity.boardId) {
// data.mode: board
// current activitie is linked
return createCardLink(data.activity.card(), data.activity.board().title);
}
return createCardLink(this.currentData().activity.card(), null);
cardLabel() {
return TAPi18n.__('this-card');
},
cardLink() {
const data = this.currentData();
const currentBoardId = Session.get('currentBoard');
if (data.mode !== 'board') {
// data.mode: card, linkedcard, linkedboard
return createCardLink(data.activity.card(), null);
}
else if (currentBoardId != data.activity.boardId) {
// data.mode: board
// current activitie is linked
return createCardLink(data.activity.card(), data.activity.board().title);
}
return createCardLink(this.currentData().activity.card(), null);
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 === '')) {
return lastLabel.color;
} else if (lastLabel.name !== undefined && lastLabel.name !== '') {
return lastLabel.name;
} else {
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{
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;
@@ -217,154 +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.path &&
Blaze.toHTML(
HTML.A(
{
href: `${attachment.link()}?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': Popup.afterConfirm('deleteComment', () => {
const commentId = this.data().activity.commentId;
CardComments.remove(commentId);
Popup.back();
}),
'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 });
},
});
Template.commentReactions.events({
'click .reaction'(event) {
if (Meteor.user().isBoardMember()) {
const codepoint = event.currentTarget.dataset['codepoint'];
const commentId = Template.instance().data.commentId;
const cardComment = CardComments.findOne({_id: commentId});
cardComment.toggleReaction(codepoint);
}
},
'click .open-comment-reaction-popup': Popup.open('addReaction'),
})
Template.addReactionPopup.events({
'click .add-comment-reaction'(event) {
if (Meteor.user().isBoardMember()) {
const codepoint = event.currentTarget.dataset['codepoint'];
const commentId = Template.instance().data.commentId;
const cardComment = CardComments.findOne({_id: commentId});
cardComment.toggleReaction(codepoint);
}
Popup.back();
},
})
Template.addReactionPopup.helpers({
codepoints() {
// Starting set of unicode codepoints as comment reactions
return [
'&#128077;',
'&#128078;',
'&#128064;',
'&#9989;',
'&#10060;',
'&#128591;',
'&#128079;',
'&#127881;',
'&#128640;',
'&#128522;',
'&#129300;',
'&#128532;'];
}
})
Template.commentReactions.helpers({
isSelected(userIds) {
return userIds.includes(Meteor.user()._id);
},
userNames(userIds) {
return Users.find({_id: {$in: userIds}})
.map(user => user.profile.fullname)
.join(', ');
}
})
function createCardLink(card, board) {
if (!card) return '';
let text = card.title;
if (board) text = `${board} > ` + text;
return (
card &&
Blaze.toHTML(
HTML.A(
{
href: card.originRelativeUrl(),
class: 'action-card',
},
DOMPurify.sanitize(text, { 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');
@@ -0,0 +1,48 @@
@import 'nib'
.activity-title
margin: 0 0.5em 0.8em
display: flex
justify-content:space-between
.activities
clear: both
.activity
margin: 10px 0
display: flex
.member
width: 24px
height: @width
.activity-desc
word-wrap: break-word
overflow: hidden
flex: 1
align-self: center
margin: 0
margin-left: 3px
overflow: hidden;
word-break: break-word;
.activity-comment
display: block
border-radius: 3px
background: white
text-decoration: none
box-shadow: 0 1px 2px rgba(0,0,0,.2)
margin-top: 5px
padding: 5px
.activity-checklist
display: block
border-radius: 3px
background: white
text-decoration: none
box-shadow: 0 1px 2px rgba(0,0,0,.2)
margin-top: 5px
padding: 5px
.activity-meta
font-size: 0.8em
color: darken(white, 40%)
-65
View File
@@ -1,65 +0,0 @@
.new-comment {
position: relative;
margin: 0 0 20px 38px;
}
.new-comment .member {
opacity: 0.7;
position: absolute;
top: 1px;
left: -38px;
}
.new-comment.is-open .member {
opacity: 1;
}
.new-comment.is-open .helper {
display: inline-block;
}
.new-comment.is-open textarea {
min-height: 100px;
color: #4d4d4d;
cursor: auto;
overflow: hidden;
word-wrap: break-word;
}
.new-comment .too-long {
margin-top: 8px;
}
.new-comment textarea {
background-color: #fff;
border: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
height: 36px;
margin: 4px 4px 6px 0;
padding: 9px 11px;
width: 100%;
}
.new-comment textarea:hover,
.new-comment textarea:is-open {
background-color: #fff;
box-shadow: 0 1px 3px rgba(0,0,0,0.33);
border: 0;
cursor: pointer;
}
.new-comment textarea:is-open {
cursor: auto;
}
.comment-item {
background-color: #fff;
border: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
color: #8c8c8c;
height: 36px;
margin: 4px 4px 6px 0;
width: 92%;
}
.comment-item:hover {
background: #e0e0e0;
}
.comment-item.add-comment {
display: flex;
margin: 5px;
}
.comment-item.add-comment a {
display: block;
margin: auto;
}
+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}}
+38 -45
View File
@@ -3,7 +3,6 @@ const commentFormIsOpen = new ReactiveVar(false);
BlazeComponent.extendComponent({
onDestroyed() {
commentFormIsOpen.set(false);
$('.note-popover').hide();
},
commentFormIsOpen() {
@@ -15,47 +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;
} else if (card.isLinkedBoard()) {
boardId = 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);
}
@@ -66,18 +63,17 @@ function resetCommentInput(input) {
// Tracker.autorun to register the component dependencies, and re-run when these
// dependencies are invalidated. A better component API would remove this hack.
Tracker.autorun(() => {
Utils.getCurrentCardId();
Session.get('currentCard');
Tracker.afterFlush(() => {
autosize.update($('.js-new-comment-input'));
});
});
EscapeActions.register(
'inlinedForm',
EscapeActions.register('inlinedForm',
() => {
const draftKey = {
fieldName: 'cardComment',
docId: Utils.getCurrentCardId(),
docId: Session.get('currentCard'),
};
const commentInput = $('.js-new-comment-input');
const draft = commentInput.val().trim();
@@ -88,10 +84,7 @@ EscapeActions.register(
}
resetCommentInput(commentInput);
},
() => {
return commentFormIsOpen.get();
},
{
() => { return commentFormIsOpen.get(); }, {
noClickEscapeOn: '.js-new-comment',
},
}
);
@@ -0,0 +1,48 @@
@import 'nib'
.new-comment
position: relative
margin: 0 0 20px 38px
.member
opacity: .7
position: absolute
top: 1px
left: -38px
&.is-open
.member
opacity: 1
.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)
color: #8c8c8c
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
@@ -14,7 +14,6 @@ template(name="archivedBoards")
i.fa.fa-undo
| {{_ 'restore-board'}}
= title
span {{ moment archivedAt 'LLL' }}
else
li.no-items-message {{_ 'no-archived-boards'}}
+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.back();
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');
-169
View File
@@ -1,169 +0,0 @@
.board-wrapper {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow-x: hidden;
overflow-y: hidden;
}
.board-wrapper .board-canvas {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
transition: margin 0.1s;
overflow-y: auto;
}
.board-wrapper .board-canvas.is-sibling-sidebar-open {
margin-right: 248px;
}
.board-wrapper .board-canvas .board-overlay {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
top: -100px;
right: -400px;
background: #000;
opacity: 0.33;
animation: fadeIn 0.2s;
z-index: 16;
}
.board-wrapper .board-canvas.is-dragging-active .open-minicard-composer,
.board-wrapper .board-canvas.is-dragging-active .minicard-wrapper.is-checked {
display: none;
}
@media screen and (max-width: 800px) {
.board-wrapper .board-canvas .swimlane {
border-bottom: 1px solid #ccc;
display: flex;
flex-direction: column;
margin: 0;
padding: 0 0px 0px 0;
overflow-x: hidden;
overflow-y: auto;
}
}
.calendar-event-green {
background: #3cb500 !important;
border-color: #2a8000;
color: #fff !important;
}
.calendar-event-yellow {
background: #fad900 !important;
border-color: #c7ac00;
color: #000 !important;
}
.calendar-event-orange {
background: #ff9f19 !important;
border-color: #cc7c14;
color: #000 !important;
}
.calendar-event-red {
background: #eb4646 !important;
border-color: #b83737;
color: #fff !important;
}
.calendar-event-purple {
background: #a632db !important;
border-color: #7d26a6;
color: #fff !important;
}
.calendar-event-blue {
background: #0079bf !important;
border-color: #005a8a;
color: #fff !important;
}
.calendar-event-pink {
background: #ff78cb !important;
border-color: #cc62a3;
color: #000 !important;
}
.calendar-event-sky {
background: #00c2e0 !important;
border-color: #0094ab;
color: #fff !important;
}
.calendar-event-black {
background: #4d4d4d !important;
border-color: #1a1a1a;
color: #fff !important;
}
.calendar-event-lime {
background: #51e898 !important;
border-color: #3eb375;
color: #000 !important;
}
.calendar-event-silver {
background: #c0c0c0 !important;
border-color: #8c8c8c;
color: #000 !important;
}
.calendar-event-peachpuff {
background: #ffdab9 !important;
border-color: #ccaf95;
color: #000 !important;
}
.calendar-event-crimson {
background: #dc143c !important;
border-color: #a8112f;
color: #fff !important;
}
.calendar-event-plum {
background: #dda0dd !important;
border-color: #a87ba8;
color: #000 !important;
}
.calendar-event-darkgreen {
background: #006400 !important;
border-color: #003000;
color: #fff !important;
}
.calendar-event-slateblue {
background: #6a5acd !important;
border-color: #4f4399;
color: #fff !important;
}
.calendar-event-magenta {
background: #f0f !important;
border-color: #c0c;
color: #fff !important;
}
.calendar-event-gold {
background: #ffd700 !important;
border-color: #ca0;
color: #000 !important;
}
.calendar-event-navy {
background: #000080 !important;
border-color: #003;
color: #fff !important;
}
.calendar-event-gray {
background: #808080 !important;
border-color: #333;
color: #fff !important;
}
.calendar-event-saddlebrown {
background: #8b4513 !important;
border-color: #572b0c;
color: #fff !important;
}
.calendar-event-paleturquoise {
background: #afeeee !important;
border-color: #8ababa;
color: #000 !important;
}
.calendar-event-mistyrose {
background: #ffe4e1 !important;
border-color: #ccb8b6;
color: #000 !important;
}
.calendar-event-indigo {
background: #4b0082 !important;
border-color: #2b004d;
color: #fff !important;
}
+23 -26
View File
@@ -6,36 +6,33 @@ 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")
if notDisplayThisBoard
| {{_ 'tableVisibilityMode-allowPrivateOnly'}}
else
.board-wrapper(class=currentBoard.colorClass)
+sidebar
.board-canvas.js-swimlanes(
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}}")
if showOverlay.get
.board-overlay
if currentBoard.isTemplatesBoard
each currentBoard.swimlanes
+swimlane(this)
else if isViewSwimlanes
each currentBoard.swimlanes
+swimlane(this)
else if isViewLists
+listsGroup(currentBoard)
else if isViewCalendar
+calendarView
else
+listsGroup(currentBoard)
.board-wrapper(class=currentBoard.colorClass)
+sidebar
.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}}")
if showOverlay.get
.board-overlay
if currentBoard.isTemplatesBoard
each currentBoard.swimlanes
+swimlane(this)
else if isViewSwimlanes
each currentBoard.swimlanes
+swimlane(this)
else if isViewLists
+listsGroup(currentBoard)
else if isViewCalendar
+calendarView
else
+listsGroup(currentBoard)
template(name="calendarView")
if isViewCalendar
+64 -155
View File
@@ -1,7 +1,5 @@
import { TAPi18n } from '/imports/i18n';
const subManager = new SubsManager();
const { calculateIndex } = Utils;
const { calculateIndex, enableClickOnTouch } = Utils;
const swimlaneWhileSortingHeight = 150;
BlazeComponent.extendComponent({
@@ -14,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(() => {
@@ -25,17 +24,17 @@ BlazeComponent.extendComponent({
},
onlyShowCurrentCard() {
return Utils.isMiniScreen() && Utils.getCurrentCardId(true);
return Utils.isMiniScreen() && Session.get('currentCard');
},
goHome() {
FlowRouter.go('home');
},
}).register('board');
BlazeComponent.extendComponent({
onCreated() {
Meteor.subscribe('tableVisibilityModeSettings');
this.showOverlay = new ReactiveVar(false);
this.draggingActive = new ReactiveVar(false);
this._isDragging = false;
@@ -48,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,
@@ -63,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,
@@ -92,6 +91,7 @@ BlazeComponent.extendComponent({
helper.append(list.clone());
return helper;
},
handle: '.js-swimlane-header',
items: '.swimlane:not(.placeholder)',
placeholder: 'swimlane placeholder',
distance: 7,
@@ -110,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();
@@ -127,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');
@@ -158,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
@@ -176,48 +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(() => {
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$swimlanesDom.sortable({
handle: '.js-swimlane-header-handle',
});
} else {
$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() || !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
@@ -228,69 +192,48 @@ BlazeComponent.extendComponent({
}
},
notDisplayThisBoard() {
let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly');
let currentBoard = Boards.findOne(Session.get('currentBoard'));
if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue && currentBoard.permission == 'public') {
return true;
}
return false;
},
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()) {
// The form had been removed in 416b17062e57f215206e93a85b02ef9eb1ab4902
// this.childComponents('swimlane')[0]
// .childComponents('addListAndSwimlaneForm')[0]
// .open();
this.childComponents('swimlane')[0]
.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
@@ -302,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');
});
},
@@ -329,11 +271,9 @@ BlazeComponent.extendComponent({
defaultView: 'agendaDay',
editable: true,
timezone: 'local',
weekNumbers: true,
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
@@ -343,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',
},
@@ -351,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);
},
@@ -411,10 +325,8 @@ BlazeComponent.extendComponent({
if (card) {
// TODO: add a flag for allDay events
if (!event.allDay) {
// https://github.com/wekan/wekan/issues/2917#issuecomment-1236753962
//card.setStart(event.start.toDate());
//card.setEnd(event.end.toDate());
card.setDue(event.start.toDate());
card.setStart(event.start.toDate());
card.setEnd(event.end.toDate());
isOk = true;
}
}
@@ -425,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');
+55
View File
@@ -0,0 +1,55 @@
@import 'nib'
position()
if arguments[0] == cover || arguments[0] == fixed-cover
if arguments[0] == cover
position: absolute
else
position: fixed
left: 0
right: 0
top: 0
bottom: 0
else
position: arguments
.board-wrapper
position: cover
overflow-x: hidden
overflow-y: hidden
.board-canvas
position: cover
transition: margin .1s
overflow-y: auto
&.is-sibling-sidebar-open
margin-right: 248px
.board-overlay
position: fixed-cover
top: -100px
right: -400px
background: black
opacity: 0.33
animation: fadeIn 0.2s
z-index: 16
&.is-dragging-active
.open-minicard-composer,
.minicard-wrapper.is-checked
display: none
@media screen and (max-width: 800px)
.board-wrapper
.board-canvas
.swimlane
border-bottom: 1px solid #CCC
display: flex
flex-direction: column
margin: 0
padding: 0 0px 0px 0
overflow-x: hidden
overflow-y: auto
File diff suppressed because it is too large. Load diff
+217
View File
@@ -0,0 +1,217 @@
// We define a set of six board colors that we took from the FlatUI palette.
// http://flatuicolors.com
//
// XXX Centralizing all these properties in a single file just because their
// value is derived from the same color, doesn't make any sense. We should
// create a mixin/macro that would generate 6 versions of a given property and
// dispatch this list in the other stylus files.
setBoardColor(color)
&#header,
&.sk-spinner div,
.board-backgrounds-list &.background-box,
.board-list & a
background-color: color
.is-selected .minicard
border-left: 3px solid color
button[type=submit].primary, input[type=submit].primary
background-color: darken(color, 20%)
&.pop-over .pop-over-list li a:not(.disabled):hover,
.sidebar .sidebar-content .sidebar-btn:hover,
.sidebar-list li a:hover
background-color: lighten(color, 10%)
&#header ul li.current, &#header-quick-access ul li.current
border-bottom: 2px solid lighten(color, 10%)
&#header-quick-access
background: darken(color, 10%)
color: white
&#header #header-main-bar .board-header-btn.emphasis
background: complement(color)
&:hover,
.board-header-btn-close
background: darken(complement(color), 10%)
&:hover .board-header-btn-close
background: darken(complement(color), 20%)
.materialCheckBox.is-checked
border-bottom: 2px solid color
border-right: 2px solid color
.is-multiselection-active .multi-selection-checkbox
&.is-checked + .minicard
background: lighten(color, 90%)
&:not(.is-checked) + .minicard:hover:not(.minicard-composer)
background: lighten(color, 97%)
.toggle-label
&:after
background-color: darken(color, 20%)
.toggle-switch:checked ~ .toggle-label
background-color: lighten(color, 20%)
&:after
background-color: darken(color, 20%)
@media screen and (max-width: 800px)
&.pop-over .header
background: color
color: white
&#header ul li.current, &#header-quick-access ul li.current
border-bottom: 4px solid lighten(color, 20%)
.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)
.board-color-belize
setBoardColor(#2980B9)
.board-color-wisteria
setBoardColor(#8E44AD)
.board-color-midnight
setBoardColor(#2C3E50)
/* 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,
.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; }
// DARK MODE, when dark background mode selected.
// Modified version from https://github.com/wekan/wekan/wiki/Custom-CSS-themes#dark-theme
// In progress, please send pull requests to fix remaining visibility issues.
.ui-sortable,
.swimlane,
.swimlane >.swimlane-header-wrap,
.swimlane >.list.js-list,
.swimlane >.list-composer.js-list-composer,
.list-body,
.list,
.list-composer,
.sidebar-content,
.card-details
background-color:#2C3E50
.card-details h3,
.card-details-items,
.card-checklist-items .ui-sortable,
.card-subtasks-items,
.activities,
.material-toggle-switch
color:#bbbbbb
.list-header
background-color: #888888
.board-widget,
.board-widget-labels,
.board-widget-members
color: #aaaaaa
/* popup menu titles (boards, swimlanes, lists, cards, labels) */
.pop-over >.header
display:none;
/* HIDE UNTIL HOVER -------------------------------------------------- */
/* header "+" button */
#header-quick-access .fa-plus
display:none
#header-quick-access:hover .fa-plus
display:inherit
/* "add card" links (use visibility rather than display so items don't jump) */
.open-minicard-composer
visibility:hidden
.list.js-list:hover .open-minicard-composer
visibility:visible
.list-header-menu
visibility:hidden
.list.js-list:hover .list-header-menu
visibility:visible
/* "add list/swimlane" links (use visibility rather than display so items don't jump) */
.list.js-list-composer >.list-header
visibility:hidden
.list.js-list-composer:hover >.list-header
visibility:visible
/* headers */
#header-quick-access, #header
background-color:rgba(0,0,0,.75) !important
#header .board-header-btn:hover
background-color:rgba(255,255,255,0.3) !important
/* foregrounds: swimlanes, lists */
.list >.list-header, .swimlane-header
color:rgba(255,255,255,.7)
/* minicards */
.minicard
background-color:rgba(255,255,255,.4)
.minicard-wrapper.is-selected .minicard,
.minicard:hover,
.minicard-composer.js-composer,
.open-minicard-composer:hover
background-color:rgba(255,255,255,.8) !important
color:#000
.minicard, .minicard .badge
color:#fff
.minicard:hover .badge, .minicard-wrapper.is-selected .badge
color:#000
/* cards */
.card-details .card-details-header
background-color:#ccc
/* sidebar */
.sidebar-tongue, .sidebar-shadow
background-color:#666 !important
.sidebar-content h3, .sidebar-content h2, .sidebar-content
color:rgba(255,255,255,.7) !important
.board-color-pumpkin
setBoardColor(#E67E22)
-23
View File
@@ -1,23 +0,0 @@
.integration-form {
padding: 5px;
border-bottom: 1px solid #ccc;
}
.flex,
.option {
display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -moz-flex;
display: -ms-flexbox;
display: flex;
}
.option {
-webkit-border-radius: 3px;
border-radius: 3px;
background: #fff;
text-decoration: none;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.2);
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
margin-top: 5px;
padding: 5px;
}
+21 -103
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}}")
@@ -80,12 +64,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(
@@ -99,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}}")
@@ -112,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
@@ -120,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(
@@ -140,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")
@@ -153,15 +129,14 @@ template(name="boardVisibilityList")
if visibilityCheck
i.fa.fa-check
span.sub-name {{_ 'private-desc'}}
if notAllowPrivateVisibilityOnly
li
with "public"
a.js-select-visibility
i.fa.fa-globe.colorful
| {{_ 'public'}}
if visibilityCheck
i.fa.fa-check
span.sub-name {{_ 'public-desc'}}
li
with "public"
a.js-select-visibility
i.fa.fa-globe.colorful
| {{_ 'public'}}
if visibilityCheck
i.fa.fa-check
span.sub-name {{_ 'public-desc'}}
template(name="boardChangeVisibilityPopup")
+boardVisibilityList
@@ -193,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
@@ -235,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'}}
@@ -246,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
@@ -273,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'}}
+115 -354
View File
@@ -1,19 +1,12 @@
import { TAPi18n } from '/imports/i18n';
/*
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'() {
Sidebar.setView('customFields');
Popup.back();
Popup.close();
},
'click .js-open-archives'() {
Sidebar.setView('archives');
Popup.back();
Popup.close();
},
'click .js-change-board-color': Popup.open('boardChangeColor'),
'click .js-change-language': Popup.open('changeLanguage'),
@@ -26,33 +19,41 @@ Template.boardMenuPopup.events({
}),
'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
Popup.back();
Popup.close();
Boards.remove(currentBoard._id);
FlowRouter.go('home');
}),
'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
'click .js-card-settings': Popup.open('boardCardSettings'),
'click .js-minicard-settings': Popup.open('boardMinicardSettings'),
});
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.back();
Popup.close();
}
event.preventDefault();
evt.preventDefault();
},
});
@@ -73,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 = Utils.getCurrentCardId();
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.back();
},
'click .js-open-swimlanes-view'() {
Utils.setBoardView('board-view-swimlanes');
Popup.back();
},
'click .js-open-cal-view'() {
Utils.setBoardView('board-view-cal');
Popup.back();
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});
@@ -197,11 +149,6 @@ const CreateBoard = BlazeComponent.extendComponent({
this.visibilityMenuIsOpen = new ReactiveVar(false);
this.visibility = new ReactiveVar('private');
this.boardId = new ReactiveVar('');
Meteor.subscribe('tableVisibilityModeSettings');
},
notAllowPrivateVisibilityOnly(){
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
},
visibilityCheck() {
@@ -217,110 +164,47 @@ 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({
notAllowPrivateVisibilityOnly(){
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
},
visibilityCheck() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
return this.currentData() === currentBoard.permission;
@@ -330,15 +214,13 @@ BlazeComponent.extendComponent({
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const visibility = this.currentData();
currentBoard.setVisibility(visibility);
Popup.back();
Popup.close();
},
events() {
return [
{
'click .js-select-visibility': this.selectBoardVisibility,
},
];
return [{
'click .js-select-visibility': this.selectBoardVisibility,
}];
},
}).register('boardChangeVisibilityPopup');
@@ -353,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.back();
},
);
},
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.back();
}
},
},
];
},
}).register('listsortPopup');
*/
BlazeComponent.extendComponent({
events() {
return [
{
'click .js-sort-due'() {
const sortBy = {
dueAt: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('due-date'));
Popup.back();
},
'click .js-sort-title'() {
const sortBy = {
title: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('title'));
Popup.back();
},
'click .js-sort-created-asc'() {
const sortBy = {
createdAt: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-newest-first'));
Popup.back();
},
'click .js-sort-created-desc'() {
const sortBy = {
createdAt: -1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-oldest-first'));
Popup.back();
},
},
];
},
}).register('cardsSortPopup');
+22
View File
@@ -0,0 +1,22 @@
.integration-form
padding: 5px
border-bottom: 1px solid #ccc
.flex
display: -webkit-box
display: -moz-box
display: -webkit-flex
display: -moz-flex
display: -ms-flexbox
display: flex
.option
@extends .flex
-webkit-border-radius: 3px;
border-radius: 3px;
background: #fff;
text-decoration: none;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.2);
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
margin-top: 5px;
padding: 5px;
-281
View File
@@ -1,281 +0,0 @@
@import url("../../../css/reset.css") print, screen;
.board-list {
margin: 0 8px;
}
.board-list li {
float: left;
width: 20%;
box-sizing: border-box;
position: relative;
}
.board-list li.placeholder:after {
content: '';
display: block;
background: #ccc;
border-radius: 3px;
height: 106px;
margin: 8px;
}
.board-list li.ui-sortable-helper {
cursor: grabbing;
transform: rotate(4deg);
display: block !important;
}
.board-list li.starred .fa-star,
.board-list li.starred .fa-star-o {
opacity: 1;
}
.board-list .board-list-item {
overflow: hidden;
background-color: #999;
color: #f6f6f6;
min-height: 100px;
font-size: 16px;
line-height: 22px;
border-radius: 3px;
display: block;
font-weight: 700;
padding: 8px;
margin: 8px;
position: relative;
text-decoration: none;
word-wrap: break-word;
}
.board-list .board-list-item.template-container {
border: 4px solid #fff;
}
.board-list .board-list-item.tile {
background-size: auto;
background-repeat: repeat;
}
.board-list .board-list-item-sub-name {
color: rgba(255,255,255,0.5);
display: block;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
.board-list .board-list-item-desc {
color: #fff;
display: block;
font-size: 14px;
font-weight: 400;
line-height: 18px;
}
.board-list .js-add-board {
text-align: center;
}
.board-list .js-add-board .label {
font-weight: normal;
line-height: 56px;
}
.board-list .js-add-board :hover {
background-color: #939393;
}
.board-list .fa-star,
.board-list .fa-star-o {
bottom: 0;
font-size: 14px;
height: 18px;
line-height: 18px;
opacity: 0;
padding: 9px 9px;
position: absolute;
right: 0;
top: 0;
transition-duration: 0.15s;
transition-property: color, font-size, background;
}
.board-list .fa-circle {
bottom: 0;
font-size: 10px;
height: 10px;
line-height: 10px;
padding: 9px 9px;
position: absolute;
right: 0;
transition-duration: 0.15s;
transition-property: color, font-size, background;
}
.board-list .has-overtime-card-active {
color: #eb4646 !important;
}
.board-list .no-overtime-card-active {
color: #3cb500 !important;
}
.board-list .is-star-active {
color: #fff;
}
.board-list .fa-clone {
position: absolute;
bottom: 0;
font-size: 14px;
height: 18px;
line-height: 18px;
opacity: 0;
right: 0;
padding: 9px 9px;
transition-duration: 0.15s;
transition-property: color, font-size, background;
}
.board-list .fa-archive {
position: absolute;
bottom: 0;
font-size: 14px;
height: 18px;
line-height: 18px;
opacity: 0;
left: 0;
padding: 9px 9px;
transition-duration: 0.15s;
transition-property: color, font-size, background;
}
.board-list li:hover a:hover .fa-star,
.board-list li:hover a:hover .fa-clone,
.board-list li:hover a:hover .fa-archive,
.board-list li:hover a:hover .fa-star-o {
color: #fff;
}
.board-list li:hover a .fa-star,
.board-list li:hover a .fa-clone,
.board-list li:hover a .fa-archive,
.board-list li:hover a .fa-star-o {
color: #fff;
opacity: 0.75;
}
.board-list li:hover a .fa-star:hover,
.board-list li:hover a .fa-clone:hover,
.board-list li:hover a .fa-archive:hover,
.board-list li:hover a .fa-star-o:hover {
font-size: 18px;
opacity: 1;
}
.board-list li:hover a .fa-star.is-star-active,
.board-list li:hover a .fa-clone.is-star-active,
.board-list li:hover a .fa-archive.is-star-active,
.board-list li:hover a .fa-star-o.is-star-active {
opacity: 1;
}
.board-backgrounds-list .board-background-select {
box-sizing: border-box;
display: block;
float: left;
width: 50%;
padding-top: 12px;
position: relative;
z-index: 1;
}
.board-backgrounds-list .board-background-select:nth-child(-n + 2) {
padding-top: 0;
}
.board-backgrounds-list .board-background-select:nth-child(2n) {
padding-left: 6px;
}
.board-backgrounds-list .board-background-select:nth-child(2n+1) {
padding-right: 6px;
}
.board-backgrounds-list .board-background-select .background-box {
color: #fff;
border-radius: 3px;
background-size: cover;
display: block;
height: 74px;
position: relative;
width: 100%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.board-backgrounds-list .board-background-select .background-box i.fa-check {
font-size: 25px;
color: #fff;
}
@media screen and (max-width: 800px) {
.board-list {
height: 100%;
overflow: scroll;
}
.board-list li {
width: 50%;
}
.board-list .board-list-item {
overflow: hidden;
height: 8rem;
}
.board-list .board-list-item-sub-name {
position: relative;
top: -100px;
left: -100px;
}
.board-list .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;
}
}
.AllBoardTeamsOrgs {
list-style-type: none;
overflow: hidden;
}
.AllBoardTeams,
.AllBoardOrgs,
.AllBoardBtns {
float: left;
}
.js-AllBoardOrgs {
margin-left: 16px;
}
.AllBoardTeams {
margin-left: 16px;
}
.AllBoardButtonsContainer {
margin: 16px;
}
#filterBtn,
#resetBtn {
display: inline;
}
.js-board {
display: block;
}
.minicard-members {
padding: 6px 0 6px 8px;
width: 100%;
margin-bottom: 2px;
margin-left: -4px;
display: inline-block;
}
.minicard-lists {
margin: 0 auto;
max-width: 95%;
height: 100%;
}
.flex {
display: flex;
}
.flex-wrap {
flex-wrap: wrap;
}
.flex-wrap .item {
margin: 2px;
padding-right: 6px;
text-align: center;
}
+32 -137
View File
@@ -1,40 +1,10 @@
template(name="boardList")
.wrapper
ul.AllBoardTeamsOrgs
li.AllBoardTeams
if userHasTeams
select.js-AllBoardTeams#jsAllBoardTeams("multiple")
option(value="-1") {{_ 'teams'}} :
each teamsDatas
option(value="{{teamId}}") {{_ teamDisplayName}}
li.AllBoardOrgs
if userHasOrgs
select.js-AllBoardOrgs#jsAllBoardOrgs("multiple")
option(value="-1") {{_ 'organizations'}} :
each orgsDatas
option(value="{{orgId}}") {{orgDisplayName}}
//li.AllBoardTemplates
// if userHasTemplates
// select.js-AllBoardTemplates#jsAllBoardTemplates("multiple")
// option(value="-1") {{_ 'templates'}} :
// each templatesDatas
// option(value="{{templateId}}") {{_ templateDisplayName}}
li.AllBoardBtns
div.AllBoardButtonsContainer
if userHasOrgsOrTeams
i.fa.fa-filter
input#filterBtn(type="button" value="{{_ 'filter'}}")
input#resetBtn(type="button" value="{{_ 'filter-clear'}}")
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="{{_id}}" class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board
li(class="{{#if isStarred}}starred{{/if}}" class=colorClass)
if isInvited
.board-list-item
span.details
@@ -46,109 +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 isTouchScreenOrShowDesktopDragHandles
i.fa.board-handle(
class="fa-arrows"
title="{{_ 'Drag board'}}")
else
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
unless currentSetting.hideBoardMemberList
if allowsBoardMemberList
.minicard-members
each member in boardMembers _id
a.name
+userAvatar(userId=member noRemove=true)
unless currentSetting.hideCardCounterList
if allowsCardCounterList
.minicard-lists.flex.flex-wrap
each list in boardLists _id
.item
| {{ list }}
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 isTouchScreenOrShowDesktopDragHandles
i.fa.board-handle(
class="fa-arrows"
title="{{_ 'Drag board'}}")
else
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 -330
View File
@@ -1,19 +1,5 @@
import { TAPi18n } from '/imports/i18n';
const subManager = new SubsManager();
Template.boardList.helpers({
currentSetting() {
return Settings.findOne();
},
hideCardCounterList() {
return Utils.isMiniScreen() && Session.get('currentBoard');
},
hideBoardMemberList() {
return Utils.isMiniScreen() && Session.get('currentBoard');
},
})
Template.boardListHeaderBar.events({
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
@@ -21,221 +7,30 @@ Template.boardListHeaderBar.events({
});
Template.boardListHeaderBar.helpers({
title() {
//if (FlowRouter.getRouteName() === 'template-container') {
// return 'template-container';
//} else {
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');
Meteor.subscribe('tableVisibilityModeSettings');
let currUser = Meteor.user();
let userLanguage;
if (currUser && currUser.profile) {
userLanguage = currUser.profile.language
}
if (userLanguage) {
TAPi18n.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 = Utils.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);
},
});
// Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => {
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$boards.sortable({
handle: '.board-handle',
});
}
});
},
userHasTeams() {
if (Meteor.user() != null && Meteor.user().teams && Meteor.user().teams.length > 0)
return true;
else
return false;
},
teamsDatas() {
if (Meteor.user().teams)
return Meteor.user().teams.sort((a, b) => a.teamDisplayName.localeCompare(b.teamDisplayName));
else
return [];
},
userHasOrgs() {
if (Meteor.user() != null && Meteor.user().orgs && Meteor.user().orgs.length > 0)
return true;
else
return false;
},
/*
userHasTemplates(){
if(Meteor.user() != null && Meteor.user().orgs && Meteor.user().orgs.length > 0)
return true;
else
return false;
},
*/
orgsDatas() {
if (Meteor.user().orgs)
return Meteor.user().orgs.sort((a, b) => a.orgDisplayName.localeCompare(b.orgDisplayName));
else
return [];
},
userHasOrgsOrTeams() {
let boolUserHasOrgs;
if (Meteor.user() != null && Meteor.user().orgs && Meteor.user().orgs.length > 0)
boolUserHasOrgs = true;
else
boolUserHasOrgs = false;
let boolUserHasTeams;
if (Meteor.user() != null && Meteor.user().teams && Meteor.user().teams.length > 0)
boolUserHasTeams = true;
else
boolUserHasTeams = false;
return (boolUserHasOrgs || boolUserHasTeams);
},
boards() {
let query = {
// { type: 'board' },
// { type: { $in: ['board','template-container'] } },
$and: [
{ archived: false },
{ type: { $in: ['board', 'template-container'] } },
{ $or: [] },
{ title: { $not: { $regex: /^\^.*\^$/ } } }
]
};
let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly');
if (FlowRouter.getRouteName() === 'home') {
query.$and[2].$or.push({ 'members.userId': Meteor.userId() });
if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue) {
query.$and.push({ 'permission': 'private' });
}
const currUser = Users.findOne(Meteor.userId());
// const currUser = Users.findOne(Meteor.userId(), {
// fields: {
// orgs: 1,
// teams: 1,
// },
// });
let orgIdsUserBelongs = currUser !== undefined && 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 !== undefined && 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 if (allowPrivateVisibilityOnly !== undefined && !allowPrivateVisibilityOnly.booleanValue) {
query = {
archived: false,
//type: { $in: ['board','template-container'] },
type: 'board',
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'] });
},
boardLists(boardId) {
let boardLists = [];
const lists = Lists.find({ 'boardId': boardId, 'archived': false },{sort: ['sort','asc']});
lists.forEach(list => {
let cardCount = Cards.find({ 'boardId': boardId, 'listId': list._id }).count()
boardLists.push(`${list.title}: ${cardCount}`);
});
return boardLists
},
boardMembers(boardId) {
let boardMembers = [];
const lists = Boards.findOne({ '_id': boardId })
let members = lists.members
members.forEach(member => {
boardMembers.push(member.userId);
});
return boardMembers
},
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);
@@ -253,124 +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) {
let title = getSlug(Boards.findOne(this.currentData()._id).title) || 'cloned-board';
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) {
console.error(err);
} else {
Session.set('fromBoard', null);
subManager.subscribe('board', res, false);
FlowRouter.go('board', {
id: res,
slug: title,
});
}
},
);
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');
}
});
},
'click #resetBtn'(event) {
let allBoards = document.getElementsByClassName("js-board");
let currBoard;
for (let i = 0; i < allBoards.length; i++) {
currBoard = allBoards[i];
currBoard.style.display = "block";
}
},
'click #filterBtn'(event) {
event.preventDefault();
let selectedTeams = document.querySelectorAll('#jsAllBoardTeams option:checked');
let selectedTeamsValues = Array.from(selectedTeams).map(function (elt) { return elt.value });
let index = selectedTeamsValues.indexOf("-1");
if (index > -1) {
selectedTeamsValues.splice(index, 1);
}
let selectedOrgs = document.querySelectorAll('#jsAllBoardOrgs option:checked');
let selectedOrgsValues = Array.from(selectedOrgs).map(function (elt) { return elt.value });
index = selectedOrgsValues.indexOf("-1");
if (index > -1) {
selectedOrgsValues.splice(index, 1);
}
if (selectedTeamsValues.length > 0 || selectedOrgsValues.length > 0) {
const query = {
$and: [
{ archived: false },
{ type: 'board' },
{ $or: [] }
]
};
if (selectedTeamsValues.length > 0) {
query.$and[2].$or.push({ 'teams.teamId': { $in: selectedTeamsValues } });
}
if (selectedOrgsValues.length > 0) {
query.$and[2].$or.push({ 'orgs.orgId': { $in: selectedOrgsValues } });
}
let filteredBoards = Boards.find(query, {}).fetch();
let allBoards = document.getElementsByClassName("js-board");
let currBoard;
if (filteredBoards.length > 0) {
let currBoardId;
let found;
for (let i = 0; i < allBoards.length; i++) {
currBoard = allBoards[i];
currBoardId = currBoard.classList[0];
found = filteredBoards.find(function (board) {
return board._id == currBoardId;
});
if (found !== undefined)
currBoard.style.display = "block";
else
currBoard.style.display = "none";
}
}
else {
for (let i = 0; i < allBoards.length; i++) {
currBoard = allBoards[i];
currBoard.style.display = "none";
}
}
}
},
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');
+197
View File
@@ -0,0 +1,197 @@
@import 'nib'
$spaceBetweenTiles = 16px
.board-list
margin: 0 ($spaceBetweenTiles/2)
li
float: left
width: 25%
box-sizing: border-box
position: relative
&.starred
.fa-star,
.fa-star-o
opacity: 1
.board-list-item
overflow: hidden;
background-color: #999
color: #f6f6f6
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
&.tile
background-size: auto
background-repeat: repeat
.board-list-item-sub-name
color: rgba(255, 255, 255, .5)
display: block
font-size: 14px
font-weight: 400
line-height: 22px
.board-list-item-desc
color: #fff
display: block
font-size: 14px
font-weight: 400
line-height: 18px
.js-add-board
text-align:center
.label
font-weight: normal
line-height:90px
:hover
background-color:#939393
.fa-star,
.fa-star-o
bottom: 0
font-size: 14px
height: 18px
line-height: 18px
opacity: 0
padding: 9px 9px
position: absolute
right: 0
top: 0
transition-duration: .15s
transition-property: color, font-size, background
.fa-circle
bottom: 0;
font-size: 10px;
height: 10px;
line-height: 10px;
padding: 9px 9px;
position: absolute;
right: 0;
transition-duration: .15s
transition-property: color, font-size, background
.has-overtime-card-active
color: #eb4646 !important
.no-overtime-card-active
color: #3cb500 !important
.is-star-active
color: white
.fa-clone
position: absolute;
bottom: 0
font-size: 14px
height: 18px
line-height: 18px
opacity: 0
right: 0
padding: 9px 9px
transition-duration: .15s
transition-property: color, font-size, background
.fa-archive
position: absolute;
bottom: 0
font-size: 14px
height: 18px
line-height: 18px
opacity: 0
left: 0
padding: 9px 9px
transition-duration: .15s
transition-property: color, font-size, background
li:hover a
&:hover
.fa-star,
.fa-clone,
.fa-archive,
.fa-star-o
color: white
.fa-star,
.fa-clone,
.fa-archive,
.fa-star-o
color: white
opacity: .75
&:hover
font-size: 18px
opacity: 1
&.is-star-active
opacity: 1
.board-backgrounds-list
.board-background-select
box-sizing: border-box
display: block
float: left
width: 50%
padding-top: 12px
position: relative
z-index: 1
&:nth-child(-n + 2)
padding-top: 0
&:nth-child(2n)
padding-left: 6px
&:nth-child(2n+1)
padding-right: 6px
.background-box
border-radius: 3px
background-size: cover
display: block
height: 74px
position: relative
width: 100%
cursor: pointer
display: flex
align-items: center
justify-content: center
i.fa-check
font-size: 25px
color: white
@media screen and (max-width: 800px)
.board-list
height: 100%
overflow: scroll
li
width: 33.3%
.board-list-item
overflow: hidden
.board-list-item-sub-name
position: relative
top: -100px
left: -100px
@media screen and (max-width: 360px)
li
width: 50%
-85
View File
@@ -1,85 +0,0 @@
.attachment-upload {
text-align: center;
font-weight: bold;
}
.attachments-galery {
display: flex;
flex-wrap: wrap;
}
.attachments-galery .attachment-item {
width: 31.33%;
margin: 10px 1% 0;
text-align: center;
border-radius: 3px;
overflow: auto;
background: #ededed;
min-height: 120px;
}
.attachments-galery .attachment-item:hover {
background: #e0e0e0;
}
.attachments-galery .attachment-item.add-attachment {
display: flex;
align-items: center;
}
.attachments-galery .attachment-item.add-attachment a {
display: block;
margin: auto;
}
.attachments-galery .attachment-item .attachment-thumbnail {
height: 80px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.attachments-galery .attachment-item .attachment-thumbnail .attachment-thumbnail-img {
max-height: 100%;
max-width: 100%;
}
.attachments-galery .attachment-item .attachment-thumbnail .attachment-thumbnail-ext {
text-transform: uppercase;
font-size: 1.6em;
}
.attachments-galery .attachment-item .attachment-details {
font-size: 0.75em;
margin: 3px;
}
.attachments-galery .attachment-item .attachment-details .attachment-details-actions a {
display: block;
}
.attachments-galery .attachment-item .attachment-details .attachment-details-actions a.attachment-details-menu {
padding-top: 10px;
}
.attachment-image-preview {
max-width: 100px;
display: block;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.preview-clipboard-image {
width: 280px;
max-width: 100%;
height: 200px;
display: block;
border: 1px solid #000;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
@media screen and (max-width: 800px) {
.attachments-galery .attachment-item {
width: 48%;
}
.attachments-galery .attachment-item .attachment-thumbnail {
height: 130px;
}
.attachments-galery .attachment-item .attachment-details {
font-size: 1.1em;
}
}
@media screen and (max-width: 360px) {
.attachments-galery .attachment-item {
width: 100%;
}
.attachments-galery .attachment-item .attachment-thumbnail {
height: 200px;
}
}
+27 -88
View File
@@ -1,31 +1,19 @@
template(name="cardAttachmentsPopup")
if $gt uploads.length 0
.attachment-upload {{_ 'uploading'}}
table
tr
th.upload-file-name-descr {{_ 'name'}}
th.upload-progress-descr {{_ 'progress'}}
th.upload-remaining-descr {{_ 'remaining_time'}}
th.upload-speed-descr {{_ 'speed'}}
each upload in uploads
tr
td.upload-file-name-value {{upload.file.name}}
td.upload-progress-value {{upload.progress.get}}%
td.upload-remaining-value {{getEstimateTime upload}}
td.upload-speed-value {{getEstimateSpeed upload}}
else
ul.pop-over-list
li
input.js-attach-file.hide(type="file" name="file" multiple)
a.js-computer-upload {{_ 'computer'}}
li
a.js-upload-clipboard-image {{_ 'clipboard'}}
ul.pop-over-list
li
input.js-attach-file.hide(type="file" name="file" multiple)
a.js-computer-upload {{_ 'computer'}}
li
a.js-upload-clipboard-image {{_ 'clipboard'}}
template(name="previewClipboardImagePopup")
p <kbd>Ctrl</kbd>+<kbd>V</kbd> {{_ "paste-or-dragdrop"}}
img.preview-clipboard-image()
button.primary.js-upload-pasted-image {{_ 'upload'}}
template(name="previewAttachedImagePopup")
img.preview-large-image.js-large-image-clicked(src="{{url}}")
template(name="attachmentDeletePopup")
p {{_ "attachment-delete-pop"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
@@ -34,81 +22,32 @@ template(name="attachmentsGalery")
.attachments-galery
each attachments
.attachment-item
a.attachment-thumbnail.swipebox(href="{{link}}" title="{{name}}")
if link
a.attachment-thumbnail.swipebox(href="{{url}}" title="{{name}}")
if isUploaded
if isImage
img.attachment-thumbnail-img(src="{{link}}")
else if($eq extension 'mp3')
video(width="100%" height="100%" controls="true")
source(src="{{link}}" type="audio/mpeg")
else if($eq extension 'ogg')
video(width="100%" height="100%" controls="true")
source(src="{{link}}" type="video/ogg")
else if($eq extension 'webm')
video(width="100%" height="100%" controls="true")
source(src="{{link}}" type="video/webm")
else if($eq extension 'mp4')
video(width="100%" height="100%" controls="true")
source(src="{{link}}" type="video/mp4")
img.attachment-thumbnail-img(src="{{url}}")
else
span.attachment-thumbnail-ext= extension
else
+spinner
p.attachment-details
= name
span.file-size ({{fileSize size}})
span.attachment-details-actions
a.js-download(href="{{link}}?download=true", download="{{name}}")
a.js-download(href="{{url download=true}}")
i.fa.fa-download
| {{_ 'download'}}
if currentUser.isBoardMember
unless currentUser.isCommentOnly
unless currentUser.isWorker
a.fa.fa-navicon.attachment-details-menu.js-open-attachment-menu(title="{{_ 'attachmentActionsPopup-title'}}")
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
template(name="attachmentActionsPopup")
ul.pop-over-list
li
if isImage
a(class="{{#if isCover}}js-remove-cover{{else}}js-add-cover{{/if}}")
i.fa.fa-thumb-tack
if isCover
| {{_ 'remove-cover'}}
else
| {{_ 'add-cover'}}
if currentUser.isBoardAdmin
a.js-rename
i.fa.fa-pencil-square-o
| {{_ 'rename'}}
a.js-confirm-delete
i.fa.fa-close
| {{_ 'delete'}}
p.attachment-storage
| {{versions.original.storage}}
if $neq versions.original.storage "fs"
a.js-move-storage-fs
i.fa.fa-arrow-right
| {{_ 'attachment-move-storage-fs'}}
if $neq versions.original.storage "gridfs"
if versions.original.storage
a.js-move-storage-gridfs
i.fa.fa-arrow-right
| {{_ 'attachment-move-storage-gridfs'}}
if $neq versions.original.storage "s3"
if versions.original.storage
a.js-move-storage-s3
i.fa.fa-arrow-right
| {{_ 'attachment-move-storage-s3'}}
template(name="attachmentRenamePopup")
input.js-edit-attachment-name(type='text' autofocus value="{{getNameWithoutExtension}}" dir="auto")
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-attachment-name(type="submit") {{_ 'save'}}
li.attachment-item.add-attachment
a.js-add-attachment {{_ 'add-attachment' }}
+96 -191
View File
@@ -1,229 +1,134 @@
import { ObjectID } from 'bson';
const filesize = require('filesize');
const prettyMilliseconds = require('pretty-ms');
Template.attachmentsGalery.events({
'click .js-add-attachment': Popup.open('cardAttachments'),
'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.
'click .js-download'(event) {
event.stopPropagation();
},
'click .js-open-attachment-menu': Popup.open('attachmentActions'),
});
Template.attachmentsGalery.helpers({
isBoardAdmin() {
return Meteor.user().isBoardAdmin();
'click .js-add-cover'() {
Cards.findOne(this.cardId).setCover(this._id);
},
fileSize(size) {
const ret = filesize(size);
return ret;
'click .js-remove-cover'() {
Cards.findOne(this.cardId).unsetCover();
},
'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.
// here make sure to get correct size when this img fully loaded.
const img = $('img.preview-large-image')[0];
if (!img) return;
const rePosPopup = () => {
const w = img.width;
const h = img.height;
// if the image is too large, we resize & center the popup.
if (w > 300) {
$('div.pop-over').css({
width: (w + 20),
position: 'absolute',
left: (window.innerWidth - w)/2,
top: (window.innerHeight - h)/2,
});
}
};
const url = $(evt.currentTarget).attr('src');
if (img.src === url && img.complete)
rePosPopup();
else
img.onload = rePosPopup;
},
});
Template.cardAttachmentsPopup.onCreated(function() {
this.uploads = new ReactiveVar([]);
});
Template.cardAttachmentsPopup.helpers({
getEstimateTime(upload) {
const ret = prettyMilliseconds(upload.estimateTime.get());
return ret;
Template.previewAttachedImagePopup.events({
'click .js-large-image-clicked'(){
Popup.close();
},
getEstimateSpeed(upload) {
const ret = filesize(upload.estimateSpeed.get(), {round: 0}) + "/s";
return ret;
},
uploads() {
return Template.instance().uploads.get();
}
});
Template.cardAttachmentsPopup.events({
'change .js-attach-file'(event, templateInstance) {
'change .js-attach-file'(evt) {
const card = this;
const files = event.currentTarget.files;
if (files) {
let uploads = [];
for (const file of files) {
const fileId = new ObjectID().toString();
const config = {
file: file,
fileId: fileId,
meta: Utils.getCommonAttachmentMetaFrom(card),
chunkSize: 'dynamic',
};
config.meta.fileId = fileId;
const uploader = Attachments.insert(
config,
false,
);
uploader.on('start', function() {
uploads.push(this);
templateInstance.uploads.set(uploads);
});
uploader.on('uploaded', (error, fileRef) => {
if (!error) {
if (fileRef.isImage) {
card.setCover(fileRef._id);
}
}
});
uploader.on('end', (error, fileRef) => {
uploads = uploads.filter(_upload => _upload.config.fileId != fileRef._id);
templateInstance.uploads.set(uploads);
if (uploads.length == 0 ) {
Popup.back();
}
});
uploader.start();
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 {
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 card = this;
if (pastedResults && pastedResults.file) {
const file = pastedResults.file;
window.oPasted = pastedResults;
const fileId = new ObjectID().toString();
const config = {
file,
fileId: fileId,
meta: Utils.getCommonAttachmentMetaFrom(card),
fileName: file.name || file.type.replace('image/', 'clipboard.'),
chunkSize: 'dynamic',
};
config.meta.fileId = fileId;
const uploader = Attachments.insert(
config,
false,
);
uploader.on('uploaded', (error, fileRef) => {
if (!error) {
if (fileRef.isImage) {
card.setCover(fileRef._id);
}
const results = pastedResults;
if (results && results.file) {
const card = this;
const file = new FS.File(results.file);
if (!results.name) {
// if no filename, it's from clipboard. then we give it a name, with ext name from MIME type
if (typeof results.file.type === 'string') {
file.name(results.file.type.replace('image/', 'clipboard.'));
}
});
uploader.on('end', (error, fileRef) => {
pastedResults = null;
$(document.body).pasteImageReader(() => {});
Popup.back();
});
uploader.start();
}
file.updatedAt(new Date());
file.boardId = card.boardId;
file.cardId = card._id;
file.userId = Meteor.userId();
const attachment = Attachments.insert(file);
if (attachment && attachment._id && attachment.isImage()) {
card.setCover(attachment._id);
}
pastedResults = null;
$(document.body).pasteImageReader(() => {});
Popup.close();
}
},
});
BlazeComponent.extendComponent({
isCover() {
const ret = Cards.findOne(this.data().meta.cardId).coverId == this.data()._id;
return ret;
},
events() {
return [
{
'click .js-rename': Popup.open('attachmentRename'),
'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', function() {
Attachments.remove(this._id);
Popup.back(2);
}),
'click .js-add-cover'() {
Cards.findOne(this.data().meta.cardId).setCover(this.data()._id);
Popup.back();
},
'click .js-remove-cover'() {
Cards.findOne(this.data().meta.cardId).unsetCover();
Popup.back();
},
'click .js-move-storage-fs'() {
Meteor.call('moveAttachmentToStorage', this.data()._id, "fs");
Popup.back();
},
'click .js-move-storage-gridfs'() {
Meteor.call('moveAttachmentToStorage', this.data()._id, "gridfs");
Popup.back();
},
'click .js-move-storage-s3'() {
Meteor.call('moveAttachmentToStorage', this.data()._id, "s3");
Popup.back();
},
}
]
}
}).register('attachmentActionsPopup');
BlazeComponent.extendComponent({
getNameWithoutExtension() {
const ret = this.data().name.replace(new RegExp("\." + this.data().extension + "$"), "");
return ret;
},
events() {
return [
{
'keydown input.js-edit-attachment-name'(evt) {
// enter = save
if (evt.keyCode === 13) {
this.find('button[type=submit]').click();
}
},
'click button.js-submit-edit-attachment-name'(event) {
// save button pressed
event.preventDefault();
const name = this.$('.js-edit-attachment-name')[0]
.value
.trim() + this.data().extensionWithDot;
Meteor.call('renameAttachment', this.data()._id, name);
Popup.back(2);
},
}
]
}
}).register('attachmentRenamePopup');
+85
View File
@@ -0,0 +1,85 @@
@import 'nib'
.attachments-galery
display: flex
flex-wrap: wrap
.attachment-item
width: 33.33% - 2%
margin: 10px 1% 0
text-align: center
border-radius: 3px
overflow: hidden
background: darken(white, 7%)
min-height: 120px
&:hover
background: darken(white, 12%)
&.add-attachment
display: flex
align-items: center
a
display: block
margin: auto
.attachment-thumbnail
height: 80px
display: flex
align-items: center
justify-content: center
position: relative
.attachment-thumbnail-img
max-height: 100%
max-width: 100%
.attachment-thumbnail-ext
text-transform: uppercase
font-size: 1.6em
.attachment-details
font-size: 0.75em
margin: 3px
.attachment-details-actions a
display: block
.attachment-image-preview
max-width: 100px
display: block
box-shadow: 0 1px 2px rgba(0,0,0,.2)
.preview-large-image
max-width: 1000px
display: block
box-shadow: 0 1px 2px rgba(0,0,0,.2)
.preview-clipboard-image
width: 280px
max-width: 100%;
height: 200px
display: block
border: 1px solid black
box-shadow: 0 1px 2px rgba(0,0,0,.2)
@media screen and (max-width: 800px)
.attachments-galery
flex-direction
row
.attachment-item
width: 50% - 2%
.attachment-thumbnail
height: 130px
.attachment-details
font-size: 1.1em
@media screen and (max-width: 360px)
.attachments-galery
.attachment-item
width: 100%
.attachment-thumbnail
height: 200px
+9 -74
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,53 +45,16 @@ 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 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
= formattedValue
else
| {{_ 'edit'}}
else
if value
= formattedValue
template(name="cardCustomField-date")
if canModifyCard
a.js-edit-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
if value
div.card-date
time(datetime="{{showISODate}}")
| {{showDate}}
b
| {{showWeek}}
else
| {{_ 'edit'}}
else
if value
div.card-date
time(datetime="{{showISODate}}")
| {{showDate}}
b
| {{showWeek}}
a.js-edit-date(title="{{showTitle}}" class="{{classes}}")
if value
div.card-date
time(datetime="{{showISODate}}")
| {{showDate}}
else
| {{_ 'edit'}}
template(name="cardCustomField-dropdown")
if canModifyCard
@@ -119,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
+46 -199
View File
@@ -1,143 +1,85 @@
import moment from 'moment/min/moment-with-locales';
import { TAPi18n } from '/imports/i18n';
import { DatePicker } from '/client/lib/datepicker';
import Cards from '/models/cards';
import { CustomFieldStringTemplate } from '/client/lib/customFields'
Template.cardCustomFieldsPopup.helpers({
hasCustomField() {
const card = Utils.getCurrentCard();
const card = Cards.findOne(Session.get('currentCard'));
const customFieldId = this._id;
return card.customFieldIndex(customFieldId) > -1;
},
});
Template.cardCustomFieldsPopup.events({
'click .js-select-field'(event) {
const card = Utils.getCurrentCard();
'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}`;
},
onCreated() {
const self = this;
self.card = Utils.getCurrentCard();
self.card = Cards.findOne(Session.get('currentCard'));
self.customFieldId = this.data()._id;
},
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;
@@ -152,10 +94,6 @@ CardCustomField.register('cardCustomField');
});
}
showWeek() {
return this.date.get().week().toString();
}
showDate() {
// this will start working once mquandalle:moment
// is updated to at least moment.js 2.10.5
@@ -170,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 '';
@@ -184,20 +120,19 @@ 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 {
onCreated() {
super.onCreated();
const self = this;
self.card = Utils.getCurrentCard();
self.card = Cards.findOne(Session.get('currentCard'));
self.customFieldId = this.data()._id;
this.data().value && this.date.set(moment(this.data().value));
}
@@ -209,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;
@@ -224,109 +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 CardCustomFieldStringTemplate extends CardCustomField {
onCreated() {
super.onCreated();
this.customField = new CustomFieldStringTemplate(this.data().definition);
this.stringtemplateItems = new ReactiveVar(this.data().value ?? []);
}];
}
formattedValue() {
const ret = this.customField.getFormattedValue(this.data().value);
return ret;
}
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.stringtemplateItems.get();
this.card.setCustomField(this.customFieldId, items);
},
'keydown .js-card-customfield-stringtemplate-item'(event) {
if (event.keyCode === 13) {
event.preventDefault();
if (event.target.value.trim() || event.metaKey || event.ctrlKey) {
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);
}
if (event.metaKey || event.ctrlKey) {
this.find('button[type=submit]').click();
}
}
},
'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 ?? []);
},
},
];
}
}
CardCustomFieldStringTemplate.register('cardCustomField-stringtemplate');
}).register('cardCustomField-dropdown');
-67
View File
@@ -1,67 +0,0 @@
.card-date {
display: block;
border-radius: 4px;
padding: 1px 3px;
background-color: #dbdbdb;
}
.card-date:hover,
.card-date.is-active {
background-color: #b3b3b3;
}
.card-date.current,
.card-date.almost-due,
.card-date.due,
.card-date.long-overdue {
color: #fff;
}
.card-date.current {
background-color: #5ba639;
}
.card-date.current:hover,
.card-date.current.is-active {
background-color: #46802c;
}
.card-date.almost-due {
background-color: #edc909;
}
.card-date.almost-due:hover,
.card-date.almost-due.is-active {
background-color: #bc9f07;
}
.card-date.due {
background-color: #fa3f00;
}
.card-date.due:hover,
.card-date.due.is-active {
background-color: #c73200;
}
.card-date.long-overdue {
background-color: #fd5d47;
}
.card-date.long-overdue:hover,
.card-date.long-overdue.is-active {
background-color: #fd3e24;
}
.card-date.end-date time::before {
content: "\f253";
}
.card-date.due-date time::before {
content: "\f090";
}
.card-date.start-date time::before {
content: "\f251";
}
.card-date.received-date time::before {
content: "\f08b";
}
.card-date time::before {
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
-webkit-font-smoothing: antialiased;
margin-right: 0.3em;
}
.customfield-date {
display: block;
border-radius: 4px;
padding: 1px 3px;
}
+2 -12
View File
@@ -1,20 +1,10 @@
template(name="dateBadge")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
a.js-edit-date.card-date(title="{{showTitle}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
a.card-date(title="{{showTitle}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
b
| {{showWeek}}
template(name="dateCustomField")
a(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
b
| {{showWeek}}
+140 -159
View File
@@ -1,24 +1,102 @@
import moment from 'moment/min/moment-with-locales';
import { TAPi18n } from '/imports/i18n';
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,24 +104,22 @@ Template.dateBadge.helpers({
}
_deleteDate() {
this.card.unsetReceived();
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,14 +128,14 @@ Template.dateBadge.helpers({
}
_deleteDate() {
this.card.unsetStart();
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,14 +151,14 @@ Template.dateBadge.helpers({
}
_deleteDate() {
this.card.unsetDue();
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,9 +174,10 @@ Template.dateBadge.helpers({
}
_deleteDate() {
this.card.unsetEnd();
this.card.setEnd(null);
}
}.register('editCardEndDatePopup'));
}).register('editCardEndDatePopup');
// Display received, start, due & end dates
const CardDate = BlazeComponent.extendComponent({
@@ -117,10 +194,6 @@ const CardDate = BlazeComponent.extendComponent({
}, 60000);
},
showWeek() {
return this.date.get().week().toString();
},
showDate() {
// this will start working once mquandalle:moment
// is updated to at least moment.js 2.10.5
@@ -151,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() {
@@ -191,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;
}
@@ -225,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;
}
@@ -259,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;
}
@@ -277,126 +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));
});
}
showWeek() {
return this.date.get().week().toString();
}
showDate() {
// this will start working once mquandalle:moment
// is updated to at least moment.js 2.10.5
// until then, the date is displayed in the "L" format
return this.date.get().calendar(null, {
sameElse: 'llll',
});
}
showTitle() {
return `${this.date.get().format('LLLL')}`;
}
classes() {
return 'customfield-date';
}
events() {
return [];
}
}
CardCustomFieldDate.register('cardCustomFieldDate');
(class extends CardReceivedDate {
showDate() {
return this.date.get().format('L');
return this.date.get().format('l');
}
}.register('minicardReceivedDate'));
}).register('minicardReceivedDate');
(class extends CardStartDate {
showDate() {
return this.date.get().format('L');
return this.date.get().format('l');
}
}.register('minicardStartDate'));
}).register('minicardStartDate');
(class extends CardDueDate {
showDate() {
return this.date.get().format('L');
return this.date.get().format('l');
}
}.register('minicardDueDate'));
}).register('minicardDueDate');
(class extends CardEndDate {
showDate() {
return this.date.get().format('L');
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');
+59
View File
@@ -0,0 +1,59 @@
.card-date
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
&.current
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
background-color: darken(#fd5d47, 7)
&.end-date
time
&::before
content: "\f253" // symbol: fa-hourglass-end
&.due-date
time
&::before
content: "\f090" // symbol: fa-sign-in
&.start-date
time
&::before
content: "\f251" // symbol: fa-hourglass-start
&.received-date
time
&::before
content: "\f08b" // symbol: fa-sign-out
time
&::before
font: normal normal normal 14px/1 FontAwesome
font-size: inherit
-webkit-font-smoothing: antialiased
margin-right: 0.3em
@@ -1,56 +0,0 @@
.new-description {
position: relative;
margin: 0 0 20px 0;
}
.new-description.is-open .helper {
display: inline-block;
}
.new-description.is-open textarea {
min-height: 100px;
color: #4d4d4d;
cursor: auto;
overflow: hidden;
word-wrap: break-word;
}
.new-description .too-long {
margin-top: 8px;
}
.new-description textarea {
background-color: #fff;
border: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
height: 36px;
margin: 4px 4px 6px 0;
padding: 9px 11px;
width: 100%;
}
.new-description textarea:hover,
.new-description textarea:is-open {
background-color: #fff;
box-shadow: 0 1px 3px rgba(0,0,0,0.33);
border: 0;
cursor: pointer;
}
.new-description textarea:is-open {
cursor: auto;
}
.description-item {
background-color: #fff;
border: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
color: #8c8c8c;
height: 36px;
margin: 4px 4px 6px 0;
width: 92%;
}
.description-item:hover {
background: #e0e0e0;
}
.description-item.add-description {
display: flex;
margin: 5px;
}
.description-item.add-description a {
display: block;
margin: auto;
}
@@ -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,37 +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)) {
const submitButton = this.find('button[type=submit]');
if (submitButton) {
submitButton.click();
}
}
},
},
];
},
}).register('descriptionForm');
-588
View File
@@ -1,588 +0,0 @@
.assignee {
border-radius: 3px;
display: block;
position: relative;
float: left;
height: 30px;
width: 30px;
margin: 0 4px 4px 0;
cursor: pointer;
user-select: none;
z-index: 1;
text-decoration: none;
border-radius: 50%;
}
.assignee .avatar {
overflow: hidden;
border-radius: 50%;
}
.assignee .avatar.avatar-assignee-initials {
height: 70%;
width: 70%;
padding: 15%;
background-color: #dbdbdb;
color: #444;
position: absolute;
}
.assignee .avatar.avatar-image {
object-fit: cover;
object-position: center;
height: 100%;
width: 100%;
}
.assignee .assignee-presence-status {
background-color: #b3b3b3;
border: 1px solid #fff;
border-radius: 50%;
height: 7px;
width: 7px;
position: absolute;
right: -1px;
bottom: -1px;
border: 1px solid #fff;
z-index: 15;
}
.assignee .assignee-presence-status.active {
background: #64c464;
border-color: #daf1da;
}
.assignee .assignee-presence-status.idle {
background: #e4e467;
border-color: #f7f7d4;
}
.assignee .assignee-presence-status.disconnected {
background: #bdbdbd;
border-color: #ededed;
}
.assignee .assignee-presence-status.pending {
background: #e44242;
border-color: #f1dada;
}
.assignee.add-assignee {
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 0 2px #bfbfbf inset;
}
.assignee.add-assignee:hover,
.assignee.add-assignee.is-active {
box-shadow: 0 0 0 2px #666 inset;
}
.copied-tooltip {
display: none;
padding: 0px 10px;
background-color: rgba(0,0,0,0.875);
color: #fff;
border-radius: 5px;
}
.card-details {
padding: 0;
flex-shrink: 0;
flex-basis: 600px;
will-change: flex-basis;
overflow-y: scroll;
overflow-x: hidden;
background: #f7f7f7;
border-radius: bottom 3px;
z-index: 20;
animation: flexGrowIn 0.1s;
box-shadow: 0 0 7px 0 #b3b3b3;
transition: flex-basis 0.1s;
box-sizing: border-box;
}
.card-details .mCustomScrollBox {
padding-left: 0;
}
.card-details .card-details-canvas {
width: auto;
padding: 0 20px;
}
.card-details .card-details-header {
margin: 0 -20px 5px;
padding: 7px 20px;
background: #ededed;
border-bottom: 1px solid #dbdbdb;
position: sticky;
top: 0px;
z-index: 500;
}
.card-details .card-details-header .card-number {
color: #b3b3b3;
display: inline-block;
margin-right: 5px;
}
.card-details .card-details-header .close-card-details,
.card-details .card-details-header .maximize-card-details,
.card-details .card-details-header .minimize-card-details,
.card-details .card-details-header .card-details-menu,
.card-details .card-details-header .card-copy-button,
.card-details .card-details-header .card-copy-mobile-button,
.card-details .card-details-header .close-card-details-mobile-web,
.card-details .card-details-header .card-details-menu-mobile-web,
.card-details .card-details-header .copied-tooltip {
float: right;
}
.card-details .card-details-header .close-card-details,
.card-details .card-details-header .maximize-card-details,
.card-details .card-details-header .minimize-card-details {
font-size: 24px;
padding: 5px 10px 5px 10px;
margin-right: -8px;
}
.card-details .card-details-header .close-card-details-mobile-web {
font-size: 24px;
padding: 5px;
margin-right: 40px;
}
.card-details .card-details-header .card-copy-button {
font-size: 17px;
padding: 10px;
margin-right: 10px;
}
.card-details .card-details-header .card-copy-mobile-button {
font-size: 17px;
padding: 10px;
margin-right: 10px;
}
.card-details .card-details-header .card-details-menu {
font-size: 17px;
padding: 10px;
}
.card-details .card-details-header .card-details-menu-mobile-web {
font-size: 17px;
padding: 10px;
margin-right: 30px;
}
.card-details .card-details-header .card-details-watch {
font-size: 17px;
padding-left: 7px;
color: #a6a6a6;
}
.card-details .card-details-header .card-details-title {
font-weight: bold;
font-size: 1.33em;
margin: 7px 0 0;
padding: 0;
}
.card-details .card-details-header .linked-card-location {
font-style: italic;
font-size: 1em;
margin-bottom: 0;
}
.card-details .card-details-header .linked-card-location p {
margin-bottom: 0;
}
.card-details .card-details-header form.inlined-form {
margin-top: 5px;
margin-bottom: 10px;
}
.card-details .card-details-header form.inlined-form .copied-tooltip {
padding: 0px 10px;
}
.card-details .card-details-header .card-details-list {
font-size: 0.85em;
margin-bottom: 3px;
}
.card-details .card-details-header .card-details-list a.card-details-list-title {
font-weight: bold;
}
.card-details .card-details-header .card-details-list a.card-details-list-title.is-editable {
display: inline-block;
background: #e6e6e6;
border-radius: 3px;
padding: 0px 5px;
}
.card-details .card-details-header .copied-tooltip {
margin-right: 10px;
padding: 10px;
}
.card-details .card-description textarea {
min-height: 100px;
}
.card-details .card-details-items {
display: flex;
flex-wrap: wrap;
margin: 15px 0;
}
.card-details .card-details-items .card-details-item {
margin-right: 0.5em;
flex-grow: 1;
}
.card-details .card-details-items .card-details-item:last-child {
margin-right: 0;
}
.card-details .card-details-items .card-details-item.card-details-item-labels {
display: block;
word-wrap: break-word;
max-width: 95%;
}
.card-details .card-details-items .card-details-item.card-details-item-members,
.card-details .card-details-items .card-details-item.card-details-item-assignees,
.card-details .card-details-items .card-details-item.card-details-item-customfield,
.card-details .card-details-items .card-details-item.card-details-item-name {
display: block;
word-wrap: break-word;
max-width: 36%;
}
.card-details .card-details-items .card-details-item.card-details-item-creator,
.card-details .card-details-items .card-details-item.card-details-item-received,
.card-details .card-details-items .card-details-item.card-details-item-start,
.card-details .card-details-items .card-details-item.card-details-item-due,
.card-details .card-details-items .card-details-item.card-details-item-end {
display: block;
word-wrap: break-word;
max-width: 28%;
}
.card-details .card-details-items .card-details-item.custom-fields {
padding-left: 10px;
}
.card-details .card-details-item-title {
font-size: 16px;
font-weight: bold;
color: #4d4d4d;
}
.card-details .activities {
padding-top: 10px;
}
@media screen and (min-width: 801px) {
.card-details-maximized {
padding: 0;
flex-shrink: 0;
flex-basis: calc(100% - 20px);
will-change: flex-basis;
overflow-y: scroll;
overflow-x: scroll;
background: #f7f7f7;
border-radius: bottom 3px;
z-index: 100;
animation: flexGrowIn 0.1s;
box-shadow: 0 0 7px 0 #b3b3b3;
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-maximized .card-details-left {
float: left;
top: 60px;
left: 20px;
width: 47%;
border-right: solid 2px #dbdbdb;
padding-right: 10px;
}
.card-details-maximized .card-details-right {
position: absolute;
float: right;
left: 50%;
margin: 15px 0;
}
.card-details-maximized .card-details-header {
width: 100%;
}
}
input[type="text"].attachment-add-link-input {
float: left;
margin: 0 0 8px;
width: 80%;
}
input[type="submit"].attachment-add-link-submit {
float: left;
margin: 0 0 8px 4px;
padding: 6px 12px;
width: 18%;
}
@media screen and (max-width: 800px) {
.card-details {
width: calc(100% - 1px);
padding: 0px 20px 0px 20px;
margin: 0px;
transition: none;
overflow-y: revert;
overflow-x: revert;
}
.card-details .card-details-canvas {
width: 100%;
padding-left: 0px;
}
.card-details .card-details-header .close-card-details {
margin-right: 0px;
}
.card-details .card-details-header .card-details-menu {
margin-right: 40px;
}
.card-details .card-details-header .maximize-card-details {
margin-right: 40px;
}
.card-details .card-details-header .minimize-card-details {
margin-right: 40px;
}
.card-details-popup {
padding: 0px 10px;
}
.pop-over > .content-wrapper > .popup-container-depth-0 {
width: 100%;
}
.pop-over > .content-wrapper > .popup-container-depth-0 > .content {
width: calc(100% - 10px);
}
.pop-over > .content-wrapper > .popup-container-depth-0 > .content > .card-details-popup hr {
margin: 15px 0px;
}
.pop-over > .content-wrapper > .popup-container-depth-0 .card-details-header {
margin: 0;
}
}
.card-details-white {
background: #fff !important;
color: #000 !important;
border: 1px solid #eee;
}
.card-details-green {
background: #3cb500 !important;
color: #fff !important;
}
.card-details-yellow {
background: #fad900 !important;
color: #000 !important;
}
.card-details-orange {
background: #ff9f19 !important;
color: #000 !important;
}
.card-details-red {
background: #eb4646 !important;
color: #fff !important;
}
.card-details-purple {
background: #a632db !important;
color: #fff !important;
}
.card-details-blue {
background: #0079bf !important;
color: #fff !important;
}
.card-details-pink {
background: #ff78cb !important;
color: #000 !important;
}
.card-details-sky {
background: #00c2e0 !important;
color: #fff !important;
}
.card-details-black {
background: #4d4d4d !important;
color: #fff !important;
}
.card-details-lime {
background: #51e898 !important;
color: #000 !important;
}
.card-details-silver {
background: #c0c0c0 !important;
color: #000 !important;
}
.card-details-peachpuff {
background: #ffdab9 !important;
color: #000 !important;
}
.card-details-crimson {
background: #dc143c !important;
color: #fff !important;
}
.card-details-plum {
background: #dda0dd !important;
color: #000 !important;
}
.card-details-darkgreen {
background: #006400 !important;
color: #fff !important;
}
.card-details-slateblue {
background: #6a5acd !important;
color: #fff !important;
}
.card-details-magenta {
background: #f0f !important;
color: #fff !important;
}
.card-details-gold {
background: #ffd700 !important;
color: #000 !important;
}
.card-details-navy {
background: #000080 !important;
color: #fff !important;
}
.card-details-gray {
background: #808080 !important;
color: #fff !important;
}
.card-details-saddlebrown {
background: #8b4513 !important;
color: #fff !important;
}
.card-details-paleturquoise {
background: #afeeee !important;
color: #000 !important;
}
.card-details-mistyrose {
background: #ffe4e1 !important;
color: #000 !important;
}
.card-details-indigo {
background: #4b0082 !important;
color: #fff !important;
}
.voted {
opacity: 0.7;
}
.vote-title {
display: flex;
justify-content: space-between;
}
.vote-title .js-edit-date {
align-self: baseline;
margin-left: 5px;
}
.vote-result {
display: flex;
}
.js-show-positive-votes {
cursor: pointer;
}
.poker-voted {
opacity: 0.7;
}
.poker-title {
display: flex;
justify-content: space-between;
}
.poker-title .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: 0.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;
}
.estimation-add input {
display: inline-block;
float: right;
margin: auto;
margin-right: 10px;
width: 100px;
}
.estimation-add 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 #aaa;
text-align: center;
position: relative;
cursor: pointer;
}
.poker-card .inner {
display: table-cell;
vertical-align: middle;
border-radius: 5px;
overflow: hidden;
background-color: #cecece;
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+229
View File
@@ -0,0 +1,229 @@
@import 'nib'
.card-details
padding: 0
flex-shrink: 0
flex-basis: 510px
will-change: flex-basis
overflow-y: scroll
overflow-x: hidden
background: darken(white, 3%)
border-radius: bottom 3px
z-index: 20 !important
animation: flexGrowIn 0.1s
box-shadow: 0 0 7px 0 darken(white, 30%)
transition: flex-basis 0.1s
.mCustomScrollBox
padding-left: 0
.ps-scrollbar-y-rail
pointer-event: all
position: absolute;
.card-details-canvas
width: 470px
padding-left: 20px;
.card-details-header
margin: 0 -20px 5px
padding 7px 16px
background: darken(white, 7%)
border-bottom: 1px solid darken(white, 14%)
.close-card-details,
.card-details-menu
float: right
.close-card-details
font-size: 24px
padding: 5px
margin-right: -8px
.card-details-menu
font-size: 17px
padding: 10px
.card-details-watch
font-size: 17px
padding-left: 7px
color: #a6a6a6
.card-details-title
font-weight: bold
font-size: 1.33em
margin: 7px 0 0
padding: 0
.linked-card-location
font-style: italic
font-size: 1em
margin-bottom: 0
& p
margin-bottom: 0
form.inlined-form
margin-top: 5px
margin-bottom: 10px
.card-details-list
font-size: 0.85em
margin-bottom: 3px
a.card-details-list-title
font-weight: bold
&.is-editable
display: inline-block
background: darken(white, 10%)
border-radius: 3px
padding: 0px 5px
.card-description textarea
min-height: 100px
.card-details-items
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,
&.card-details-item-members,
&.card-details-item-received,
&.card-details-item-start,
&.card-details-item-due,
&.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
color: #000
.card-label
padding-top: 5px
padding-bottom: 5px
.activities
padding-top: 10px
input[type="text"].attachment-add-link-input
float: left
margin: 0 0 8px
width: 80%
input[type="submit"].attachment-add-link-submit
float: left
margin: 0 0 8px 4px
padding: 6px 12px
width: 18%
@media screen and (max-width: 800px)
.card-details
width: calc(100% - 40px)
padding: 0px 20px 0px 20px
margin: 0px
transition: none
.card-details-canvas
width: 100%
padding-left: 0px;
.card-details-header
.close-card-details
margin-right: 0px
.card-details-menu
margin-right: 10px
card-details-color(background, color...)
background: background !important
if color
color: color !important //overwrite text for better visibility
.card-details-white
card-details-color(unset, #000) //Black text for better visibility
border: 1px solid #eee
.card-details-green
card-details-color(#3cb500, #ffffff) //White text for better visibility
.card-details-yellow
card-details-color(#fad900, #000) //Black text for better visibility
.card-details-orange
card-details-color(#ff9f19, #000) //Black text for better visibility
.card-details-red
card-details-color(#eb4646, #ffffff) //White text for better visibility
.card-details-purple
card-details-color(#a632db, #ffffff) //White text for better visibility
.card-details-blue
card-details-color(#0079bf, #ffffff) //White text for better visibility
.card-details-pink
card-details-color(#ff78cb, #000) //Black text for better visibility
.card-details-sky
card-details-color(#00c2e0, #ffffff) //White text for better visibility
.card-details-black
card-details-color(#4d4d4d, #ffffff) //White text for better visibility
.card-details-lime
card-details-color(#51e898, #000) //Black text for better visibility
.card-details-silver
card-details-color(#c0c0c0, #000) //Black text for better visibility
.card-details-peachpuff
card-details-color(#ffdab9, #000) //Black text for better visibility
.card-details-crimson
card-details-color(#dc143c, #ffffff) //White text for better visibility
.card-details-plum
card-details-color(#dda0dd, #000) //Black text for better visibility
.card-details-darkgreen
card-details-color(#006400, #ffffff) //White text for better visibility
.card-details-slateblue
card-details-color(#6a5acd, #ffffff) //White text for better visibility
.card-details-magenta
card-details-color(#ff00ff, #ffffff) //White text for better visibility
.card-details-gold
card-details-color(#ffd700, #000) //Black text for better visibility
.card-details-navy
card-details-color(#000080, #ffffff) //White text for better visibility
.card-details-gray
card-details-color(#808080, #ffffff) //White text for better visibility
.card-details-saddlebrown
card-details-color(#8b4513, #ffffff) //White text for better visibility
.card-details-paleturquoise
card-details-color(#afeeee, #000) //Black text for better visibility
.card-details-mistyrose
card-details-color(#ffe4e1, #000) //Black text for better visibility
.card-details-indigo
card-details-color(#4b0082, #ffffff) //White text for better visibility
-18
View File
@@ -1,18 +0,0 @@
.card-time {
display: block;
border-radius: 4px;
padding: 1px 3px;
color: #fff;
background-color: #dbdbdb;
}
.card-time:hover,
.card-time.is-active {
background-color: #b3b3b3;
}
.card-time time::before {
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
-webkit-font-smoothing: antialiased;
content: "\f017";
margin-right: 0.3em;
}
+28 -45
View File
@@ -1,5 +1,3 @@
import { TAPi18n } from '/imports/i18n';
BlazeComponent.extendComponent({
template() {
return 'editCardSpentTime';
@@ -11,6 +9,7 @@ BlazeComponent.extendComponent({
toggleOvertime() {
this.card.setIsOvertime(!this.card.getIsOvertime());
$('#overtime .materialCheckBox').toggleClass('is-checked');
$('#overtime').toggleClass('is-checked');
},
storeTime(spentTime, isOvertime) {
@@ -19,37 +18,31 @@ BlazeComponent.extendComponent({
},
deleteTime() {
this.card.setSpentTime(null);
this.card.setIsOvertime(false);
},
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();
let isOvertime = false;
if ($('#overtime').attr('class').indexOf('is-checked') >= 0) {
isOvertime = true;
}
if (spentTime >= 0) {
this.storeTime(spentTime, isOvertime);
Popup.back();
} else {
this.error.set('invalid-time');
evt.target.time.focus();
}
},
'click .js-delete-time'(evt) {
evt.preventDefault();
this.deleteTime();
Popup.back();
},
'click a.js-toggle-overtime': this.toggleOvertime,
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();
Popup.close();
},
'click a.js-toggle-overtime': this.toggleOvertime,
}];
},
}).register('editCardSpentTimePopup');
@@ -63,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();
},
});
+17
View File
@@ -0,0 +1,17 @@
.card-time
display: block
border-radius: 4px
padding: 1px 3px
color: #fff
background-color: #dbdbdb
&:hover, &.is-active
background-color: #b3b3b3
time
&::before
font: normal normal normal 14px/1 FontAwesome
font-size: inherit
-webkit-font-smoothing: antialiased
content: "\f017" // clock symbol
margin-right: 0.3em
-173
View File
@@ -1,173 +0,0 @@
.js-add-checklist {
color: #8c8c8c;
}
textarea.js-add-checklist-item,
textarea.js-edit-checklist-item {
overflow: hidden;
word-wrap: break-word;
resize: none;
height: 34px;
}
.delete-text,
.js-delete-checklist-item,
.js-convert-checklist-item-to-card {
color: #8c8c8c;
text-decoration: underline;
word-wrap: break-word;
float: right;
padding-top: 6px;
}
.delete-text:hover,
.js-delete-checklist-item:hover,
.js-convert-checklist-item-to-card:hover {
color: inherit;
}
.checklists-title {
display: flex;
justify-content: space-between;
}
.checklist-progress-bar-container {
display: flex;
flex-direction: row;
align-items: center;
}
.checklist-progress-bar-container .checklist-progress-text {
margin-right: 10px;
}
.checklist-progress-bar-container .checklist-progress-bar {
width: 80%;
height: 10px;
}
.checklist-progress-bar-container .checklist-progress-bar .checklist-progress {
color: #fff !important;
background-color: #2196f3 !important;
padding: 0.01em 16px;
border-radius: 16px;
height: 100%;
}
.checklist-title .checkbox {
float: left;
width: 30px;
height: 30px;
font-size: 18px;
line-height: 30px;
}
.checklist-title .title {
font-size: 18px;
line-height: 25px;
}
.checklist-title .checklist-stat {
margin: 0 0.5em;
float: right;
padding-top: 6px;
}
.checklist-title .checklist-stat.is-finished {
color: #3cb500;
}
.checklist-title span.fa.checklist-handle {
padding-right: 20px;
padding-top: 3px;
float: left;
}
#card-details-overlay {
top: 0;
bottom: -600px;
right: 0;
}
.checklist {
background: #f7f7f7;
}
.checklist.placeholder {
background: #ccc;
border-radius: 2px;
}
.checklist.ui-sortable-helper {
box-shadow: -2px 2px 8px rgba(0,0,0,0.3), 0 0 1px rgba(0,0,0,0.5);
transform: rotate(4deg);
cursor: grabbing;
}
.checklist-item {
margin: 0 0 0 0.1em;
line-height: 18px;
font-size: 1.1em;
margin-top: 3px;
display: flex;
background: #f7f7f7;
opacity: 1;
transition: height 0ms 400ms, opacity 400ms 0ms;
height: auto;
overflow: hidden;
}
.checklist-item.is-checked.invisible {
opacity: 0;
height: 0;
transition: height 0ms 0ms, opacity 600ms 0ms;
margin-top: 0;
margin-bottom: 0;
}
.checklist-item.placeholder {
background: #ccc;
border-radius: 2px;
}
.checklist-item.ui-sortable-helper {
box-shadow: -2px 2px 8px rgba(0,0,0,0.3), 0 0 1px rgba(0,0,0,0.5);
transform: rotate(4deg);
cursor: grabbing;
}
.checklist-item:hover {
background-color: #ebebeb;
}
.checklist-item .check-box-container {
padding-right: 10px;
}
.checklist-item .check-box {
margin: 0.1em 0 0 0;
}
.checklist-item .check-box.is-checked {
border-bottom: 2px solid #3cb500;
border-right: 2px solid #3cb500;
}
.checklist-item .item-title {
flex: 1;
}
.checklist-item .item-title.is-checked {
color: #8c8c8c;
font-style: italic;
text-decoration: line-through;
}
.checklist-item .item-title .viewer p {
margin-bottom: 2px;
display: block;
word-wrap: break-word;
max-width: 420px;
}
.checklist-item span.fa.checklistitem-handle {
padding-top: 2px;
padding-right: 10px;
}
.js-delete-checklist-item,
.js-convert-checklist-item-to-card {
margin: 0 0 0.5em 1.33em;
padding: 12px 0 0 0;
}
.add-checklist-item {
margin: 0.2em 0 0.5em 1.33em;
}
.add-checklist-item.js-open-inlined-form,
.add-checklist.js-open-inlined-form {
display: block;
width: 50%;
}
.add-checklist-item.js-open-inlined-form:hover,
.add-checklist.js-open-inlined-form:hover {
background: #dbdbdb;
color: #222;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.checklist-details-menu {
float: right;
padding: 6px 10px 6px 10px;
}
.edit-controls label.toggle-label {
margin-left: 2px;
}
+30 -97
View File
@@ -1,27 +1,21 @@
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)
.card-checklist-items
each checklist in checklists
each checklist in currentCard.checklists
+checklistDetail(checklist = checklist)
if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId)
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=false)
+addChecklistItemForm
else
a.add-checklist.js-open-inlined-form(title="{{_ 'add-checklist'}}")
a.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-checklist'}}...
template(name="checklistDetail")
.js-checklist.checklist
@@ -31,45 +25,37 @@ template(name="checklistDetail")
.checklist-title
span
if canModifyCard
a.fa.fa-navicon.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}")
a.js-delete-checklist.toggle-delete-checklist-dialog {{_ "delete"}}...
if canModifyCard
h4.title.js-open-inlined-form.is-editable
if isTouchScreenOrShowDesktopDragHandles
span.fa.checklist-handle(class="fa-arrows" title="{{_ 'dragChecklist'}}")
h2.title.js-open-inlined-form.is-editable
+viewer
= checklist.title
else
h4.title
h2.title
+viewer
= checklist.title
if $gt finishedPercent 0
.checklist-progress-bar-container
.checklist-progress-text {{finishedPercent}}%
.checklist-progress-bar
.checklist-progress(style="width:{{finishedPercent}}%")
+checklistItems(checklist = checklist)
template(name="checklistDeletePopup")
p {{_ 'confirm-checklist-delete-popup'}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="checklistDeleteDialog")
.js-confirm-checklist-delete
p
i(class="fa fa-exclamation-triangle" aria-hidden="true")
p
| {{_ 'confirm-checklist-delete-dialog'}}
span {{checklist.title}}
| ?
.js-checklist-delete-buttons
button.confirm-checklist-delete(type="button") {{_ 'delete'}}
button.toggle-delete-checklist-dialog(type="button") {{_ 'cancel'}}
template(name="addChecklistItemForm")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.js-add-checklist-item(rows='1' autofocus)
.edit-controls.clearfix
button.primary.confirm.js-submit-add-checklist-item-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-add-checklist-item'}}")
if showNewlineBecomesNewChecklistItem
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItem'}}")
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItem")
label.toggle-label(for="toggleNewlineBecomesNewChecklistItem")
a.fa.fa-times-thin.js-close-inlined-form
template(name="editChecklistItemForm")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.js-edit-checklist-item(rows='1' autofocus dir="auto")
if $eq type 'item'
= item.title
@@ -77,13 +63,10 @@ template(name="editChecklistItemForm")
= checklist.title
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-checklist-item-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-edit-checklist-item'}}")
a.fa.fa-times-thin.js-close-inlined-form
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
@@ -94,19 +77,16 @@ template(name="checklistItems")
+checklistItemDetail(item = item checklist = checklist)
if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist)
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true)
+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}}"
role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
.js-checklist-item.checklist-item
if canModifyCard
.check-box-container
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
if isTouchScreenOrShowDesktopDragHandles
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
@@ -115,50 +95,3 @@ template(name='checklistItemDetail')
.item-title(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
template(name="checklistActionsPopup")
ul.pop-over-list
li
a.js-delete-checklist.delete-checklist
i.fa.fa-trash
| {{_ "delete"}} ...
a.js-move-checklist.move-checklist
i.fa.fa-arrow-right
| {{_ "moveChecklist"}} ...
a.js-copy-checklist.copy-checklist
i.fa.fa-copy
| {{_ "copyChecklist"}} ...
template(name="copyChecklistPopup")
+copyAndMoveChecklist
template(name="moveChecklistPopup")
+copyAndMoveChecklist
template(name="copyAndMoveChecklist")
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}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{title}}
label {{_ 'swimlanes'}}:
select.js-select-swimlanes
each swimlanes
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{title}}
label {{_ 'lists'}}:
select.js-select-lists
each lists
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{title}}
label {{_ 'cards'}}:
select.js-select-cards
each cards
option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{title}}
.edit-controls.clearfix
button.primary.confirm.js-done {{_ 'done'}}
+74 -249
View File
@@ -1,10 +1,4 @@
import { TAPi18n } from '/imports/i18n';
import Cards from '/models/cards';
import Boards from '/models/boards';
import { DialogWithBoardSwimlaneListCard } from '/client/lib/dialogWithBoardSwimlaneListCard';
const subManager = new SubsManager();
const { calculateIndexData, capitalize } = Utils;
const { calculateIndexData, enableClickOnTouch } = Utils;
function initSorting(items) {
items.sortable({
@@ -12,13 +6,13 @@ 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: true,
scroll: false,
start(evt, ui) {
ui.placeholder.height(ui.helper.height());
EscapeActions.clickExecute(evt.target, 'inlinedForm');
EscapeActions.executeUpTo('popup-close');
},
stop(evt, ui) {
const parent = ui.item.parents('.js-checklist-items');
@@ -42,6 +36,9 @@ function initSorting(items) {
checklistItem.move(checklistId, sortIndex.base);
},
});
// ugly touch event hotfix
enableClickOnTouch('.js-checklist-item:not(.placeholder)');
}
BlazeComponent.extendComponent({
@@ -49,7 +46,7 @@ BlazeComponent.extendComponent({
const self = this;
self.itemsDom = this.$('.js-checklist-items');
initSorting(self.itemsDom);
self.itemsDom.mousedown(function (evt) {
self.itemsDom.mousedown(function(evt) {
evt.stopPropagation();
});
@@ -60,42 +57,27 @@ BlazeComponent.extendComponent({
// 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.isTouchScreenOrShowDesktopDragHandles()) {
$(self.itemsDom).sortable({
handle: 'span.fa.checklistitem-handle',
});
}
}
});
},
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
},
/** returns the finished percent of the checklist */
finishedPercent() {
const ret = this.data().checklist.finishedPercent();
return ret;
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.isLinkedCard()) cardId = card.linkedId;
if (card.isLinked())
cardId = card.linkedId;
if (title) {
Checklists.insert({
@@ -103,34 +85,27 @@ BlazeComponent.extendComponent({
title,
sort: card.checklists().count(),
});
this.closeAllInlinedForms();
setTimeout(() => {
this.$('.add-checklist-item')
.last()
.click();
this.$('.add-checklist-item').last().click();
}, 100);
}
textarea.value = '';
textarea.focus();
},
addChecklistItem(event) {
event.preventDefault();
const textarea = this.find('textarea.js-add-checklist-item');
const newlineBecomesNewChecklistItem = this.find('input#toggleNewlineBecomesNewChecklistItem');
const title = textarea.value.trim();
const checklist = this.currentData().checklist;
if (title) {
let checklistItems = [title];
if (newlineBecomesNewChecklistItem.checked) {
checklistItems = title.split('\n').map(_value => _value.trim());
}
for (let checklistItem of checklistItems) {
ChecklistItems.insert({
title: checklistItem,
checklistId: checklist._id,
cardId: checklist.cardId,
sort: Utils.calculateIndexData(checklist.lastItem()).base,
});
}
ChecklistItems.insert({
title,
checklistId: checklist._id,
cardId: checklist.cardId,
sort: checklist.itemCount(),
});
}
// We keep the form opened, empty it.
textarea.value = '';
@@ -138,12 +113,15 @@ 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() {
const checklist = this.currentData().checklist;
if (checklist && checklist._id) {
Checklists.remove(checklist._id);
this.toggleDeleteDialog.set(false);
}
},
deleteItem() {
@@ -171,6 +149,11 @@ BlazeComponent.extendComponent({
item.setTitle(title);
},
onCreated() {
this.toggleDeleteDialog = new ReactiveVar(false);
this.checklistToDelete = null; //Store data context to pass to checklistDeleteDialog template
},
pressKey(event) {
//If user press enter key inside a form, submit it
//Unless the user is also holding down the 'shift' key
@@ -181,186 +164,54 @@ 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();
}
},
/** closes all inlined forms (checklist and checklist-item input fields) */
closeAllInlinedForms() {
this.$('.js-close-inlined-form').click();
},
events() {
const events = {
'click #toggleHideCheckedItemsButton'() {
Meteor.call('toggleHideCheckedItems');
'click .toggle-delete-checklist-dialog'(event) {
if($(event.target).hasClass('js-delete-checklist')){
this.checklistToDelete = this.currentData().checklist; //Store data context
}
this.toggleDeleteDialog.set(!this.toggleDeleteDialog.get());
},
};
return [
{
...events,
'click .js-open-checklist-details-menu': Popup.open('checklistActions'),
'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,
'focus .js-add-checklist-item': this.focusChecklistItem,
// add and delete checklist / checklist-item
'click .js-open-inlined-form': this.closeAllInlinedForms,
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'));
},
Template.checklistDeleteDialog.onCreated(() => {
const $cardDetails = this.$('.card-details');
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; });
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({
checklists() {
const card = Cards.findOne(this.cardId);
const ret = card.checklists();
return ret;
},
hideCheckedItems() {
const currentUser = Meteor.user();
if (currentUser) return currentUser.hasHideCheckedItems();
return false;
},
//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
$cardDetails.scrollTop(0);
}
});
});
BlazeComponent.extendComponent({
onRendered() {
autosize(this.$('textarea.js-add-checklist-item'));
},
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
},
events() {
return [
{
'click a.fa.fa-copy'(event) {
const $editor = this.$('textarea');
const promise = Utils.copyTextToClipboard($editor[0].value);
const $tooltip = this.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
}
];
}
}).register('addChecklistItemForm');
BlazeComponent.extendComponent({
events() {
return [
{
'click .js-delete-checklist': Popup.afterConfirm('checklistDelete', function () {
Popup.back(2);
const checklist = this.checklist;
if (checklist && checklist._id) {
Checklists.remove(checklist._id);
}
}),
'click .js-move-checklist': Popup.open('moveChecklist'),
'click .js-copy-checklist': Popup.open('copyChecklist'),
}
]
}
}).register('checklistActionsPopup');
BlazeComponent.extendComponent({
onRendered() {
autosize(this.$('textarea.js-edit-checklist-item'));
},
canModifyCard() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
!Meteor.user().isCommentOnly() &&
!Meteor.user().isWorker()
);
},
events() {
return [
{
'click a.fa.fa-copy'(event) {
const $editor = this.$('textarea');
const promise = Utils.copyTextToClipboard($editor[0].value);
const $tooltip = this.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
}
];
}
}).register('editChecklistItemForm');
Template.checklistDeleteDialog.onDestroyed(() => {
const $cardDetails = this.$('.card-details');
$cardDetails.off('scroll'); //Reactivate scrolling
$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();
},
});
@@ -373,34 +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');
/** Move Checklist Dialog */
(class extends DialogWithBoardSwimlaneListCard {
getDialogOptions() {
const ret = Meteor.user().getMoveChecklistDialogOptions();
return ret;
}
setDone(cardId, options) {
Meteor.user().setMoveChecklistDialogOption(this.currentBoardId, options);
this.data().checklist.move(cardId);
}
}).register('moveChecklistPopup');
/** Copy Checklist Dialog */
(class extends DialogWithBoardSwimlaneListCard {
getDialogOptions() {
const ret = Meteor.user().getCopyChecklistDialogOptions();
return ret;
}
setDone(cardId, options) {
Meteor.user().setCopyChecklistDialogOption(this.currentBoardId, options);
this.data().checklist.copy(cardId);
}
}).register('copyChecklistPopup');
+139
View File
@@ -0,0 +1,139 @@
.js-add-checklist
color: #8c8c8c
textarea.js-add-checklist-item, textarea.js-edit-checklist-item
overflow: hidden
word-wrap: break-word
resize: none
height: 34px
.delete-text
color: #8c8c8c
text-decoration: underline
word-wrap: break-word
float: right
padding-top: 6px
&:hover
color: inherit
.checklist-title
.checkbox
float: left
width: 30px
height 30px
font-size: 18px
line-height: 30px
.title
font-size: 18px
line-height: 25px
.checklist-stat
margin: 0 0.5em
float: right
padding-top: 6px
&.is-finished
color: #3cb500
.js-delete-checklist
@extends .delete-text
.js-confirm-checklist-delete
background-color: darken(white, 3%)
position: absolute
float: left;
width: 60%
margin-top: 0
margin-left: 13%
padding-bottom: 2%
padding-left: 3%
padding-right: 3%
z-index: 17
border-radius: 3px
p
position: relative
margin-top: 3%
width: 100%
text-align: center
span
font-weight: bold
i
font-size: 2em
.js-checklist-delete-buttons
position: relative
padding: left 2% right 2%
.confirm-checklist-delete
margin-left: 12%
float: left
.toggle-delete-checklist-dialog
margin-right: 12%
float: right
#card-details-overlay
top: 0
bottom: -600px
right: 0
.checklist
background: darken(white, 3%)
&.placeholder
background: darken(white, 20%)
border-radius: 2px
&.ui-sortable-helper
box-shadow: -2px 2px 8px rgba(0, 0, 0, .3),
0 0 1px rgba(0, 0, 0, .5)
transform: rotate(4deg)
cursor: grabbing
.checklist-item
margin: 0 0 0 0.1em
line-height: 18px
font-size: 1.1em
margin-top: 3px
display: flex
background: darken(white, 3%)
&.placeholder
background: darken(white, 20%)
border-radius: 2px
&.ui-sortable-helper
box-shadow: -2px 2px 8px rgba(0, 0, 0, .3),
0 0 1px rgba(0, 0, 0, .5)
transform: rotate(4deg)
cursor: grabbing
&:hover
background-color: darken(white, 8%)
.check-box
margin: 0.1em 0 0 0;
&.is-checked
border-bottom: 2px solid #3cb500
border-right: 2px solid #3cb500
.item-title
flex: 1
padding-left: 10px;
&.is-checked
color: #8c8c8c
font-style: italic
& .viewer
p
margin-bottom: 2px
.js-delete-checklist-item
margin: 0 0 0.5em 1.33em
@extends .delete-text
padding: 12px 0 0 0
.add-checklist-item
margin: 0.2em 0 0.5em 1.33em
display: inline-block
-230
View File
@@ -1,230 +0,0 @@
.card-label {
border: 1px solid #000;
border-radius: 4px;
color: #fff;
display: inline-block;
font-weight: 700;
font-size: 13px;
margin-right: 4px;
margin-bottom: 5px;
padding: 3px 8px;
max-width: 210px;
min-width: 8px;
word-wrap: break-word;
min-height: 18px;
vertical-align: middle;
white-space: initial;
overflow: initial;
}
.card-label:hover {
color: #fff;
}
.card-label.square {
height: 30px;
width: 30px;
padding: 0;
}
.card-label.add-label {
box-shadow: 0 0 0 2px #bfbfbf inset;
border: initial;
}
.card-label.add-label:hover,
.card-label.add-label.is-active {
box-shadow: 0 0 0 2px #666 inset;
}
.card-label p {
margin: 0px;
}
.palette-colors {
display: flex;
flex-wrap: wrap;
}
.palette-colors .palette-color {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
}
.card-label-white {
background-color: #fff;
color: #000;
}
.card-label-white:hover {
color: #aaa;
}
.card-label-green {
background-color: #3cb500;
}
.card-label-green:hover {
color: #000;
}
.card-label-yellow {
background-color: #fad900;
color: #000;
}
.card-label-orange {
background-color: #ff9f19;
color: #000;
}
.card-label-red {
background-color: #eb4646;
}
.card-label-purple {
background-color: #a632db;
}
.card-label-blue {
background-color: #0079bf;
}
.card-label-pink {
background-color: #ff78cb;
color: #000;
}
.card-label-sky {
background-color: #00c2e0;
}
.card-label-black {
background-color: #4d4d4d;
}
.card-label-lime {
background-color: #51e898;
color: #000;
}
.card-label-silver {
background-color: #c0c0c0;
color: #000;
}
.card-label-peachpuff {
background-color: #ffdab9;
color: #000;
}
.card-label-crimson {
background-color: #dc143c;
}
.card-label-plum {
background-color: #dda0dd;
color: #000;
}
.card-label-darkgreen {
background-color: #006400;
}
.card-label-slateblue {
background-color: #6a5acd;
}
.card-label-magenta {
background-color: #f0f;
}
.card-label-gold {
background-color: #ffd700;
color: #000;
}
.card-label-navy {
background-color: #000080;
}
.card-label-gray {
background-color: #808080;
}
.card-label-saddlebrown {
background-color: #8b4513;
}
.card-label-paleturquoise {
background-color: #afeeee;
color: #000;
}
.card-label-mistyrose {
background-color: #ffe4e1;
color: #000;
}
.card-label-indigo {
background-color: #4b0082;
}
.edit-label .card-label,
.create-label .card-label {
float: left;
height: 25px;
margin: 0px 3% 7px 0px;
width: 10.5%;
max-width: 10.5%;
cursor: pointer;
}
.edit-labels input[type="text"] {
margin: 4px 0 6px 38px;
width: 243px;
}
.edit-labels .card-label {
height: 30px;
left: 0;
padding: 1px 5px;
position: absolute;
top: 0;
width: 24px;
}
.edit-labels .labels-static .card-label {
line-height: 30px;
margin-bottom: 4px;
position: relative;
top: auto;
left: 0;
width: 260px;
}
.edit-labels-pop-over {
margin-bottom: 8px;
}
.edit-labels-pop-over .card-label .viewer p {
margin: 0;
}
.edit-labels-pop-over .shortcut {
display: inline-block;
}
.card-label-selectable {
border-radius: 3px;
cursor: pointer;
margin: 0;
margin-bottom: 3px;
width: 190px;
min-height: 18px;
padding: 8px;
position: relative;
transition: margin-right 0.1s;
}
.card-label-selectable .card-label-selectable-icon {
position: absolute;
top: 8px;
right: -20px;
}
.card-label-selectable.active:hover,
.card-label-selectable.active,
.card-label-selectable.active.selected:hover,
.card-label-selectable.active.selected {
padding-right: 32px;
}
.card-label-selectable.active:hover .card-label-selectable-icon,
.card-label-selectable.active .card-label-selectable-icon,
.card-label-selectable.active.selected:hover .card-label-selectable-icon,
.card-label-selectable.active.selected .card-label-selectable-icon {
right: 6px;
}
.card-label-selectable.selected,
.card-label-selectable:hover {
opacity: 0.8;
}
.active .card-label-selectable,
.active .card-label-selectable:hover {
margin-right: 0;
}
.active .card-label-selectable .card-label-selectable-icon {
right: 8px;
}
.card-label-edit-button {
border-radius: 3px;
float: right;
padding: 8px;
}
.card-label-edit-button:hover {
background: #dbdbdb;
}
ul.edit-labels-pop-over span.fa.label-handle {
padding-right: 10px;
}
ul.edit-labels-pop-over span.fa.label-handle + .card-label {
max-width: 180px;
}
+2 -4
View File
@@ -27,11 +27,9 @@ template(name="deleteLabelPopup")
template(name="cardLabelsPopup")
ul.edit-labels-pop-over
each board.labels
li.js-card-label-item
li
a.card-label-edit-button.fa.fa-pencil.js-edit-label
if isTouchScreenOrShowDesktopDragHandles
span.fa.label-handle(class="fa-arrows" title="{{_ 'dragLabel'}}")
span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="card-label-{{color}}"
span.card-label.card-label-selectable.js-select-label(class="card-label-{{color}}"
class="{{# if isLabelSelected ../_id }}active{{/if}}")
+viewer
= name
+24 -83
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');
@@ -39,71 +37,20 @@ Template.createLabelPopup.helpers({
},
});
BlazeComponent.extendComponent({
onRendered() {
const itemsSelector = 'li.js-card-label-item:not(.placeholder)';
const $labels = this.$('.edit-labels-pop-over');
$labels.sortable({
connectWith: '.edit-labels-pop-over',
tolerance: 'pointer',
appendTo: '.edit-labels-pop-over',
helper(element, currentItem) {
let ret = currentItem.clone();
if (currentItem.closest('.popup-container-depth-0').size() == 0) { // only set css transform at every sub-popup, not at the main popup
const content = currentItem.closest('.content')[0]
const offsetLeft = content.offsetLeft;
const offsetTop = $('.pop-over > .header').height() * -1;
ret.css("transform", `translate(${offsetLeft}px, ${offsetTop}px)`);
}
return ret;
},
distance: 7,
items: itemsSelector,
placeholder: 'card-label-wrapper placeholder',
start(evt, ui) {
ui.helper.css('z-index', 1000);
ui.placeholder.height(ui.helper.height());
EscapeActions.clickExecute(evt.target, 'inlinedForm');
},
stop(evt, ui) {
const newLabelOrderOnlyIds = ui.item.parent().children().toArray().map(_element => Blaze.getData(_element)._id)
const card = Blaze.getData(this);
card.board().setNewLabelOrder(newLabelOrderOnlyIds);
},
});
// Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => {
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$labels.sortable({
handle: '.label-handle',
});
}
});
},
events() {
return [
{
'click .js-select-label'(event) {
const card = this.data();
const labelId = this.currentData()._id;
card.toggleLabel(labelId);
event.preventDefault();
},
'click .js-edit-label': Popup.open('editLabel'),
'click .js-add-label': Popup.open('createLabel'),
}
];
}
}).register('cardLabelsPopup');
Template.cardLabelsPopup.events({
'click .js-select-label'(evt) {
const card = Cards.findOne(Session.get('currentCard'));
const labelId = this._id;
card.toggleLabel(labelId);
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');
@@ -115,33 +62,27 @@ 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();
},
});
Template.editLabelPopup.events({
'click .js-delete-label': Popup.afterConfirm('deleteLabel', function () {
'click .js-delete-label': Popup.afterConfirm('deleteLabel', function() {
const board = Boards.findOne(Session.get('currentBoard'));
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();
},
Loaded 100 of 1112 files, more files were not shown because too many files have changed in this diff. Show more