Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94611c4f38 | ||
|
|
0e6035f846 | ||
|
|
a8faeb2e5f | ||
|
|
118c9613dc | ||
|
|
b42ecb7948 | ||
|
|
e0398f01c8 | ||
|
|
b260d05a8b | ||
|
|
bbc68309af | ||
|
|
48ebc5f117 | ||
|
|
c04292e988 | ||
|
|
2203d46493 | ||
|
|
dae8033d61 | ||
|
|
b5c566615b | ||
|
|
d792033ffa | ||
|
|
63fa75819c | ||
|
|
f25e54a9f1 | ||
|
|
621efd59db | ||
|
|
5e36c188f9 |
No files matched your search
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-stage-3"
|
||||
],
|
||||
"env": {
|
||||
"COVERAGE": {
|
||||
"plugins": [
|
||||
"istanbul"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
packages/*
|
||||
.snap-meteor-1.8/*
|
||||
+13
-39
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,3 +0,0 @@
|
||||
* text=auto eol=lf
|
||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||
@@ -1,3 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
custom: ['https://wekan.team/commercial-support/']
|
||||
+18
-26
@@ -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*
|
||||
@@ -1,6 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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@507c2f2dc502c992ad446e3d7a5dfbe311567a96
|
||||
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@37abcedcc1da61a57767b7588cb9d03eb57e28b3
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -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)
|
||||
@@ -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.5.0
|
||||
env:
|
||||
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -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
@@ -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
|
||||
Vendored
-10
@@ -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/
|
||||
@@ -1,4 +0,0 @@
|
||||
tasks:
|
||||
- init: npm install
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
@@ -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
@@ -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.5
|
||||
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.4
|
||||
matb33:collection-hooks
|
||||
matteodem:easy-search
|
||||
mongo@1.3.1
|
||||
mquandalle:collection-mutations
|
||||
|
||||
# Account system
|
||||
accounts-password@2.3.3
|
||||
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.3.0
|
||||
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
@@ -1 +1 @@
|
||||
METEOR@2.10.0
|
||||
METEOR@1.6.0.1
|
||||
+130
-177
@@ -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.2
|
||||
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.5.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.5
|
||||
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.2.0
|
||||
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.11.0
|
||||
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.4.2
|
||||
meteortesting:mocha@2.0.4
|
||||
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.4
|
||||
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,122 +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.5
|
||||
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.3.0
|
||||
twbs:bootstrap@3.3.6
|
||||
templating@1.3.2
|
||||
templating-compiler@1.3.3
|
||||
templating-runtime@1.3.2
|
||||
templating-tools@1.1.2
|
||||
tracker@1.1.3
|
||||
ui@1.0.13
|
||||
underscore@1.0.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.3
|
||||
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
|
||||
zodern:types@1.0.9
|
||||
@@ -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:
|
||||
@@ -1,8 +0,0 @@
|
||||
packages/
|
||||
node_modules/
|
||||
.build/
|
||||
.meteor/
|
||||
.vscode/
|
||||
.tx/
|
||||
.github/
|
||||
.snap-meteor-1.8/
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
+4
-4
@@ -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
@@ -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
|
||||
|
||||
|
||||
Vendored
-45
@@ -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
-6908
File diff suppressed because it is too large.
Load diff
+2
-33
@@ -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
@@ -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"]
|
||||
@@ -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"]
|
||||
|
||||
@@ -1,51 +1,42 @@
|
||||
[Gitpod Ready-to-Code](https://gitpod.io/#https://github.com/wekan/wekan)
|
||||
# Wekan - Open Source kanban
|
||||
|
||||
# WeKan ® - Open Source kanban
|
||||
[](https://github.com/wekan/wekan/graphs/contributors)
|
||||
[](https://quay.io/repository/wekan/wekan)
|
||||
[](https://hub.docker.com/r/wekanteam/wekan)
|
||||
[](https://hub.docker.com/r/wekanteam/wekan)
|
||||
[![Wekan Build Status][travis_badge]][travis_status]
|
||||
[](https://www.codacy.com/app/xet7/wekan?utm_source=github.com&utm_medium=referral&utm_content=wekan/wekan&utm_campaign=Badge_Grade)
|
||||
[](https://codeclimate.com/github/wekan/wekan)
|
||||
[](https://david-dm.org/wekan/wekan)
|
||||
[](https://www.openhub.net/p/wekan)
|
||||
[](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
|
||||
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_large)
|
||||
+7
-7
@@ -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
@@ -1,5 +1,5 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v6.71.0"
|
||||
appVersion: "v2.95.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
||||
@@ -1,379 +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
|
||||
|
||||
syntax = """=== Wekan API Python CLI: Shows IDs for addcard ===
|
||||
# AUTHORID is USERID that writes card or custom field.
|
||||
If *nix: chmod +x api.py => ./api.py users
|
||||
Syntax:
|
||||
User API:
|
||||
python3 api.py user # Current user and list of current user boards
|
||||
python3 api.py boards USERID # Boards of USERID
|
||||
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 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 listattachments BOARDID # List attachments
|
||||
|
||||
Admin API:
|
||||
python3 api.py users # All users
|
||||
python3 api.py boards # All Public Boards
|
||||
python3 api.py newuser USERNAME EMAIL PASSWORD
|
||||
"""
|
||||
|
||||
if arguments == 0:
|
||||
print(syntax)
|
||||
exit
|
||||
|
||||
# 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")
|
||||
|
||||
# ------- 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 -------------
|
||||
|
||||
"""
|
||||
=== 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'
|
||||
apiuser = 'api/user'
|
||||
apiallusers = 'api/allusers'
|
||||
e = 'export'
|
||||
s = '/'
|
||||
l = 'lists'
|
||||
sw = 'swimlane'
|
||||
sws = 'swimlanes'
|
||||
cs = 'cards'
|
||||
cf = 'custom-fields'
|
||||
bs = 'boards'
|
||||
apbs = 'allpublicboards'
|
||||
atl = 'attachmentslist'
|
||||
at = 'attachment'
|
||||
ats = 'attachments'
|
||||
users = wekanurl + apiusers
|
||||
user = wekanurl + apiuser
|
||||
allusers = wekanurl + apiallusers
|
||||
|
||||
# ------- 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] == 'user':
|
||||
# ------- LIST OF ALL USERS START -----------
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
print(user)
|
||||
print("=== USER ===\n")
|
||||
body = requests.get(user, headers=headers)
|
||||
data2 = body.text.replace('}',"}\n")
|
||||
print(data2)
|
||||
# ------- LIST OF ALL 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 -----------
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
// PWA
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function() {
|
||||
navigator.serviceWorker.register('/pwa-service-worker.js');
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 }}
|
||||
@@ -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 [
|
||||
'👍',
|
||||
'👎',
|
||||
'👀',
|
||||
'✅',
|
||||
'❌',
|
||||
'🙏',
|
||||
'👏',
|
||||
'🎉',
|
||||
'🚀',
|
||||
'😊',
|
||||
'🤔',
|
||||
'😔'];
|
||||
}
|
||||
})
|
||||
|
||||
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%)
|
||||
@@ -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,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}}
|
||||
|
||||
@@ -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'}}
|
||||
|
||||
|
||||
@@ -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');
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
@@ -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
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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'}}
|
||||
|
||||
@@ -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');
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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'}}
|
||||
@@ -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');
|
||||
@@ -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%
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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' }}
|
||||
@@ -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');
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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');
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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
+310
-1313
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
},
|
||||
});
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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'}}
|
||||
@@ -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');
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in new issue
Block a user