This commit is contained in:
John Supplee 2021-12-19 12:18:05 +02:00
commit 241c3ed8ae
332 changed files with 18869 additions and 18221 deletions

View file

@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND=noninteractive
ENV \ ENV \
DEBUG=false \ DEBUG=false \
NODE_VERSION=v12.22.4 \ NODE_VERSION=v12.22.8 \
METEOR_RELEASE=1.10.2 \ METEOR_RELEASE=1.10.2 \
USE_EDGE=false \ USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \ METEOR_EDGE=1.5-beta.17 \
@ -22,6 +22,7 @@ ENV \
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 \ ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 \
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 \ ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 \
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 \ ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 \
ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS=90 \
RICHER_CARD_COMMENT_EDITOR=false \ RICHER_CARD_COMMENT_EDITOR=false \
CARD_OPENED_WEBHOOK_ENABLED=false \ CARD_OPENED_WEBHOOK_ENABLED=false \
ATTACHMENTS_STORE_PATH="" \ ATTACHMENTS_STORE_PATH="" \
@ -195,6 +196,10 @@ COPY \
settings.json \ settings.json \
/home/wekan/app/ /home/wekan/app/
COPY \
tests \
/home/wekan/app/tests/
COPY \ COPY \
packages \ packages \
/home/wekan/app/packages/ /home/wekan/app/packages/
@ -226,6 +231,19 @@ RUN \
chmod u+w package.json npm-shrinkwrap.json && \ chmod u+w package.json npm-shrinkwrap.json && \
npm install npm install
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 ENV PORT=3000
EXPOSE $PORT EXPOSE $PORT
WORKDIR /home/wekan/app WORKDIR /home/wekan/app

View file

@ -12,9 +12,9 @@ services:
expose: expose:
- 27017 - 27017
volumes: volumes:
- /etc/localtime:/etc/localtime:ro
- ./volumes/wekan-db:/data/db - ./volumes/wekan-db:/data/db
- ./volumes/wekan-db-dump:/dump - ./volumes/wekan-db-dump:/dump
- /etc/localtime:/etc/localtime:ro
wekan-dev: wekan-dev:
container_name: wekan-dev-app container_name: wekan-dev-app
@ -36,19 +36,13 @@ services:
depends_on: depends_on:
- wekandb-dev - wekandb-dev
volumes: volumes:
- /etc/localtime:/etc/localtime:ro
- ../client:/home/wekan/app/client - ../client:/home/wekan/app/client
- ../models:/home/wekan/app/models - ../models:/home/wekan/app/models
- ../config:/home/wekan/app/config - ../config:/home/wekan/app/config
- ../i18n:/home/wekan/app/i18n - ../i18n:/home/wekan/app/i18n
- ../server:/home/wekan/app/server - ../server:/home/wekan/app/server
- ../public:/home/wekan/app/public - ../public:/home/wekan/app/public
- /etc/localtime:/etc/localtime:ro
volumes:
wekan-dev-db:
driver: local
wekan-dev-db-dump:
driver: local
networks: networks:
wekan-dev-tier: wekan-dev-tier:

View file

@ -122,6 +122,7 @@
"Activities": true, "Activities": true,
"Attachments": true, "Attachments": true,
"Boards": true, "Boards": true,
"CardCommentReactions": true,
"CardComments": true, "CardComments": true,
"DatePicker": true, "DatePicker": true,
"Cards": true, "Cards": true,
@ -156,6 +157,7 @@
"Integrations": true, "Integrations": true,
"HTTP": true, "HTTP": true,
"AccountSettings": true, "AccountSettings": true,
"TableVisibilityModeSettings": true,
"Announcements": true, "Announcements": true,
"Swimlanes": true, "Swimlanes": true,
"ChecklistItems": true, "ChecklistItems": true,

View file

@ -81,7 +81,7 @@ parts:
wekan: wekan:
source: . source: .
plugin: nodejs plugin: nodejs
node-engine: 12.22.4 node-engine: 12.22.8
node-packages: node-packages:
- node-gyp - node-gyp
- node-pre-gyp - node-pre-gyp

View file

@ -83,7 +83,7 @@ parts:
wekan: wekan:
source: . source: .
plugin: nodejs plugin: nodejs
node-engine: 12.22.4 node-engine: 12.22.8
node-packages: node-packages:
- node-gyp - node-gyp
- node-pre-gyp - node-pre-gyp

View file

@ -1,12 +1,13 @@
## Issue ## Issue
Note: With Docker, please don't use latest tag. Only use release tags. **[PLEASE UPGRADE](https://github.com/wekan/wekan/wiki/Backup)** to newest WeKan ® before adding new issue !!
See https://github.com/wekan/wekan/issues/3874 - We get too many duplicate reports of already fixed bugs. Newest WeKan ® has newest bugfixes and security fixes.
- Please search existing Open and Closed issues, most questions have already been answered many times.
If you can not login for any reason: If you can not login for any reason:
- https://github.com/wekan/wekan/wiki/Forgot-Password - https://github.com/wekan/wekan/wiki/Forgot-Password
Email settings: Email settings, only SMTP MAIL_URL and MAIL_FROM are in use:
- https://github.com/wekan/wekan/wiki/Troubleshooting-Mail - https://github.com/wekan/wekan/wiki/Troubleshooting-Mail
Add these issues to elsewhere: Add these issues to elsewhere:
@ -20,14 +21,12 @@ Other Wekan issues can be added here.
* Note: Please anonymize info, and do not add to this public issue any of your Wekan board URLs, passwords, API tokens etc, do you understand?: * Note: Please anonymize info, and do not add to this public issue any of your Wekan board URLs, passwords, API tokens etc, do you understand?:
* Did you test in newest Wekan?: * Did you test in newest Wekan?:
* For new Wekan install, did you configure root-url correctly so Wekan cards open correctly https://github.com/wekan/wekan/wiki/Settings ? * For new Wekan install, did you configure root-url correctly so Wekan cards open 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: * Operating System:
* Deployment Method(snap/docker/sandstorm/mongodb bundle/source): * Deployment Method(Snap/Docker/Sandstorm/bundle/source):
* Http frontend if any (Caddy, Nginx, Apache, see config examples from Wekan GitHub wiki first): * Http frontend if any (Caddy, Nginx, Apache, see config examples from Wekan GitHub wiki first):
* Node Version: * Node.js Version:
* MongoDB Version: * MongoDB Version:
* Wekan only works on newest desktop Firefox/Chromium/Chrome/Edge/Chromium Edge and mobile Chrome. What webbrowser version are you using? * Wekan works on newest desktop and mobile webbrowsers that support Javascript. What webbrowser version are you using?
**Problem description**: **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* - *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*

63
.github/workflows/docker-publish.yml vendored Normal file
View file

@ -0,0 +1,63 @@
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@v2
# 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@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
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@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
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@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

25
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: Release Charts
on:
push:
branches:
- master
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
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.1.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

1
.gitignore vendored
View file

@ -38,3 +38,4 @@ ehthumbs.db
# Helm chart # Helm chart
# Chart dependencies # Chart dependencies
/helm/wekan/**/*.tgz /helm/wekan/**/*.tgz
/helm/wekan/charts

View file

@ -60,11 +60,10 @@ reactive-var@1.0.11
fortawesome:fontawesome fortawesome:fontawesome
mousetrap:mousetrap mousetrap:mousetrap
mquandalle:jquery-textcomplete mquandalle:jquery-textcomplete
mquandalle:jquery-ui-drag-drop-sort
mquandalle:mousetrap-bindglobal mquandalle:mousetrap-bindglobal
peerlibrary:blaze-components@=0.15.1 peerlibrary:blaze-components@=0.15.1
templates:tabs templates:tabs
verron:autosize meteor-autosize
simple:json-routes simple:json-routes
rajit:bootstrap3-datepicker rajit:bootstrap3-datepicker
shell-server@0.5.0 shell-server@0.5.0

View file

@ -76,6 +76,7 @@ matb33:collection-hooks@0.9.1
matteodem:easy-search@1.6.4 matteodem:easy-search@1.6.4
mdg:validation-error@0.5.1 mdg:validation-error@0.5.1
meteor@1.9.3 meteor@1.9.3
meteor-autosize@5.0.1
meteor-base@1.4.0 meteor-base@1.4.0
meteor-platform@1.2.6 meteor-platform@1.2.6
meteorhacks:aggregate@1.3.0 meteorhacks:aggregate@1.3.0
@ -106,7 +107,6 @@ mquandalle:collection-mutations@0.1.0
mquandalle:jade@0.4.9 mquandalle:jade@0.4.9
mquandalle:jade-compiler@0.4.5 mquandalle:jade-compiler@0.4.5
mquandalle:jquery-textcomplete@0.8.0_1 mquandalle:jquery-textcomplete@0.8.0_1
mquandalle:jquery-ui-drag-drop-sort@0.2.0
mquandalle:moment@1.0.1 mquandalle:moment@1.0.1
mquandalle:mousetrap-bindglobal@0.0.1 mquandalle:mousetrap-bindglobal@0.0.1
msavin:usercache@1.8.0 msavin:usercache@1.8.0
@ -219,7 +219,6 @@ url@1.3.2
useraccounts:core@1.14.2 useraccounts:core@1.14.2
useraccounts:flow-routing@1.14.2 useraccounts:flow-routing@1.14.2
useraccounts:unstyled@1.14.2 useraccounts:unstyled@1.14.2
verron:autosize@3.0.8
webapp@1.10.1 webapp@1.10.1
webapp-hashing@1.1.0 webapp-hashing@1.1.0
wekan-accounts-cas@0.1.0 wekan-accounts-cas@0.1.0

View file

@ -3,7 +3,7 @@ sudo: required
env: env:
TRAVIS_DOCKER_COMPOSE_VERSION: 1.24.0 TRAVIS_DOCKER_COMPOSE_VERSION: 1.24.0
TRAVIS_NODE_VERSION: 12.22.4 TRAVIS_NODE_VERSION: 12.22.8
TRAVIS_NPM_VERSION: latest TRAVIS_NPM_VERSION: latest
before_install: before_install:

View file

@ -39,7 +39,7 @@ host = https://www.transifex.com
# tap:i18n requires us to use `-` separator in the language identifiers whereas # 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 # Transifex uses a `_` separator, without an option to customize it on one side
# or the other, so we need to do a Manual mapping. # or the other, so we need to do a Manual mapping.
lang_map = ar_EG:ar-EG, bg_BG:bg, de_CH:de-CH, en_IT:en-IT, en_GB:en-GB, es_AR:es-AR, es_CL:es-CL, es_419:es-LA, es_PE:es-PE, es_MX:es-MX, es_TX:es-TX, es_PY:es-PY, el_GR:el, fa_IR:fa-IR, fi_FI:fi, hu_HU:hu, id_ID:id, mn_MN:mn, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, sl_SI:sl, zh_CN:zh-CN, zh_TW:zh-TW, zh_HK:zh-HK lang_map = ar_EG:ar-EG, bg_BG:bg, de_AT:de-AT, de_CH:de-CH, en_DE:en-DE, en_IT:en-IT, en_GB:en-GB, es_AR:es-AR, es_CL:es-CL, es_419:es-LA, es_PE:es-PE, es_MX:es-MX, es_TX:es-TX, es_PY:es-PY, el_GR:el-GR, fa_IR:fa-IR, fi_FI:fi, fr_FR:fr-FR, fr_CH:fr-CH, gu_IN:gu-IN, hi_IN:hi-IN, hu_HU:hu, id_ID:id, mn_MN:mn, ms_MY:ms-MY, lv_LV:lv, pt_BR:pt-BR, ro_RO:ro, sl_SI:sl, zh_CN:zh-CN, zh_TW:zh-TW, zh_Hans:zh-Hans, zh_HK:zh-HK
[wekan.application] [wekan.application]
file_filter = i18n/<lang>.i18n.json file_filter = i18n/<lang>.i18n.json

View file

@ -1,7 +1,717 @@
[Mac ChangeLog](https://github.com/wekan/wekan/wiki/Mac) [Mac ChangeLog](https://github.com/wekan/wekan/wiki/Mac)
Note: With Docker, please don't use latest tag. Only use release tags. # v5.85 2021-12-17 WeKan ® release
See https://github.com/wekan/wekan/issues/3874
This release adds the following updates:
- [Updated to Node.js v12.22.8](https://github.com/wekan/wekan/commit/5ad9ee1de6446e3b2f3e4a5df207d12de76e1b95).
Thanks to Node.js developers.
and fixes the following bugs:
- [Fix mobile card details for Modern Dark theme](https://github.com/wekan/wekan/pull/4240).
Thanks to jghaanstra.
- [Fixed undefinded added member to board](https://github.com/wekan/wekan/pull/4245).
Thanks to Emile840.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.84 2021-12-15 WeKan ® release
This release adds the following new features:
- [Kubernetes 1.22 support and basic helm test](https://github.com/wekan/wekan/pull/4208).
Thanks to varac.
- [Added Helm Chart usage docs](https://github.com/wekan/wekan/pull/4224).
Thanks to varac.
- [Add full name if exists in `email-invite-subject` for user to invite](https://github.com/wekan/wekan/pull/4226).
Thanks to Emile840.
- [Sort Organizations, Teams and People](https://github.com/wekan/wekan/pull/4232).
Thanks to Emile840.
and fixes the following bugs:
- [List title doesn't overlap with hamburger menu anymore](https://github.com/wekan/wekan/pull/4203).
Thanks to mfilser.
- [Fix legal notice traduction bug when refreshing sign in page](https://github.com/wekan/wekan/pull/4217).
Thanks to Emile840.
- [Fix: Clicking to view Lists or Swimlanes Archive adds temporarily many empty Lists to board](https://github.com/wekan/wekan/pull/4221).
Thanks to Ben0it-T.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.83 2021-11-30 WeKan ® release
This release adds to following new improvements:
- [Changed delete checklist dialog to a popup](https://github.com/wekan/wekan/pull/4200).
Thanks to mfilser.
- [Dragging minicards scrolls now vertically at the end of the screen](https://github.com/wekan/wekan/pull/4201).
Thanks to mfilser.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.82 2021-11-29 WeKan ® release
This release removes the following new features:
- [Revert change from WeKan v5.81: At Sandstorm, every WeKan user is now WeKan Admin and has Admin Panel](https://github.com/wekan/wekan/commit/ebc7741fcb9ad854234921ed0546255411adeec9).
Thanks to ocdtrekkie and xet7.
and adds the following new features:
- [List header contains now a button to add the card to the bottom of the list](https://github.com/wekan/wekan/pull/4195).
Thanks to mfilser.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.81 2021-11-29 WeKan ® release
This release adds the following new features:
- [At Sandstorm, every WeKan user is now WeKan Admin and has WeKan Admin Panel. This could help export, board member permissions, etc](https://github.com/wekan/wekan/commit/23a2e90f5f553c2051978a0b4cd5b0d6d4ee03da).
Thanks to PizzaProgram and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.80 2021-11-26 WeKan ® release
This release adds the following new features:
- [Show helper at label drag/drop if label popup opened from card details popup](https://github.com/wekan/wekan/pull/4176).
Thanks to mfilser.
- [Show or hide members and assignee(s) on minicard](https://github.com/wekan/wekan/pull/4179).
Thanks to Ben0it-T.
- [List adding has now a cancel button](https://github.com/wekan/wekan/pull/4183).
Thanks to mfilser.
- [CustomFields Currency, autofocus on edit](https://github.com/wekan/wekan/pull/4189).
Thanks to mfilser.
- [Attachments, show file size in KB in card details](https://github.com/wekan/wekan/pull/4191).
Thanks to mfilser.
- [Sidebar Member Settings Popup has now a Popup title](https://github.com/wekan/wekan/pull/4190).
Thanks to mfilser.
- [Add copy text button to most textarea fields](https://github.com/wekan/wekan/pull/4185).
Thanks to mfilser.
- Copy text button at most textarea fields is now translatable.
[Part 1](https://github.com/wekan/wekan/commit/5088c122536e13b44cf2fdbcfabeefd00cee332e),
[Part 2](https://github.com/wekan/wekan/commit/96465ac664c526d8749dcad158704b512317e256).
Thanks to xet7.
and adds the following updates:
- [Docker build script to be executeable](https://github.com/wekan/wekan/commit/8054f2b0025c4cb3f6a3ddf71754ae7c707d6ac0).
Thanks to xet7.
- [Drag drop jquery-ui update + screen and list scroll](https://github.com/wekan/wekan/pull/4181).
Thanks to mfilser.
- [Settings, add some space between radio buttons](https://github.com/wekan/wekan/pull/4186).
Thanks to mfilser.
and fixes the following bugs:
- [Default Top Left Corner Logo Image display few seconds before a display of custom Top Left Corner Logo Image](https://github.com/wekan/wekan/issues/4173).
Thanks to Emile840.
- [App reconnect link wasn't clickable](https://github.com/wekan/wekan/pull/4180).
Thanks to mfilser.
- [Copy card URL works now again](https://github.com/wekan/wekan/pull/4184).
Thanks to mfilser.
- [Fix: On mobile infinite scrolling didn't work](https://github.com/wekan/wekan/pull/4187).
Thanks to mfilser.
- [Custom Field StringTemplates didn't save the last input value on touch devices](https://github.com/wekan/wekan/pull/4188).
Thanks to mfilser.
- [Move cards to top/bottom ignores the current filter if active](https://github.com/wekan/wekan/pull/4192).
Thanks to mfilser.
- [Moving many cards with multi selection drag/drop to another list keeps the card order](https://github.com/wekan/wekan/pull/4193).
Thanks to mfilser.
- [Sidebar multi selection actions keep now the card sorting (cards moving, cards to archive etc)](https://github.com/wekan/wekan/pull/4194).
Thanks to mfilser.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.79 2021-11-25 WeKan ® release
This release fixes the following bugs:
- [Fix label width oversize bug](https://github.com/wekan/wekan/pull/4157).
Thanks to mfilser.
- [Fixed label popup at desktop view (add and remove labels)](https://github.com/wekan/wekan/pull/4170).
Thanks to mfilser.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.78 2021-11-17 WeKan ® release
This release fixes the following bugs:
- [Fix: Sandstorm WeKan Admin Panel version info broken](https://github.com/wekan/wekan/commit/02b6df320fc98e18e5a97105a35196bdffec98bb).
Thanks to ocdtrekkie and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.77 2021-11-16 WeKan ® release
This release adds the following updates:
- [Updated Docker Ubuntu base image](https://github.com/wekan/wekan/commit/b1b12b05b571f4eebd38e7486dea28dfd97a885d).
Thanks to Ubuntu developers.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.76 2021-11-16 WeKan ® release
This release adds the following new features:
- [Global search load card details](https://github.com/wekan/wekan/pull/4142).
Thanks to mfilser.
- [Layout improvement: Adding organisations to the board](https://github.com/wekan/wekan/pull/4143).
Thanks to Ben0it-T.
- [App reconnect is now possible if the connection was interrupted](https://github.com/wekan/wekan/pull/4147).
Thanks to mfilser.
- [Boards view has now drag handles at desktop view if drag handles are enabled](https://github.com/wekan/wekan/pull/4149).
Thanks to mfilser.
- [Account configuration of option loginExpirationInDays is now possible](https://github.com/wekan/wekan/pull/4150).
Thanks to mfilser.
- [Part 2: Added remaining of Account configuration of option loginExpirationInDays for Snap](https://github.com/wekan/wekan/commit/17d90684bb59fd4159f80b2da224638824151c6f).
Thanks to xet7.
- [Improve multi selection sidebar opening and closing](https://github.com/wekan/wekan/pull/4153).
Thanks to marook.
and adds the following updates:
- [Added release scripts for building local Docker images and pushing them to Quay.io and Docker Hub](https://github.com/wekan/wekan/commit/49c4dd8b14d9c13a9ae2aa18b37238a05ed41f92).
Thanks to xet7.
and fixes the following bugs:
- [Fixed trim whitespace at multiline editor fields](https://github.com/wekan/wekan/pull/4146).
Thanks to mfilser.
- [Fixed placeholder was not visible at list view (mobile view)](https://github.com/wekan/wekan/pull/4148).
Thanks to mfilser.
- [Fix list adding to bottom](https://github.com/wekan/wekan/pull/4152).
Thanks to mfilser.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.75 2021-11-12 WeKan ® release
This release adds the following new features:
- [Card popup close color remove move bottom delete](https://github.com/wekan/wekan/pull/4138).
Thanks to mfilser.
- [Comment edit has now a cancel button](https://github.com/wekan/wekan/pull/4139).
Thanks to mfilser.
- [Checklist and items drag drop scrollable mobile view](https://github.com/wekan/wekan/pull/4140).
Thanks to mfilser.
and adds the following updates:
- [Updated release scripts](https://github.com/wekan/wekan/commit/936d9fe30697e4651cba04d505393e05f8c902c1).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.74 2021-11-11 WeKan ® release
This release fixes the following bugs:
- [Docker fix failed export and timezone](https://github.com/wekan/wekan/pull/4137).
Thanks to mfilser.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.73 2021-11-11 WeKan ® release
This release adds the following new features:
- [Added NodeJS Statistics to Admin Panel/Versio](https://github.com/wekan/wekan/pull/4118).
Thanks to Ben0it-T.
- [Card detail popup loads now comments if opened from board search](https://github.com/wekan/wekan/pull/4128).
Thanks to mfilser.
and adds the following updates:
- Updated dependencies
[Part 1](https://github.com/wekan/wekan/commit/cf6713a31c9f6ce9d30832ee6bf6c95d35d7044b),
[Part 2](https://github.com/wekan/wekan/commit/ac7ef4d4cd7179a140f0c56c7c7d1ffc33e75fbe).
Thanks to developers of dependencies.
and fixes the following bugs:
- [Card Details, add missing hr line before Activity title](https://github.com/wekan/wekan/pull/4117).
Thanks to Ben0it-T.
- [Sidebar search only opens the card as popup on mobile view](https://github.com/wekan/wekan/pull/4122).
Thanks to mfilser.
- [Fixed a bug related to the default text of the OIDC button](https://github.com/wekan/wekan/pull/4132).
Thanks to Emile840.
- [Fix: Impossible to export board to excel where title exceeding 31 chars](https://github.com/wekan/wekan/pull/4135).
Thanks to Ben0it-T.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.72 2021-10-31 WeKan ® release
This release adds the following new features:
- [Add a possibility for non-admin users (who have an email on a given domain name in Admin Panel) to invite new users for registration](https://github.com/wekan/wekan/pull/4107).
Thanks to Emile840.
and fixes the following bugs:
- [Try to fix: Filter List by Title - Hide empty lists in Swimlane view](https://github.com/wekan/wekan/pull/4108).
Thanks to Ben0it-T.
- [Card labels on minicard withouth text are now at the same line again](https://github.com/wekan/wekan/pull/4109).
Thanks to mfilser.
- [Rename "Domaine" to "Domain" that is more like English](https://github.com/wekan/wekan/commit/c136033c1fb25688d310b1b62841003f3901641a).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.71 2021-10-29 WeKan ® release
This release adds the following updates:
- [Updated dependencies](https://github.com/wekan/wekan/commit/df2a2aae1d44ba22563cc28bc8d9baac71b2ced7).
Thanks to developers of dependencies.
and fixes the following bugs:
- [Fix: Filter List by Card Title](https://github.com/wekan/wekan/pull/4105).
Thanks to Ben0it-T.
- Add info about upgrades to GitHub issue template.
[Part 1](https://github.com/wekan/wekan/commit/46a5eec7d21b66eb1aacac4fec84a0d0a0f4d16b),
[Part 2](https://github.com/wekan/wekan/commit/7cc35970a849c19d35b89cf0a5fb91216a66fcb3).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.70 2021-10-28 WeKan ® release
This release fixes the following bugs:
- [Fix bug related to Admin Panel teams management](https://github.com/wekan/wekan/pull/4103).
Thanks to Emile840.
- Docker: Try to fix "Failed export and unexpected container restart". Added timezone and localtime.
[Part 1](https://github.com/wekan/wekan/commit/ec33d0b34f3abe5634be0b87f03314c738c771d1),
[Part 2](https://github.com/wekan/wekan/commit/e3292dd5627f95d59d130a8c1b9a62df317ae6bd).
Thanks to akitzing, mfilser and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.69 2021-10-28 WeKan ® release
This release adds the following updates:
- [Updated Docker base image to Ubuntu 21.10 Impish](https://github.com/wekan/wekan/commit/5411113544f040cab2df86234745e4846029660f).
Thanks to Ubuntu developers.
and fixes the following bugs:
- [Fix Docs: Only MAIL_URL and MAIL_FROM for email settings. Not Admin Panel anymore](https://github.com/wekan/wekan/commit/d9adce7b676b705da786eb44cd2c2c4dba120d30).
Thanks to niklasdahlheimer.
- [Popup fixes: Archive cards, upload attachements etc](https://github.com/wekan/wekan/pull/4101).
Thanks to mfilser.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.68 2021-10-27 WeKan ® release
This release adds the following new features:
- [Labels are now drag/drop/sortable](https://github.com/wekan/wekan/pull/4084).
Thanks to mfilser.
and fixes the following bugs:
- [Fix labels desktop view add and delete](https://github.com/wekan/wekan/pull/4087).
Thanks to mfilser.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.67 2021-10-27 WeKan ® release
This release fixes the following bugs:
- [Fix typo](https://github.com/wekan/wekan/commit/cb9b8d4f2b8e24475a2aafd6f9653f28f305eefb).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.66 2021-10-27 WeKan ® release
This release adds the following new features:
- [api.py: List All Public Boards](https://github.com/wekan/wekan/commit/eac102dbbf302ccc121bbf1e4e8faf115e1f9da8).
Thanks to xet7.
- [api.py: List Custom Fields of Board](https://github.com/wekan/wekan/commit/bcf35731316c327090a8513a4c4094e32e301e3f).
Thanks to xet7.
- [api.py: Info of one Custom Field](https://github.com/wekan/wekan/commit/5c571ca8638c29e558f3a196daf5458274eb715e).
Thanks to xet7.
- [api.py: Add Custom Fields to Board. Does not work yet, error: Settings must be object](https://github.com/wekan/wekan/commit/3921209c9fbf1d908f2ef3e97dade5863a000309).
Thanks to xet7.
- [Add full name if exists in email-invite-subject or when tagging someone with `@` while commenting a card](https://github.com/wekan/wekan/pull/4057).
Thanks to Emile840.
- [Popup sorting number](https://github.com/wekan/wekan/pull/4060).
Thanks to mfilser.
- [At mobile view the card details are opened as Popup](https://github.com/wekan/wekan/pull/4062).
Thanks to mfilser.
- [Add card button has now a cancel button](https://github.com/wekan/wekan/pull/4067).
Thanks to mfilser.
- [Global search checklistitems and custom fields boolean](https://github.com/wekan/wekan/pull/4074).
Thanks to mfilser.
- [Board View, sort cards button also in mobile view](https://github.com/wekan/wekan/pull/4076).
Thanks to mfilser.
- [Minicard label popup](https://github.com/wekan/wekan/pull/4079).
Thanks to mfilser.
- [Re-enables custom schemes auto linking](https://github.com/wekan/wekan/commit/f67a174c4a7706a2d419ba3dd43d696104f90696).
Thanks to chrisi51.
- [Board search remove limit](https://github.com/wekan/wekan/pull/4082).
Thanks to mfilser.
- [Add a possibility of selecting displayed users in Admin Panel](https://github.com/wekan/wekan/pull/4083).
Thanks to Emile840.
and adds the following updates:
- Updated dependencies.
[Part 1](https://github.com/wekan/wekan/commit/f14e710ac0d5381ec092c9f383b9b68f446cab4d),
[Part 2](https://github.com/wekan/wekan/commit/156c0b5d4d91dae2ee9b12ed8c312dc19a3c3075).
Thanks to developers of dependencies.
- [Added npm publish script for releases](https://github.com/wekan/wekan/commit/2666b30ba911da8502153be5827f277b81354f8b).
Thanks to xet7.
and fixes the following bugs:
- [Fix infinite loading of public boards](https://github.com/wekan/wekan/pull/4053).
Thanks to mfilser.
- [Fix: Setting overtime not working](https://github.com/wekan/wekan/pull/4056).
Thanks to Ben0it-T.
- [Fix main scrollbar](https://github.com/wekan/wekan/pull/4063).
Thanks to mfilser.
- [Try to fix orphanedAttachments](https://github.com/wekan/wekan/commit/6a06522777a0bfa2f758e96c2d25e1237a7b43dc).
Thanks to Madko and xet7.
- [Fix markdown header quick access](https://github.com/wekan/wekan/pull/4065).
Thanks to mfilser.
- [Fix Filter List by Card Title](https://github.com/wekan/wekan/pull/4066).
Thanks to Ben0it-T.
- [Fix long textarea editing](https://github.com/wekan/wekan/pull/4068).
Thanks to mfilser.
- [Boards weren't loaded because of missing filter](https://github.com/wekan/wekan/pull/4069).
Thanks to mfilser.
- [Fix Card details Custom Fields popup empty hr sections and plus icon](https://github.com/wekan/wekan/pull/4070).
Thanks to mfilser.
- [Card popup search and global search improvements](https://github.com/wekan/wekan/pull/4071).
Thanks to mfilser.
- [Comment out showing Search All Boards logs in console](https://github.com/wekan/wekan/commit/a62a177fb1cdf8b823b5c32380a81e803e0049e7).
Thanks to mfilser and xet7.
- [Long labels on card and minicard are wrapped if too long](https://github.com/wekan/wekan/pull/4073).
Thanks to mfilser.
- [Card dates, if deleted rules didn't apply on "unset date fields"](https://github.com/wekan/wekan/pull/4075).
Thanks to mfilser.
- [Comment, added confirm delete popup](https://github.com/wekan/wekan/pull/4077).
Thanks to mfilser.
- [Fix: Filter List by Card Title](https://github.com/wekan/wekan/pull/4078).
Thanks to Ben0it-T.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.65 2021-10-12 WeKan ® release
This release adds to following CRITICAL SECURITY UPDATES:
- [Updated to Node.js v12.22.7](https://github.com/wekan/wekan/commit/64fc2e5d8fe50115175d44c01f7fca4e668c7231).
Thanks to Node.js developers.
and fixes the following bugs:
- [Excel Export: Export only comments for cards that are not linked](https://github.com/wekan/wekan/pull/4047).
Thanks to Ben0it-T.
- [If OIDC button text was customized, the default text will be added if a user click on `Sign In`](https://github.com/wekan/wekan/pull/4052).
Thanks to Emile840.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.64 2021-10-09 WeKan ® release
This release adds the following new features:
- [Excel Export : add board description, add comments worksheet](https://github.com/wekan/wekan/pull/4045).
Thanks to Ben0it-T.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.63 2021-10-07 Wekan release
This release adds the following new features:
- [Allow setting custom kubernetes labels when using the helm chart](https://github.com/wekan/wekan/pull/4031).
Thanks to ariep.
and fixes the following bugs:
- [Fixed SMTP by reverting MAIL_SERVICE changes](https://github.com/wekan/wekan/commit/9c99c5c3ae8d291df5305b3b6cd1825fc5cc2c21).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.62 2021-10-04 Wekan release
This release adds the following new features:
- [Allow word match for rules -> title filter](https://github.com/wekan/wekan/pull/4025).
Thanks to ilvar.
- [CSV/TSV/Excel Export translatable and fixed, CSV semicolon option added](https://github.com/wekan/wekan/pull/4028).
Thanks to Ben0it-T.
- Added week numbers to dates at card, minicard, Custom Field dates, DatePicker and Calendar.
[Part 1](https://github.com/wekan/wekan/commit/d06ac09485dafb0256ae7fbe613ab2dbe00b70f3),
[Part 2](https://github.com/wekan/wekan/commit/9e6744d1e33b37e0d23eea5869ccac3ff37f7d53).
Thanks to xet7.
- [Confirm Archive Card](https://github.com/wekan/wekan/commit/6c3fcdcc4c446fd4c8dc4dca1b2846f6e3ea72e4).
Thanks to xet7.
and fixes the following bugs:
- [Clean up /tmp after Docker build. This drastically reduces docker image size from ~280 MB to ~180 MB](https://github.com/wekan/wekan/pull/4026).
Thanks to ilvar.
- [Removed extra quotes from Export menu](https://github.com/wekan/wekan/commit/553652556468ac88c0691d4d688d5a922ef6a0c2).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.61 2021-09-25 Wekan release
This release adds the following new features:
- [Search by name or username or emails address when adding a new user to a board](https://github.com/wekan/wekan/pull/4018).
Thanks to Emile840.
and fixes the following bugs:
- [Fixed REST API, it shoud work now by Admin user](https://github.com/wekan/wekan/commit/e3a0dea85fa1f8e2f580f419b30cf5f36775d731).
Reverted [Allow board members to use more of API of Wekan v5.35](https://github.com/wekan/wekan/commit/a719e8fda1f78bcbf9af6e7b4341f8be1d141e90).
Thanks to tomhughes and xet7.
- [Wekan Gantt GPL: Fix Tasks not displayed in Gantt screen](https://github.com/wekan/wekan-gantt-gpl/commit/72d464f5eb55501f08eb0cfd31fd5340380d7f3b).
Thanks to MrLovegreen and khjde1207.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.60 2021-09-22 Wekan release
This release adds the following new features:
- [Toggle opened card Custom Fields layout between Grid and one per row](https://github.com/wekan/wekan/commit/fc2fb9a081021663cc822bf2a687fda74cd0afa6).
Thanks to xet7.
and adds the following updates:
- [Updated Docker base image to newer Ubuntu](https://github.com/wekan/wekan/commit/442e6bf983ada47c26a15dbc1982c554118fa84d).
Thanks to xet7.
- [Try to add Docker image to GitHub Docker Image Registry](https://github.com/wekan/wekan/commit/70ba1eca787671879215726c16335a84e2b636c9).
Thanks to xet7.
- [Update build scripts to install npm from NodeSource, and meteor with npm](https://github.com/wekan/wekan/commit/c062621dd5486b60bdd200a9279a38b98fc0d410).
Thanks to Meteor developers.
and fixes the following bugs:
- [Try to fix Bug: Card number equal to #0 when creating a sub-task from a card](https://github.com/wekan/wekan/commit/4c659da5334641f558e77285f7ca47e562f7c853).
Thanks to marcungeschikts, olivierlambert and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.59 2021-09-17 Wekan release
This release adds the following new features:
- [Admin Panel/People: Possibility of adding a team to all selected users](https://github.com/wekan/wekan/pull/3996).
Thanks to Emile840.
- [Add / remove team members as board members when adding / removing team from board](https://github.com/wekan/wekan/pull/4000).
Thanks to Emile840.
- [Added more translations to: Admin Panel/People: Possibility of adding a team to all selected users](https://github.com/wekan/wekan/commit/3d9b7eb7ab41c6450b473f6f349d894f516c5487).
Thanks to xet7.
- [Enter new password 2 times when registering](https://github.com/wekan/wekan/commit/0da84f8f3eb91c5bf726e058f5ec74a7891d734b).
Thanks to sh2515 and xet7.
- Sum of cards. In Progress, not ready yet.
[Part 1: Add Custom Field options for field sum](https://github.com/wekan/wekan/commit/8626b466b830adf6c671211bbd61b53b96ac5a49).
[Part 2: Show option for custom field sum only for currency and number custom fields](https://github.com/wekan/wekan/commit/9bee6ae6663a5e1c974de2811f6a5fdd2d66efe5).
Thanks to xet7.
- [Admin Panel/Settings/Layout: Customize OIDC button text](https://github.com/wekan/wekan/pull/4011).
Thanks to Emile840.
- [At card attachments, show play and fullscreen controls for video webm/mp4/ogg, and play controls for audio mp3/ogg](https://github.com/wekan/wekan/commit/bd9fbedbf9fbe0181913876b930b335261cd2a0a).
Thanks to luistiktok and xet7.
and fixes the following bugs:
- [Links to devel branch are broken; use master instead](https://github.com/wekan/wekan/pull/3993).
Thanks to garrison.
- [Fix first user creation for via OIDC](https://github.com/wekan/wekan/pull/3994).
Thanks to ww-daniel-mora.
- [When list has just one card, to show 'card' instead of 'cards'](https://github.com/wekan/wekan/pull/3999).
Thanks to helioguardabaxo.
- [Fix: Linked card cannot change date](https://github.com/wekan/wekan/pull/4002).
Thanks to Ben0it-T.
- [Try to fix: Can't delete attachment](https://github.com/wekan/wekan/commit/889ec1339a025a68ec919f059b9d58e8d94a3376).
Thanks to luistiktok and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.58 2021-09-01 Wekan release
This release fixes the following bugs:
- [1) Edit profile and modify password menus are not displayed if SSO authentication is used.
2) Board filtering will be displayed only if user belongs to atleast one team or
organization](https://github.com/wekan/wekan/pull/3983).
Thanks to Emile840.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.57 2021-08-31 Wekan release
This release adds the following updates:
- [Updated build scripts](https://github.com/wekan/wekan/commit/52fafe997659e933e403acb0ee0cffc99f74e35f).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.56 2021-08-31 Wekan release
This release adds the following updates:
- [Updated dependencies](https://github.com/wekan/wekan/commit/858967f4200783cadaa62d0e3436f661c772ede7).
Thanks to developers of dependencies.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.55 2021-08-31 Wekan release
This release adds to following CRITICAL SECURITY UPDATES:
- [Updated to Node.js v12.22.6](https://github.com/wekan/wekan/commit/48636892489dd01c6f6b930bafb94651c00859d8).
Thanks to Node.js developers.
and fixes the following bugs:
- [Fixed bugs](https://github.com/wekan/wekan/pull/3981):
1) Public Boards page shows only "Add Board" button, not any Public Boards.
2) When at Admin Panel / Boards visibility / Private only, public board still accessible publicly by it's
public board URL.
Thanks to Emile840.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.54 2021-08-28 Wekan release
This release adds the following new features:
- [Admin panel: Added a parameter to display or not the visibility of a board in private mode only](https://github.com/wekan/wekan/pull/3976).
Thanks to Emile840.
and fixes the following bugs:
- [Fix: Incorrect card numbers for sub tasks](https://github.com/wekan/wekan/pull/3977).
Thanks to syndimann.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.53 2021-08-27 Wekan release
This release fixes the following bugs:
- [Try to fix MAIL_FROM](https://github.com/wekan/wekan/commit/787df044190915c46e22159f3c40fb611846dc07).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.52 2021-08-26 Wekan release
This release adds the following new features:
- Added MAIL_SERVICE settings for Well Known Email Services
[Part 1](https://github.com/wekan/wekan/commit/ab8e56e16a02ef0afb7b4023a43b4adf2558a8ff),
[Part 2](https://github.com/wekan/wekan/commit/1fadf204c2d5fa96ea41b9cb39f003cc05e2fe46).
https://github.com/wekan/wekan/wiki/Troubleshooting-Mail . Please test.
Thanks to xet7.
- [All Boards page: Possibility of filtering board by team or organization](https://github.com/wekan/wekan/pull/3964).
Thanks to Emile840.
- [Fixed translation of "Clear Filter" for "All boards page: Possibility of filtering board by team or organization"](https://github.com/wekan/wekan/commit/b36a7621e0feca5c22fc4a24eceba1a9fc584ab0).
Thanks to xet7.
and adds the following new translations:
- [Added Chinese (Simplified) (zh-Hans or zh-CN)](https://github.com/wekan/wekan/commit/f2c242f49e18e2197f1f90c9b2dac5934a08325d).
Thanks to translators.
and fixes the following bugs:
- [Initials not required for new user that is created at Admin Panel](https://github.com/wekan/wekan/commit/9c7c481f48cb66406715f7571439f9d7fa332b87).
Thanks to xet7.
- [Delete user is now possible at Admin Panel](https://github.com/wekan/wekan/commit/7808fdd22f04cc482b7df21187aaf3e9623f19e6).
But you should remove user first from all boards, because otherwise there could be
bug of empty avatars at boards, that need to be removed manually from database.
Thanks to xet7.
- [Fixed Save button not clickable in maximized card view](https://github.com/wekan/wekan/commit/a59932af00c066871102970d252b78d262d06fa0).
Thanks to hatl, urmel1960 and syndimann.
- [Fixed New wide card edit view is all jumbled on mobile](https://github.com/wekan/wekan/commit/241eb9df0fb446b3775704848281b0cc032c4921).
Thanks to jdaviescoates and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.51 2021-08-17 Wekan release
This release fixes the following bugs:
- [Fixed exception in global search](https://github.com/wekan/wekan/pull/3949).
Thanks to syndimann.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.50 2021-08-15 Wekan release
This release fixes the following bugs:
- [Fix: Save user initials and fullname when a new user is created](https://github.com/wekan/wekan/pull/3946).
Thanks to syndimann.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.49 2021-08-14 Wekan release
This release adds the following new features:
- [Text "Search" now translatable at Card Add Member/Assignee](https://github.com/wekan/wekan/commit/9ce65c601a875a4259fb69fdda45124b8412ae6f).
Thanks to xet7.
- [Add Card Comment Reactions](https://github.com/wekan/wekan/pull/3945).
Thanks to syndimann.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.48 2021-08-11 Wekan release
This release adds the following CRITICAL SECURITY UPDATES:
- [Updated to Node.js v12.22.5](https://github.com/wekan/wekan/commit/91cad7b49e25cecdf417321dadcdd9ea5cd8b020).
Thanks to Node.js developers.
- Also jszip update in some of included update commits.
and adds the following new features:
- [Searchfields for members and assignees card popups](https://github.com/wekan/wekan/pull/3942).
Thanks to syndimann.
and adds the following updates:
- [Updated dependencies](https://github.com/wekan/wekan/commit/b3cc01b04167bd67dde02c6c899baf8917ae09c1).
Thanks to developers of dependencies.
and adds the following new translations:
- [French (Switzerland) (fr_CH)](https://github.com/wekan/wekan/commit/23c70ac252494b464cd2a268d7e680370775ddc4).
Thanks to translators.
and fixes the following bugs:
- [Fixed: Can't save user without Initials](https://github.com/wekan/wekan/commit/9a03654062f9c8ac7aac257f11b386a054cd39e7).
Thanks to devagleo and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v5.47 2021-08-05 Wekan release # v5.47 2021-08-05 Wekan release
@ -5578,7 +6288,7 @@ This release adds the following new features:
and fixes the following bugs: and fixes the following bugs:
- Revert [Sandstorm API changes](https://github.com/wekan/wekan/commit/be03a191c4321c2f80116c0ee1ae6c826d882535) - Revert [Sandstorm API changes](https://github.com/wekan/wekan/commit/be03a191c4321c2f80116c0ee1ae6c826d882535)
that were done at [Wekan v2.05](https://github.com/wekan/wekan/blob/devel/CHANGELOG.md#v205-2019-01-27-wekan-release) that were done at [Wekan v2.05](https://github.com/wekan/wekan/blob/master/CHANGELOG.md#v205-2019-01-27-wekan-release)
to fix #2143. Thanks to pantraining and xet7. to fix #2143. Thanks to pantraining and xet7.
Thanks to above GitHub users and translators for contributions. Thanks to above GitHub users and translators for contributions.
@ -5725,7 +6435,7 @@ Update translations. Thanks to translators.
This release adds the following new features: This release adds the following new features:
- [IFTTT Rules improvements](https://github.com/wekan/wekan/pull/2088). Thanks to Angtrim. - [IFTTT Rules improvements](https://github.com/wekan/wekan/pull/2088). Thanks to Angtrim.
- Add [find.sh](https://github.com/wekan/wekan/blob/devel/find.sh) bash script that ignores - Add [find.sh](https://github.com/wekan/wekan/blob/master/find.sh) bash script that ignores
extra directories when searching. xet7 uses this a lot when developing. Thanks to xet7. extra directories when searching. xet7 uses this a lot when developing. Thanks to xet7.
Thanks to above GitHub users for their contributions. Thanks to above GitHub users for their contributions.
@ -7236,7 +7946,7 @@ This release adds the following new features:
- [Checklist templates](https://github.com/wekan/wekan/pull/1470); - [Checklist templates](https://github.com/wekan/wekan/pull/1470);
- Added [Finnish language changelog](https://github.com/wekan/wekan/tree/devel/meta/t9n-changelog) - Added [Finnish language changelog](https://github.com/wekan/wekan/tree/devel/meta/t9n-changelog)
and [more Finnish traslations](https://github.com/wekan/wekan/blob/devel/sandstorm-pkgdef.capnp) and [more Finnish traslations](https://github.com/wekan/wekan/blob/master/sandstorm-pkgdef.capnp)
to Sandstorm. to Sandstorm.
Thanks to GitHub users erikturk and xet7 for their contributions. Thanks to GitHub users erikturk and xet7 for their contributions.

View file

@ -1,8 +1,8 @@
FROM quay.io/wekan/ubuntu:groovy-20210115 FROM quay.io/wekan/ubuntu:impish-20211102
LABEL maintainer="wekan" LABEL maintainer="wekan"
# 2020-12-03: # 2021-09-18:
# - Above Ubuntu base image copied from Docker Hub ubuntu:groovy-20201125.2 # - Above Ubuntu base image copied from Docker Hub ubuntu:hirsute-20210825
# to Quay to avoid Docker Hub rate limits. # to Quay to avoid Docker Hub rate limits.
# Set the environment variables (defaults where required) # Set the environment variables (defaults where required)
@ -12,7 +12,7 @@ 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 libarchive-tools gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates python3" \
DEBUG=false \ DEBUG=false \
NODE_VERSION=v12.22.4 \ NODE_VERSION=v12.22.8 \
METEOR_RELEASE=1.10.2 \ METEOR_RELEASE=1.10.2 \
USE_EDGE=false \ USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \ METEOR_EDGE=1.5-beta.17 \
@ -28,6 +28,7 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 \ ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 \
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 \ ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 \
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 \ ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 \
ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS=90 \
RICHER_CARD_COMMENT_EDITOR=false \ RICHER_CARD_COMMENT_EDITOR=false \
CARD_OPENED_WEBHOOK_ENABLED=false \ CARD_OPENED_WEBHOOK_ENABLED=false \
ATTACHMENTS_STORE_PATH="" \ ATTACHMENTS_STORE_PATH="" \
@ -309,6 +310,7 @@ RUN \
apt-get remove --purge -y ${BUILD_DEPS} && \ apt-get remove --purge -y ${BUILD_DEPS} && \
apt-get autoremove -y && \ apt-get autoremove -y && \
npm uninstall -g api2html &&\ npm uninstall -g api2html &&\
rm -R /tmp/* && \
rm -R /var/lib/apt/lists/* && \ rm -R /var/lib/apt/lists/* && \
rm -R /home/wekan/.meteor && \ rm -R /home/wekan/.meteor && \
rm -R /home/wekan/app && \ rm -R /home/wekan/app && \

View file

@ -4,7 +4,7 @@ FROM amd64/alpine:3.7 AS builder
ENV QEMU_VERSION=v4.2.0-6 \ ENV QEMU_VERSION=v4.2.0-6 \
QEMU_ARCHITECTURE=aarch64 \ QEMU_ARCHITECTURE=aarch64 \
NODE_ARCHITECTURE=linux-arm64 \ NODE_ARCHITECTURE=linux-arm64 \
NODE_VERSION=v12.22.4 \ NODE_VERSION=v12.22.8 \
WEKAN_VERSION=latest \ WEKAN_VERSION=latest \
WEKAN_ARCHITECTURE=arm64 WEKAN_ARCHITECTURE=arm64
@ -40,7 +40,7 @@ LABEL maintainer="wekan"
# Set the environment variables (defaults where required) # Set the environment variables (defaults where required)
ENV QEMU_ARCHITECTURE=aarch64 \ ENV QEMU_ARCHITECTURE=aarch64 \
NODE_ARCHITECTURE=linux-arm64 \ NODE_ARCHITECTURE=linux-arm64 \
NODE_VERSION=v12.22.4 \ NODE_VERSION=v12.22.8 \
NODE_ENV=production \ NODE_ENV=production \
NPM_VERSION=latest \ NPM_VERSION=latest \
WITH_API=true \ WITH_API=true \

View file

@ -1,6 +1,6 @@
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/wekan/wekan) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/wekan/wekan)
# Wekan - Open Source kanban # WeKan ® - Open Source kanban
[![Contributors](https://img.shields.io/github/contributors/wekan/wekan.svg "Contributors")](https://github.com/wekan/wekan/graphs/contributors) [![Contributors](https://img.shields.io/github/contributors/wekan/wekan.svg "Contributors")](https://github.com/wekan/wekan/graphs/contributors)
[![Docker Repository on Quay](https://quay.io/repository/wekan/wekan/status "Docker Repository on Quay")](https://quay.io/repository/wekan/wekan) [![Docker Repository on Quay](https://quay.io/repository/wekan/wekan/status "Docker Repository on Quay")](https://quay.io/repository/wekan/wekan)
@ -14,19 +14,19 @@
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwekan%2Fwekan.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_shield) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwekan%2Fwekan.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwekan%2Fwekan?ref=badge_shield)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4619/badge)](https://bestpractices.coreinfrastructure.org/projects/4619) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4619/badge)](https://bestpractices.coreinfrastructure.org/projects/4619)
## [Translate Wekan at Transifex](https://transifex.com/wekan/wekan) ## [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. Translations to non-English languages are accepted only at [Transifex](https://transifex.com/wekan/wekan) using webbrowser.
New English strings of new features can be added as PRs to edge branch file wekan/i18n/en.i18n.json . 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. It's better than at chat where details get lost when chat scrolls up.
## Chat ## Chat
[Discussions][discussions] - Wekan Community GitHub Discussions, that are not [Feature Requests and Bugs](https://github.com/wekan/wekan/issues). [Discussions][discussions] - WeKan Community GitHub Discussions, that are not [Feature Requests and Bugs](https://github.com/wekan/wekan/issues).
[Wekan IRC FAQ](https://github.com/wekan/wekan/wiki/IRC-FAQ) [Wekan IRC FAQ](https://github.com/wekan/wekan/wiki/IRC-FAQ)
@ -41,9 +41,9 @@ See https://github.com/wekan/wekan/issues/3874
- Please read the [FAQ](https://github.com/wekan/wekan/wiki/FAQ) first - 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](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 :)
## 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. collaborative kanban board application with MIT license.
Whether youre maintaining a personal todo list, planning your holidays with some friends, Whether youre maintaining a personal todo list, planning your holidays with some friends,
@ -51,58 +51,58 @@ 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, 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. and make you productive by allowing you to focus on the few items that matter the most.
Since Wekan is a free software, you dont have to trust us with your data and can Since WeKan ® is a free software, you dont have to trust us with your data and can
install Wekan on your own computer or server. In fact we encourage you to do install Wekan on your own computer or server. In fact we encourage you to do
that by providing one-click installation on various platforms. that by providing one-click installation on various platforms.
- Wekan is used in [most countries of the world](https://snapcraft.io/wekan). - 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 largest user has 13k users using Wekan in their company.
- Wekan has been [translated](https://transifex.com/wekan/wekan) to about 63 languages. - Wekan has been [translated](https://transifex.com/wekan/wekan) to about 70 languages.
- [Features][features]: Wekan has real-time user interface. - [Features][features]: WeKan ® has real-time user interface.
- [Platforms][platforms]: Wekan supports many platforms. - [Platforms][platforms]: WeKan ® supports many platforms.
Wekan is critical part of new platforms Wekan is currently being integrated to. WeKan ® is critical part of new platforms Wekan is currently being integrated to.
## Requirements ## Requirements
- 64bit: Linux [Snap](https://github.com/wekan/wekan-snap/wiki/Install) or [Sandstorm](https://sandstorm.io) / - 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). [Mac](https://github.com/wekan/wekan/wiki/Mac) / [Windows](https://github.com/wekan/wekan/wiki/Install-Wekan-from-source-on-Windows).
[More Platforms](https://github.com/wekan/wekan/wiki/Platforms), bundle for RasPi3 ARM and other CPUs where Node.js and MongoDB exists. [More Platforms](https://github.com/wekan/wekan/wiki/Platforms), 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. - 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, 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. 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. - 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. - 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. 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. 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). - [Reporting all new bugs immediately](https://github.com/wekan/wekan/issues).
New features and fixes are added to Wekan [many times a day](https://github.com/wekan/wekan/blob/devel/CHANGELOG.md). 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. - [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. 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 and Demo
[Roadmap][roadmap_wekan] - Public read-only board at Wekan demo. [Roadmap][roadmap_wekan] - Public read-only board at WeKan ® demo.
[Developer Documentation][dev_docs] [Developer Documentation][dev_docs]
- There is many companies and individuals contributing code to Wekan, to add features and bugfixes - 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). [many times a day](https://github.com/wekan/wekan/blob/master/CHANGELOG.md).
- [Please add Add new Feature Requests and Bug Reports immediately](https://github.com/wekan/wekan/issues). - [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/commercial-support/).
We also welcome sponsors for features and bugfixes. 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.
## Screenshot ## Screenshot
[More screenshots at Features page](https://github.com/wekan/wekan/wiki/Features) [More screenshots at Features page](https://github.com/wekan/wekan/wiki/Features)
[![Screenshot of Wekan][screenshot_wekan]][roadmap_wekan] [![Screenshot of WeKan ®][screenshot_wekan]][roadmap_wekan]
## License ## 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). with [Meteor](https://www.meteor.com).
[platforms]: https://github.com/wekan/wekan/wiki/Platforms [platforms]: https://github.com/wekan/wekan/wiki/Platforms

View file

@ -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 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 Wekan uses xss package for input fields like cards, as you can see from
[package.json](https://github.com/wekan/wekan/blob/devel/package.json). Other used versions can be seen 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/devel/.meteor/versions). [Meteor versions file](https://github.com/wekan/wekan/blob/master/.meteor/versions).
Forms can include markdown links, html, image tags etc like you see at https://wekan.github.io . 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. 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 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), [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, [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/devel/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/master/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/). like for example [AWS WAF](https://aws.amazon.com/waf/).
[All Wekan Platforms](https://github.com/wekan/wekan/wiki/Platforms) [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: Typical already known or "no impact" bugs such as:
- Brute force password guessign. Currently there is - Brute force password guessign. Currently there is
[brute force protection with eluck:accounts-lockout](https://github.com/wekan/wekan/blob/devel/CHANGELOG.md#v080-2018-04-04-wekan-release). [brute force protection with eluck:accounts-lockout](https://github.com/wekan/wekan/blob/master/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 - 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. 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) Upgrading [has been tried many times, spending a lot of time](https://github.com/meteor/meteor/issues/9609)

View file

@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928 appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v5.47.0" appVersion: "v5.85.0"
files: files:
userUploads: userUploads:
- README.md - README.md

113
api.py
View file

@ -5,6 +5,9 @@
# Wekan API Python CLI, originally from here, where is more details: # Wekan API Python CLI, originally from here, where is more details:
# https://github.com/wekan/wekan/wiki/New-card-with-Python3-and-REST-API # 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: try:
# python 3 # python 3
from urllib.parse import urlencode from urllib.parse import urlencode
@ -23,12 +26,16 @@ if arguments == 0:
print("AUTHORID is USERID that writes card.") print("AUTHORID is USERID that writes card.")
print("If *nix: chmod +x api.py => ./api.py users") print("If *nix: chmod +x api.py => ./api.py users")
print("Syntax:") print("Syntax:")
print(" python3 api.py users # All users") print(" python3 api.py users # All users")
print(" python3 api.py boards USERID # Boards of USERID") print(" python3 api.py boards # All Public Boards")
print(" python3 api.py board BOARDID # Info of BOARDID") print(" python3 api.py boards USERID # Boards of USERID")
print(" python3 api.py swimlanes BOARDID # Swimlanes of BOARDID") print(" python3 api.py board BOARDID # Info of BOARDID")
print(" python3 api.py lists BOARDID # Lists of BOARDID") print(" python3 api.py customfields BOARDID # Custom Fields of BOARDID")
print(" python3 api.py list BOARDID LISTID # Info of LISTID") print(" python3 api.py customfield BOARDID CUSTOMFIELDID # Info of CUSTOMFIELDID")
print(" python3 api.py addcustomfieldtoboard AUTHORID BOARDID NAME TYPE SETTINGS SHOWONCARD AUTOMATICALLYONCARD SHOWLABELONMINICARD SHOWSUMATTOPOFLIST # Add Custom Field to Board")
print(" python3 api.py swimlanes BOARDID # Swimlanes of BOARDID")
print(" python3 api.py lists BOARDID # Lists of BOARDID")
print(" python3 api.py list BOARDID LISTID # Info of LISTID")
print(" python3 api.py createlist BOARDID LISTTITLE # Create list") print(" python3 api.py createlist BOARDID LISTTITLE # Create list")
print(" python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION") print(" python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION")
print(" python3 api.py editcard BOARDID LISTID CARDID NEWCARDTITLE NEWCARDDESCRIPTION") print(" python3 api.py editcard BOARDID LISTID CARDID NEWCARDTITLE NEWCARDDESCRIPTION")
@ -65,12 +72,15 @@ chmod +x api.py
=== Wekan API Python CLI: Shows IDs for addcard === === Wekan API Python CLI: Shows IDs for addcard ===
AUTHORID is USERID that writes card. AUTHORID is USERID that writes card.
Syntax: Syntax:
python3 api.py users # All users python3 api.py users # All users
python3 api.py boards USERID # Boards of USERID python3 api.py boards USERID # Boards of USERID
python3 api.py board BOARDID # Info of BOARDID python3 api.py board BOARDID # Info of BOARDID
python3 api.py swimlanes BOARDID # Swimlanes of BOARDID python3 api.py customfields BOARDID # Custom Fields of BOARDID
python3 api.py lists BOARDID # Lists of BOARDID python3 api.py customfield BOARDID CUSTOMFIELDID # Info of CUSTOMFIELDID
python3 api.py list BOARDID LISTID # Info of LISTID python3 api.py addcustomfieldtoboard AUTHORID BOARDID NAME TYPE SETTINGS SHOWONCARD AUTOMATICALLYONCARD SHOWLABELONMINICARD SHOWSUMATTOPOFLIST # Add Custom Field to Board
python3 api.py swimlanes BOARDID # Swimlanes of BOARDID
python3 api.py lists BOARDID # Lists of BOARDID
python3 api.py list BOARDID LISTID # Info of LISTID
python3 api.py createlist BOARDID LISTTITLE # Create list python3 api.py createlist BOARDID LISTTITLE # Create list
python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION
python3 api.py editcard BOARDID LISTID CARDID NEWCARDTITLE NEWCARDDESCRIPTION python3 api.py editcard BOARDID LISTID CARDID NEWCARDTITLE NEWCARDDESCRIPTION
@ -78,6 +88,13 @@ Syntax:
python3 api.py attachmentjson BOARDID ATTACHMENTID # One attachment as JSON base64 python3 api.py attachmentjson BOARDID ATTACHMENTID # One attachment as JSON base64
python3 api.py attachmentbinary BOARDID ATTACHMENTID # One attachment as binary file python3 api.py attachmentbinary BOARDID ATTACHMENTID # One attachment as binary file
=== ADD CUSTOM FIELD TO BOARD ===
Type: text, number, date, dropdown, checkbox, currency, stringtemplate.
python3 api.py addcustomfieldtoboard cmx3gmHLKwAXLqjxz LcDW4QdooAx8hsZh8 "SomeField" "date" "" true true true true
=== USERS === === USERS ===
python3 api.py users python3 api.py users
@ -133,6 +150,7 @@ l = 'lists'
sw = 'swimlane' sw = 'swimlane'
sws = 'swimlanes' sws = 'swimlanes'
cs = 'cards' cs = 'cards'
cf = 'custom-fields'
bs = 'boards' bs = 'boards'
atl = 'attachmentslist' atl = 'attachmentslist'
at = 'attachment' at = 'attachment'
@ -150,10 +168,34 @@ apikey = d['token']
# ------- LOGIN TOKEN END ----------- # ------- 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 arguments == 7:
if sys.argv[1] == 'addcard': if sys.argv[1] == 'addcard':
# ------- WRITE TO CARD START ----------- # ------- ADD CARD START -----------
authorid = sys.argv[2] authorid = sys.argv[2]
boardid = sys.argv[3] boardid = sys.argv[3]
swimlaneid = sys.argv[4] swimlaneid = sys.argv[4]
@ -161,18 +203,18 @@ if arguments == 7:
cardtitle = sys.argv[6] cardtitle = sys.argv[6]
carddescription = sys.argv[7] carddescription = sys.argv[7]
cardtolist = wekanurl + apiboards + boardid + s + l + s + listid + s + cs cardtolist = wekanurl + apiboards + boardid + s + l + s + listid + s + cs
# Write to card # Add card
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)} headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
post_data = {'authorId': '{}'.format(authorid), 'title': '{}'.format(cardtitle), 'description': '{}'.format(carddescription), 'swimlaneId': '{}'.format(swimlaneid)} post_data = {'authorId': '{}'.format(authorid), 'title': '{}'.format(cardtitle), 'description': '{}'.format(carddescription), 'swimlaneId': '{}'.format(swimlaneid)}
body = requests.post(cardtolist, data=post_data, headers=headers) body = requests.post(cardtolist, data=post_data, headers=headers)
print(body.text) print(body.text)
# ------- WRITE TO CARD END ----------- # ------- ADD CARD END -----------
if arguments == 6: if arguments == 6:
if sys.argv[1] == 'editcard': if sys.argv[1] == 'editcard':
# ------- LIST OF BOARD START ----------- # ------- EDIT CARD START -----------
boardid = sys.argv[2] boardid = sys.argv[2]
listid = sys.argv[3] listid = sys.argv[3]
cardid = sys.argv[4] cardid = sys.argv[4]
@ -187,7 +229,7 @@ if arguments == 6:
body = requests.get(edcard, headers=headers) body = requests.get(edcard, headers=headers)
data2 = body.text.replace('}',"}\n") data2 = body.text.replace('}',"}\n")
print(data2) print(data2)
# ------- LISTS OF BOARD END ----------- # ------- EDIT CARD END -----------
if arguments == 3: if arguments == 3:
@ -217,6 +259,19 @@ if arguments == 3:
print(data2) print(data2)
# ------- LISTS OF BOARD END ----------- # ------- 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: if arguments == 2:
# ------- BOARDS LIST START ----------- # ------- BOARDS LIST START -----------
@ -230,8 +285,8 @@ if arguments == 2:
data2 = body.text.replace('}',"}\n") data2 = body.text.replace('}',"}\n")
print(data2) print(data2)
# ------- BOARDS LIST END ----------- # ------- BOARDS LIST END -----------
if sys.argv[1] == 'board':
if sys.argv[1] == 'board':
# ------- BOARD INFO START ----------- # ------- BOARD INFO START -----------
boardid = sys.argv[2] boardid = sys.argv[2]
board = wekanurl + apiboards + boardid board = wekanurl + apiboards + boardid
@ -242,6 +297,17 @@ if arguments == 2:
print(data2) print(data2)
# ------- BOARD INFO END ----------- # ------- 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': if sys.argv[1] == 'swimlanes':
boardid = sys.argv[2] boardid = sys.argv[2]
swimlanes = wekanurl + apiboards + boardid + s + sws swimlanes = wekanurl + apiboards + boardid + s + sws
@ -289,3 +355,14 @@ if arguments == 1:
data2 = body.text.replace('}',"}\n") data2 = body.text.replace('}',"}\n")
print(data2) print(data2)
# ------- LIST OF USERS END ----------- # ------- LIST OF USERS END -----------
if sys.argv[1] == 'boards':
# ------- LIST OF PUBLIC BOARDS START -----------
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
print("=== PUBLIC BOARDS ===\n")
listpublicboards = wekanurl + apiboards
body = requests.get(listpublicboards, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- LIST OF PUBLIC BOARDS END -----------

View file

@ -12,7 +12,7 @@ template(name="boardActivities")
+activity(activity=activityData card=card mode=mode) +activity(activity=activityData card=card mode=mode)
template(name="cardActivities") template(name="cardActivities")
each activityData in currentCard.activities each activityData in activities
+activity(activity=activityData card=card mode=mode) +activity(activity=activityData card=card mode=mode)
template(name="editOrDeleteComment") template(name="editOrDeleteComment")
@ -21,6 +21,26 @@ template(name="editOrDeleteComment")
= ' - ' = ' - '
a.js-delete-comment {{_ "delete"}} a.js-delete-comment {{_ "delete"}}
template(name="deleteCommentPopup")
p {{_ "comment-delete"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
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
template(name="addReactionPopup")
.reactions-popup
each codepoint in codepoints
span.add-comment-reaction(data-codepoint="#{codepoint}") !{codepoint}
template(name="activity") template(name="activity")
.activity .activity
+userAvatar(userId=activity.user._id) +userAvatar(userId=activity.user._id)
@ -120,10 +140,12 @@ template(name="activity")
= activity.comment.text = activity.comment.text
.edit-controls .edit-controls
button.primary(type="submit") {{_ 'edit'}} button.primary(type="submit") {{_ 'edit'}}
.fa.fa-times-thin.js-close-inlined-form
else else
.activity-comment .activity-comment
+viewer +viewer
= activity.comment.text = activity.comment.text
+commentReactions(reactions=activity.comment.reactions commentId=activity.comment._id)
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }} span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
if($eq currentUser._id activity.comment.userId) if($eq currentUser._id activity.comment.userId)
+editOrDeleteComment +editOrDeleteComment
@ -150,20 +172,20 @@ template(name="activity")
if($eq activity.activityType 'a-startAt') if($eq activity.activityType 'a-startAt')
| {{{_ 'activity-startDate' (sanitize startDate) cardLink}}}. | {{{_ 'activity-startDate' (sanitize startDate) cardLink}}}.
if($eq activity.activityType 'a-dueAt') if($eq activity.activityType 'a-dueAt')
| {{{_ 'activity-dueDate' (sanitize dueDate) cardLink}}}. | {{{_ 'activity-dueDate' (sanitize dueDate) cardLink}}}.
if($eq activity.activityType 'a-endAt') if($eq activity.activityType 'a-endAt')
| {{{_ 'activity-endDate' (sanitize endDate) cardLink}}}. | {{{_ 'activity-endDate' (sanitize endDate) cardLink}}}.
if($eq mode 'board') if($eq mode 'board')
if($eq activity.activityType 'a-receivedAt') if($eq activity.activityType 'a-receivedAt')
| {{{_ 'activity-receivedDate' (sanitize receivedDate) cardLink}}}. | {{{_ 'activity-receivedDate' (sanitize receivedDate) cardLink}}}.
if($eq activity.activityType 'a-startAt') if($eq activity.activityType 'a-startAt')
| {{{_ 'activity-startDate' (sanitize startDate) cardLink}}}. | {{{_ 'activity-startDate' (sanitize startDate) cardLink}}}.
if($eq activity.activityType 'a-dueAt') if($eq activity.activityType 'a-dueAt')
| {{{_ 'activity-dueDate' (sanitize dueDate) cardLink}}}. | {{{_ 'activity-dueDate' (sanitize dueDate) cardLink}}}.

View file

@ -13,14 +13,14 @@ BlazeComponent.extendComponent({
this.autorun(() => { this.autorun(() => {
let mode = this.data().mode; let mode = this.data().mode;
const capitalizedMode = Utils.capitalize(mode); const capitalizedMode = Utils.capitalize(mode);
let thisId, searchId; let searchId;
if (mode === 'linkedcard' || mode === 'linkedboard') { if (mode === 'linkedcard' || mode === 'linkedboard') {
thisId = Session.get('currentCard'); searchId = Utils.getCurrentCard().linkedId;
searchId = Cards.findOne({ _id: thisId }).linkedId;
mode = mode.replace('linked', ''); mode = mode.replace('linked', '');
} else if (mode === 'card') {
searchId = Utils.getCurrentCardId();
} else { } else {
thisId = Session.get(`current${capitalizedMode}`); searchId = Session.get(`current${capitalizedMode}`);
searchId = thisId;
} }
const limit = this.page.get() * activitiesPerPage; const limit = this.page.get() * activitiesPerPage;
const user = Meteor.user(); const user = Meteor.user();
@ -54,6 +54,13 @@ BlazeComponent.extendComponent({
}, },
}).register('activities'); }).register('activities');
Template.activities.helpers({
activities() {
const ret = this.card.activities();
return ret;
},
});
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
checkItem() { checkItem() {
const checkItemId = this.currentData().activity.checklistItemId; const checkItemId = this.currentData().activity.checklistItemId;
@ -113,8 +120,10 @@ BlazeComponent.extendComponent({
).getLabelById(lastLabelId); ).getLabelById(lastLabelId);
if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) { if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
return lastLabel.color; return lastLabel.color;
} else { } else if (lastLabel.name !== undefined && lastLabel.name !== '') {
return lastLabel.name; return lastLabel.name;
} else {
return null;
} }
}, },
@ -211,10 +220,11 @@ BlazeComponent.extendComponent({
return [ return [
{ {
// XXX We should use Popup.afterConfirmation here // XXX We should use Popup.afterConfirmation here
'click .js-delete-comment'() { 'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => {
const commentId = this.currentData().activity.commentId; const commentId = this.data().activity.commentId;
CardComments.remove(commentId); CardComments.remove(commentId);
}, Popup.back();
}),
'submit .js-edit-comment'(evt) { 'submit .js-edit-comment'(evt) {
evt.preventDefault(); evt.preventDefault();
const commentText = this.currentComponent() const commentText = this.currentComponent()
@ -240,6 +250,60 @@ Template.activity.helpers({
}, },
}); });
Template.commentReactions.events({
'click .reaction'(event) {
if (Meteor.user().isBoardMember()) {
const codepoint = event.currentTarget.dataset['codepoint'];
const commentId = Template.instance().data.commentId;
const cardComment = CardComments.findOne({_id: commentId});
cardComment.toggleReaction(codepoint);
}
},
'click .open-comment-reaction-popup': Popup.open('addReaction'),
})
Template.addReactionPopup.events({
'click .add-comment-reaction'(event) {
if (Meteor.user().isBoardMember()) {
const codepoint = event.currentTarget.dataset['codepoint'];
const commentId = Template.instance().data.commentId;
const cardComment = CardComments.findOne({_id: commentId});
cardComment.toggleReaction(codepoint);
}
Popup.back();
},
})
Template.addReactionPopup.helpers({
codepoints() {
// Starting set of unicode codepoints as comment reactions
return [
'&#128077;',
'&#128078;',
'&#128064;',
'&#9989;',
'&#10060;',
'&#128591;',
'&#128079;',
'&#127881;',
'&#128640;',
'&#128522;',
'&#129300;',
'&#128532;'];
}
})
Template.commentReactions.helpers({
isSelected(userIds) {
return userIds.includes(Meteor.user()._id);
},
userNames(userIds) {
return Users.find({_id: {$in: userIds}})
.map(user => user.profile.fullname)
.join(', ');
}
})
function createCardLink(card) { function createCardLink(card) {
if (!card) return ''; if (!card) return '';
return ( return (

View file

@ -5,6 +5,20 @@
display: flex display: flex
justify-content:space-between 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
&:hover {
background-color: #b0c4de
}
.activities .activities
clear: both clear: both
@ -18,7 +32,7 @@
height: @width height: @width
.activity-member .activity-member
font-weight: 700 font-weight: 700
.activity-desc .activity-desc
word-wrap: break-word word-wrap: break-word
@ -39,6 +53,45 @@
margin-top: 5px margin-top: 5px
padding: 5px padding: 5px
.reactions
display: flex
margin-top: 5px
gap: 5px
.open-comment-reaction-popup
display: flex
align-items: center
text-decoration: none
height: 24px;
i.fa.fa-smile-o
font-size: 17px
font-weight: 500
margin-left: 2px
i.fa.fa-plus
font-size: 8px;
margin-top: -7px;
margin-left: 1px;
.reaction
cursor: pointer
border: 1px solid grey
border-radius: 15px
display: flex
padding: 2px 5px
&.selected {
background-color: #b0c4de
}
&:hover {
background-color: #b0c4de
}
.reaction-count
font-size: 12px
.activity-checklist .activity-checklist
display: block display: block
border-radius: 3px border-radius: 3px

View file

@ -64,7 +64,7 @@ function resetCommentInput(input) {
// Tracker.autorun to register the component dependencies, and re-run when these // Tracker.autorun to register the component dependencies, and re-run when these
// dependencies are invalidated. A better component API would remove this hack. // dependencies are invalidated. A better component API would remove this hack.
Tracker.autorun(() => { Tracker.autorun(() => {
Session.get('currentCard'); Utils.getCurrentCardId();
Tracker.afterFlush(() => { Tracker.afterFlush(() => {
autosize.update($('.js-new-comment-input')); autosize.update($('.js-new-comment-input'));
}); });
@ -75,7 +75,7 @@ EscapeActions.register(
() => { () => {
const draftKey = { const draftKey = {
fieldName: 'cardComment', fieldName: 'cardComment',
docId: Session.get('currentCard'), docId: Utils.getCurrentCardId(),
}; };
const commentInput = $('.js-new-comment-input'); const commentInput = $('.js-new-comment-input');
const draft = commentInput.val().trim(); const draft = commentInput.val().trim();

View file

@ -31,7 +31,6 @@
background-color: #fff background-color: #fff
border: 0 border: 0
box-shadow: 0 1px 2px rgba(0, 0, 0, .23) box-shadow: 0 1px 2px rgba(0, 0, 0, .23)
color: #8c8c8c
height: 36px height: 36px
margin: 4px 4px 6px 0 margin: 4px 4px 6px 0
padding: 9px 11px padding: 9px 11px

View file

@ -34,7 +34,7 @@ BlazeComponent.extendComponent({
Utils.goBoardId(board._id); Utils.goBoardId(board._id);
}, },
'click .js-delete-board': Popup.afterConfirm('boardDelete', function() { 'click .js-delete-board': Popup.afterConfirm('boardDelete', function() {
Popup.close(); Popup.back();
const isSandstorm = const isSandstorm =
Meteor.settings && Meteor.settings &&
Meteor.settings.public && Meteor.settings.public &&

View file

@ -13,26 +13,29 @@ template(name="board")
+spinner +spinner
template(name="boardBody") template(name="boardBody")
.board-wrapper(class=currentBoard.colorClass) if notDisplayThisBoard
+sidebar | {{_ 'tableVisibilityMode-allowPrivateOnly'}}
.board-canvas.js-swimlanes( else
class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}" .board-wrapper(class=currentBoard.colorClass)
class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}" +sidebar
class="{{#if draggingActive.get}}is-dragging-active{{/if}}") .board-canvas.js-swimlanes(
if showOverlay.get class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}"
.board-overlay class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}"
if currentBoard.isTemplatesBoard class="{{#if draggingActive.get}}is-dragging-active{{/if}}")
each currentBoard.swimlanes if showOverlay.get
+swimlane(this) .board-overlay
else if isViewSwimlanes if currentBoard.isTemplatesBoard
each currentBoard.swimlanes each currentBoard.swimlanes
+swimlane(this) +swimlane(this)
else if isViewLists else if isViewSwimlanes
+listsGroup(currentBoard) each currentBoard.swimlanes
else if isViewCalendar +swimlane(this)
+calendarView else if isViewLists
else +listsGroup(currentBoard)
+listsGroup(currentBoard) else if isViewCalendar
+calendarView
else
+listsGroup(currentBoard)
template(name="calendarView") template(name="calendarView")
if isViewCalendar if isViewCalendar

View file

@ -23,7 +23,7 @@ BlazeComponent.extendComponent({
}, },
onlyShowCurrentCard() { onlyShowCurrentCard() {
return Utils.isMiniScreen() && Session.get('currentCard'); return Utils.isMiniScreen() && Utils.getCurrentCardId(true);
}, },
goHome() { goHome() {
@ -33,6 +33,7 @@ BlazeComponent.extendComponent({
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
onCreated() { onCreated() {
Meteor.subscribe('tableVisibilityModeSettings');
this.showOverlay = new ReactiveVar(false); this.showOverlay = new ReactiveVar(false);
this.draggingActive = new ReactiveVar(false); this.draggingActive = new ReactiveVar(false);
this._isDragging = false; this._isDragging = false;
@ -190,21 +191,11 @@ BlazeComponent.extendComponent({
}); });
this.autorun(() => { this.autorun(() => {
let showDesktopDragHandles = false; if (Utils.isMiniScreenOrShowDesktopDragHandles()) {
currentUser = Meteor.user();
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
showDesktopDragHandles = true;
} else {
showDesktopDragHandles = false;
}
if (Utils.isMiniScreen() || showDesktopDragHandles) {
$swimlanesDom.sortable({ $swimlanesDom.sortable({
handle: '.js-swimlane-header-handle', handle: '.js-swimlane-header-handle',
}); });
} else if (!Utils.isMiniScreen() && !showDesktopDragHandles) { } else {
$swimlanesDom.sortable({ $swimlanesDom.sortable({
handle: '.swimlane-header', handle: '.swimlane-header',
}); });
@ -215,7 +206,7 @@ BlazeComponent.extendComponent({
$swimlanesDom.sortable( $swimlanesDom.sortable(
'option', 'option',
'disabled', 'disabled',
!Meteor.user().isBoardAdmin(), !Meteor.user() || !Meteor.user().isBoardAdmin(),
); );
}); });
@ -235,6 +226,16 @@ 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() { isViewSwimlanes() {
currentUser = Meteor.user(); currentUser = Meteor.user();
if (currentUser) { if (currentUser) {
@ -325,6 +326,7 @@ BlazeComponent.extendComponent({
defaultView: 'agendaDay', defaultView: 'agendaDay',
editable: true, editable: true,
timezone: 'local', timezone: 'local',
weekNumbers: true,
header: { header: {
left: 'title today prev,next', left: 'title today prev,next',
center: center:

View file

@ -876,7 +876,7 @@ setBoardClear(color1,color2)
padding: 10px padding: 10px
top: 0 top: 0
.list-header .list-header-plus-icon .list-header .list-header-plus-top
color: #a6a6a6 color: #a6a6a6
.list-body .list-body
@ -956,17 +956,24 @@ setBoardClear(color1,color2)
/* Card Details */ /* Card Details */
.card-details .card-details
position: absolute
top: 30px
left: calc(50% - 384px)
width: 768px
max-height: calc(100% - 60px)
background-color: #454545 background-color: #454545
color: #cccccc color: #cccccc
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19) box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)
border: 1px solid #111111 border: 1px solid #111111
z-index: 100 !important z-index: 100 !important
@media screen and (max-width: 800px)
.card-details
width: 98%
@media screen and (min-width: 801px)
.card-details
position: absolute
top: 30px
left: calc(50% - 384px)
width: 768px
max-height: calc(100% - 60px)
.card-details .card-details
scrollbar-width: thin scrollbar-width: thin
scrollbar-color: #343434 #999999 scrollbar-color: #343434 #999999

View file

@ -80,6 +80,12 @@ template(name="boardHeaderBar")
if $eq watchLevel "muted" if $eq watchLevel "muted"
i.fa.fa-bell-slash i.fa.fa-bell-slash
span {{_ watchLevel}} 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 else
a.board-header-btn.js-log-in( a.board-header-btn.js-log-in(
@ -147,14 +153,15 @@ template(name="boardVisibilityList")
if visibilityCheck if visibilityCheck
i.fa.fa-check i.fa.fa-check
span.sub-name {{_ 'private-desc'}} span.sub-name {{_ 'private-desc'}}
li if notAllowPrivateVisibilityOnly
with "public" li
a.js-select-visibility with "public"
i.fa.fa-globe.colorful a.js-select-visibility
| {{_ 'public'}} i.fa.fa-globe.colorful
if visibilityCheck | {{_ 'public'}}
i.fa.fa-check if visibilityCheck
span.sub-name {{_ 'public-desc'}} i.fa.fa-check
span.sub-name {{_ 'public-desc'}}
template(name="boardChangeVisibilityPopup") template(name="boardChangeVisibilityPopup")
+boardVisibilityList +boardVisibilityList

View file

@ -7,11 +7,11 @@ Template.boardMenuPopup.events({
'click .js-rename-board': Popup.open('boardChangeTitle'), 'click .js-rename-board': Popup.open('boardChangeTitle'),
'click .js-custom-fields'() { 'click .js-custom-fields'() {
Sidebar.setView('customFields'); Sidebar.setView('customFields');
Popup.close(); Popup.back();
}, },
'click .js-open-archives'() { 'click .js-open-archives'() {
Sidebar.setView('archives'); Sidebar.setView('archives');
Popup.close(); Popup.back();
}, },
'click .js-change-board-color': Popup.open('boardChangeColor'), 'click .js-change-board-color': Popup.open('boardChangeColor'),
'click .js-change-language': Popup.open('changeLanguage'), 'click .js-change-language': Popup.open('changeLanguage'),
@ -24,7 +24,7 @@ Template.boardMenuPopup.events({
}), }),
'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() { 'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() {
const currentBoard = Boards.findOne(Session.get('currentBoard')); const currentBoard = Boards.findOne(Session.get('currentBoard'));
Popup.close(); Popup.back();
Boards.remove(currentBoard._id); Boards.remove(currentBoard._id);
FlowRouter.go('home'); FlowRouter.go('home');
}), }),
@ -47,7 +47,7 @@ Template.boardChangeTitlePopup.events({
if (newTitle) { if (newTitle) {
this.rename(newTitle); this.rename(newTitle);
this.setDescription(newDesc); this.setDescription(newDesc);
Popup.close(); Popup.back();
} }
event.preventDefault(); event.preventDefault();
}, },
@ -136,7 +136,7 @@ BlazeComponent.extendComponent({
Sidebar.setView('search'); Sidebar.setView('search');
}, },
'click .js-multiselection-activate'() { 'click .js-multiselection-activate'() {
const currentCard = Session.get('currentCard'); const currentCard = Utils.getCurrentCardId();
MultiSelection.activate(); MultiSelection.activate();
if (currentCard) { if (currentCard) {
MultiSelection.add(currentCard); MultiSelection.add(currentCard);
@ -173,15 +173,15 @@ Template.boardHeaderBar.helpers({
Template.boardChangeViewPopup.events({ Template.boardChangeViewPopup.events({
'click .js-open-lists-view'() { 'click .js-open-lists-view'() {
Utils.setBoardView('board-view-lists'); Utils.setBoardView('board-view-lists');
Popup.close(); Popup.back();
}, },
'click .js-open-swimlanes-view'() { 'click .js-open-swimlanes-view'() {
Utils.setBoardView('board-view-swimlanes'); Utils.setBoardView('board-view-swimlanes');
Popup.close(); Popup.back();
}, },
'click .js-open-cal-view'() { 'click .js-open-cal-view'() {
Utils.setBoardView('board-view-cal'); Utils.setBoardView('board-view-cal');
Popup.close(); Popup.back();
}, },
}); });
@ -194,6 +194,11 @@ const CreateBoard = BlazeComponent.extendComponent({
this.visibilityMenuIsOpen = new ReactiveVar(false); this.visibilityMenuIsOpen = new ReactiveVar(false);
this.visibility = new ReactiveVar('private'); this.visibility = new ReactiveVar('private');
this.boardId = new ReactiveVar(''); this.boardId = new ReactiveVar('');
Meteor.subscribe('tableVisibilityModeSettings');
},
notAllowPrivateVisibilityOnly(){
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
}, },
visibilityCheck() { visibilityCheck() {
@ -310,6 +315,9 @@ const CreateBoard = BlazeComponent.extendComponent({
}.register('headerBarCreateBoardPopup')); }.register('headerBarCreateBoardPopup'));
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
notAllowPrivateVisibilityOnly(){
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
},
visibilityCheck() { visibilityCheck() {
const currentBoard = Boards.findOne(Session.get('currentBoard')); const currentBoard = Boards.findOne(Session.get('currentBoard'));
return this.currentData() === currentBoard.permission; return this.currentData() === currentBoard.permission;
@ -319,7 +327,7 @@ BlazeComponent.extendComponent({
const currentBoard = Boards.findOne(Session.get('currentBoard')); const currentBoard = Boards.findOne(Session.get('currentBoard'));
const visibility = this.currentData(); const visibility = this.currentData();
currentBoard.setVisibility(visibility); currentBoard.setVisibility(visibility);
Popup.close(); Popup.back();
}, },
events() { events() {
@ -352,7 +360,7 @@ BlazeComponent.extendComponent({
Session.get('currentBoard'), Session.get('currentBoard'),
level, level,
(err, ret) => { (err, ret) => {
if (!err && ret) Popup.close(); if (!err && ret) Popup.back();
}, },
); );
}, },
@ -424,7 +432,7 @@ BlazeComponent.extendComponent({
const direction = down ? -1 : 1; const direction = down ? -1 : 1;
this.setSortBy([sortby, direction]); this.setSortBy([sortby, direction]);
if (Utils.isMiniScreen) { if (Utils.isMiniScreen) {
Popup.close(); Popup.back();
} }
}, },
}, },
@ -443,7 +451,7 @@ BlazeComponent.extendComponent({
}; };
Session.set('sortBy', sortBy); Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('due-date')); sortCardsBy.set(TAPi18n.__('due-date'));
Popup.close(); Popup.back();
}, },
'click .js-sort-title'() { 'click .js-sort-title'() {
const sortBy = { const sortBy = {
@ -451,7 +459,7 @@ BlazeComponent.extendComponent({
}; };
Session.set('sortBy', sortBy); Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('title')); sortCardsBy.set(TAPi18n.__('title'));
Popup.close(); Popup.back();
}, },
'click .js-sort-created-asc'() { 'click .js-sort-created-asc'() {
const sortBy = { const sortBy = {
@ -459,7 +467,7 @@ BlazeComponent.extendComponent({
}; };
Session.set('sortBy', sortBy); Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-newest-first')); sortCardsBy.set(TAPi18n.__('date-created-newest-first'));
Popup.close(); Popup.back();
}, },
'click .js-sort-created-desc'() { 'click .js-sort-created-desc'() {
const sortBy = { const sortBy = {
@ -467,7 +475,7 @@ BlazeComponent.extendComponent({
}; };
Session.set('sortBy', sortBy); Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-oldest-first')); sortCardsBy.set(TAPi18n.__('date-created-oldest-first'));
Popup.close(); Popup.back();
}, },
}, },
]; ];

View file

@ -1,11 +1,32 @@
template(name="boardList") template(name="boardList")
.wrapper .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.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.js-boards
li.js-add-board li.js-add-board
a.board-list-item.label(title="{{_ 'add-board'}}") a.board-list-item.label(title="{{_ 'add-board'}}")
| {{_ 'add-board'}} | {{_ 'add-board'}}
each boards each boards
li(class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board li(class="{{_id}}" class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board
if isInvited if isInvited
.board-list-item .board-list-item
span.details span.details
@ -33,11 +54,11 @@ template(name="boardList")
i.fa.js-has-spenttime-cards( i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" 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}}") title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
if isMiniScreen if isMiniScreenOrShowDesktopDragHandles
i.fa.board-handle( i.fa.board-handle(
class="fa-arrows" class="fa-arrows"
title="{{_ 'Drag board'}}") title="{{_ 'Drag board'}}")
unless isMiniScreen else
if isSandstorm if isSandstorm
i.fa.js-clone-board( i.fa.js-clone-board(
class="fa-clone" class="fa-clone"
@ -75,11 +96,11 @@ template(name="boardList")
i.fa.js-has-spenttime-cards( i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" 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}}") title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
if isMiniScreen if isMiniScreenOrShowDesktopDragHandles
i.fa.board-handle( i.fa.board-handle(
class="fa-arrows" class="fa-arrows"
title="{{_ 'Drag board'}}") title="{{_ 'Drag board'}}")
unless isMiniScreen else
if isSandstorm if isSandstorm
i.fa.js-clone-board( i.fa.js-clone-board(
class="fa-clone" class="fa-clone"

View file

@ -1,5 +1,4 @@
const subManager = new SubsManager(); const subManager = new SubsManager();
const { calculateIndex, enableClickOnTouch } = Utils;
Template.boardListHeaderBar.events({ Template.boardListHeaderBar.events({
'click .js-open-archived-board'() { 'click .js-open-archived-board'() {
@ -22,6 +21,7 @@ Template.boardListHeaderBar.helpers({
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
onCreated() { onCreated() {
Meteor.subscribe('setting'); Meteor.subscribe('setting');
Meteor.subscribe('tableVisibilityModeSettings');
let currUser = Meteor.user(); let currUser = Meteor.user();
let userLanguage; let userLanguage;
if(currUser && currUser.profile){ if(currUser && currUser.profile){
@ -55,7 +55,7 @@ BlazeComponent.extendComponent({
// of the previous and the following card -- if any. // of the previous and the following card -- if any.
const prevBoardDom = ui.item.prev('.js-board').get(0); const prevBoardDom = ui.item.prev('.js-board').get(0);
const nextBoardBom = ui.item.next('.js-board').get(0); const nextBoardBom = ui.item.next('.js-board').get(0);
const sortIndex = calculateIndex(prevBoardDom, nextBoardBom, 1); const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardBom, 1);
const boardDomElement = ui.item.get(0); const boardDomElement = ui.item.get(0);
const board = Blaze.getData(boardDomElement); const board = Blaze.getData(boardDomElement);
@ -72,21 +72,56 @@ BlazeComponent.extendComponent({
}, },
}); });
// ugly touch event hotfix
enableClickOnTouch(itemsSelector);
// Disable drag-dropping if the current user is not a board member or is comment only // Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => { this.autorun(() => {
if (Utils.isMiniScreen()) { if (Utils.isMiniScreenOrShowDesktopDragHandles()) {
$boards.sortable({ $boards.sortable({
handle: '.board-handle', 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;
},
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() { boards() {
const query = { let query = {
//archived: false, //archived: false,
////type: { $in: ['board','template-container'] }, ////type: { $in: ['board','template-container'] },
//type: 'board', //type: 'board',
@ -96,9 +131,15 @@ BlazeComponent.extendComponent({
{ $or:[] } { $or:[] }
] ]
}; };
let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly');
if (FlowRouter.getRouteName() === 'home'){ if (FlowRouter.getRouteName() === 'home'){
query.$and[2].$or.push({'members.userId': Meteor.userId()}); 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());
// const currUser = Users.findOne(Meteor.userId(), { // const currUser = Users.findOne(Meteor.userId(), {
@ -108,7 +149,7 @@ BlazeComponent.extendComponent({
// }, // },
// }); // });
let orgIdsUserBelongs = currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : ''; let orgIdsUserBelongs = currUser !== undefined && currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : '';
if(orgIdsUserBelongs && orgIdsUserBelongs != ''){ if(orgIdsUserBelongs && orgIdsUserBelongs != ''){
let orgsIds = orgIdsUserBelongs.split(','); let orgsIds = orgIdsUserBelongs.split(',');
// for(let i = 0; i < orgsIds.length; i++){ // for(let i = 0; i < orgsIds.length; i++){
@ -119,7 +160,7 @@ BlazeComponent.extendComponent({
query.$and[2].$or.push({'orgs.orgId': {$in : orgsIds}}); query.$and[2].$or.push({'orgs.orgId': {$in : orgsIds}});
} }
let teamIdsUserBelongs = currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : ''; let teamIdsUserBelongs = currUser !== undefined && currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : '';
if(teamIdsUserBelongs && teamIdsUserBelongs != ''){ if(teamIdsUserBelongs && teamIdsUserBelongs != ''){
let teamsIds = teamIdsUserBelongs.split(','); let teamsIds = teamIdsUserBelongs.split(',');
// for(let i = 0; i < teamsIds.length; i++){ // for(let i = 0; i < teamsIds.length; i++){
@ -129,10 +170,17 @@ BlazeComponent.extendComponent({
query.$and[2].$or.push({'teams.teamId': {$in : teamsIds}}); query.$and[2].$or.push({'teams.teamId': {$in : teamsIds}});
} }
} }
else query.permission = 'public'; else if(allowPrivateVisibilityOnly !== undefined && !allowPrivateVisibilityOnly.booleanValue){
query = {
archived: false,
//type: { $in: ['board','template-container'] },
type: 'board',
permission: 'public',
};
}
return Boards.find(query, { return Boards.find(query, {
//sort: { sort: 1 /* boards default sorting */ }, sort: { sort: 1 /* boards default sorting */ },
}); });
}, },
isStarred() { isStarred() {
@ -206,6 +254,74 @@ BlazeComponent.extendComponent({
} }
}); });
}, },
'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";
}
}
}
},
}, },
]; ];
}, },

View file

@ -229,3 +229,25 @@ $spaceBetweenTiles = 16px
transform: translateY(-50%) transform: translateY(-50%)
right: 10px right: 10px
font-size: 24px 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;

View file

@ -26,12 +26,25 @@ template(name="attachmentsGalery")
if isUploaded if isUploaded
if isImage if isImage
img.attachment-thumbnail-img(src="{{url}}") img.attachment-thumbnail-img(src="{{url}}")
else if($eq extension 'mp3')
video(width="100%" height="100%" controls="true")
source(src="{{url}}" type="audio/mpeg")
else if($eq extension 'ogg')
video(width="100%" height="100%" controls="true")
source(src="{{url}}" type="video/ogg")
else if($eq extension 'webm')
video(width="100%" height="100%" controls="true")
source(src="{{url}}" type="video/webm")
else if($eq extension 'mp4')
video(width="100%" height="100%" controls="true")
source(src="{{url}}" type="video/mp4")
else else
span.attachment-thumbnail-ext= extension span.attachment-thumbnail-ext= extension
else else
+spinner +spinner
p.attachment-details p.attachment-details
= name = name
span.file-size ({{fileSize size}} KB)
span.attachment-details-actions span.attachment-details-actions
a.js-download(href="{{url download=true}}") a.js-download(href="{{url download=true}}")
i.fa.fa-download i.fa.fa-download

View file

@ -4,7 +4,7 @@ Template.attachmentsGalery.events({
'attachmentDelete', 'attachmentDelete',
function() { function() {
Attachments.remove(this._id); Attachments.remove(this._id);
Popup.close(); Popup.back();
}, },
), ),
// If we let this event bubble, FlowRouter will handle it and empty the page // If we let this event bubble, FlowRouter will handle it and empty the page
@ -49,11 +49,14 @@ Template.attachmentsGalery.helpers({
isBoardAdmin() { isBoardAdmin() {
return Meteor.user().isBoardAdmin(); return Meteor.user().isBoardAdmin();
}, },
fileSize(size) {
return Math.round(size / 1024);
},
}); });
Template.previewAttachedImagePopup.events({ Template.previewAttachedImagePopup.events({
'click .js-large-image-clicked'() { 'click .js-large-image-clicked'() {
Popup.close(); Popup.back();
}, },
}); });
@ -65,7 +68,7 @@ Template.cardAttachmentsPopup.events({
if (attachment && attachment._id && attachment.isImage()) { if (attachment && attachment._id && attachment.isImage()) {
card.setCover(attachment._id); card.setCover(attachment._id);
} }
Popup.close(); Popup.back();
}); });
}; };
@ -174,7 +177,7 @@ Template.previewClipboardImagePopup.events({
pastedResults = null; pastedResults = null;
$(document.body).pasteImageReader(() => {}); $(document.body).pasteImageReader(() => {});
Popup.close(); Popup.back();
} }
}, },
}); });

View file

@ -9,7 +9,7 @@
margin: 10px 1% 0 margin: 10px 1% 0
text-align: center text-align: center
border-radius: 3px border-radius: 3px
overflow: hidden overflow: auto
background: darken(white, 7%) background: darken(white, 7%)
min-height: 120px min-height: 120px

View file

@ -63,7 +63,7 @@ template(name="cardCustomField-checkbox")
template(name="cardCustomField-currency") template(name="cardCustomField-currency")
if canModifyCard if canModifyCard
+inlinedForm(classNames="js-card-customfield-currency") +inlinedForm(classNames="js-card-customfield-currency")
input(type="text" value=data.value) input(type="text" value=data.value autofocus)
.edit-controls.clearfix .edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}} button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form a.fa.fa-times-thin.js-close-inlined-form
@ -79,18 +79,22 @@ template(name="cardCustomField-currency")
template(name="cardCustomField-date") template(name="cardCustomField-date")
if canModifyCard if canModifyCard
a.js-edit-date(title="{{showTitle}}" class="{{classes}}") a.js-edit-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
if value
div.card-date
time(datetime="{{showISODate}}")
| {{showDate}}
else
| {{_ 'edit'}}
else
if value if value
div.card-date div.card-date
time(datetime="{{showISODate}}") time(datetime="{{showISODate}}")
| {{showDate}} | {{showDate}}
b
| {{showWeek}}
else
| {{_ 'edit'}}
else
if value
div.card-date
time(datetime="{{showISODate}}")
| {{showDate}}
b
| {{showWeek}}
template(name="cardCustomField-dropdown") template(name="cardCustomField-dropdown")
if canModifyCard if canModifyCard

View file

@ -3,7 +3,7 @@ import Cards from '/models/cards';
Template.cardCustomFieldsPopup.helpers({ Template.cardCustomFieldsPopup.helpers({
hasCustomField() { hasCustomField() {
const card = Cards.findOne(Session.get('currentCard')); const card = Utils.getCurrentCard();
const customFieldId = this._id; const customFieldId = this._id;
return card.customFieldIndex(customFieldId) > -1; return card.customFieldIndex(customFieldId) > -1;
}, },
@ -11,7 +11,7 @@ Template.cardCustomFieldsPopup.helpers({
Template.cardCustomFieldsPopup.events({ Template.cardCustomFieldsPopup.events({
'click .js-select-field'(event) { 'click .js-select-field'(event) {
const card = Cards.findOne(Session.get('currentCard')); const card = Utils.getCurrentCard();
const customFieldId = this._id; const customFieldId = this._id;
card.toggleCustomField(customFieldId); card.toggleCustomField(customFieldId);
event.preventDefault(); event.preventDefault();
@ -31,7 +31,7 @@ const CardCustomField = BlazeComponent.extendComponent({
onCreated() { onCreated() {
const self = this; const self = this;
self.card = Cards.findOne(Session.get('currentCard')); self.card = Utils.getCurrentCard();
self.customFieldId = this.data()._id; self.customFieldId = this.data()._id;
}, },
@ -149,6 +149,10 @@ CardCustomField.register('cardCustomField');
}); });
} }
showWeek() {
return this.date.get().week().toString();
}
showDate() { showDate() {
// this will start working once mquandalle:moment // this will start working once mquandalle:moment
// is updated to at least moment.js 2.10.5 // is updated to at least moment.js 2.10.5
@ -190,7 +194,7 @@ CardCustomField.register('cardCustomField');
onCreated() { onCreated() {
super.onCreated(); super.onCreated();
const self = this; const self = this;
self.card = Cards.findOne(Session.get('currentCard')); self.card = Utils.getCurrentCard();
self.customFieldId = this.data()._id; self.customFieldId = this.data()._id;
this.data().value && this.date.set(moment(this.data().value)); this.data().value && this.date.set(moment(this.data().value));
} }
@ -267,7 +271,7 @@ CardCustomField.register('cardCustomField');
{ {
'submit .js-card-customfield-stringtemplate'(event) { 'submit .js-card-customfield-stringtemplate'(event) {
event.preventDefault(); event.preventDefault();
const items = this.getItems(); const items = this.stringtemplateItems.get();
this.card.setCustomField(this.customFieldId, items); this.card.setCustomField(this.customFieldId, items);
}, },

View file

@ -1,14 +1,20 @@
template(name="dateBadge") template(name="dateBadge")
if canModifyCard if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}}" class="{{classes}}") a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
time(datetime="{{showISODate}}") time(datetime="{{showISODate}}")
| {{showDate}} | {{showDate}}
b
| {{showWeek}}
else else
a.card-date(title="{{showTitle}}" class="{{classes}}") a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
time(datetime="{{showISODate}}") time(datetime="{{showISODate}}")
| {{showDate}} | {{showDate}}
b
| {{showWeek}}
template(name="dateCustomField") template(name="dateCustomField")
a(title="{{showTitle}}" class="{{classes}}") a(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
time(datetime="{{showISODate}}") time(datetime="{{showISODate}}")
| {{showDate}} | {{showDate}}
b
| {{showWeek}}

View file

@ -24,7 +24,7 @@ Template.dateBadge.helpers({
} }
_deleteDate() { _deleteDate() {
this.card.setReceived(null); this.card.unsetReceived();
} }
}.register('editCardReceivedDatePopup')); }.register('editCardReceivedDatePopup'));
@ -50,7 +50,7 @@ Template.dateBadge.helpers({
} }
_deleteDate() { _deleteDate() {
this.card.setStart(null); this.card.unsetStart();
} }
}.register('editCardStartDatePopup')); }.register('editCardStartDatePopup'));
@ -73,7 +73,7 @@ Template.dateBadge.helpers({
} }
_deleteDate() { _deleteDate() {
this.card.setDue(null); this.card.unsetDue();
} }
}.register('editCardDueDatePopup')); }.register('editCardDueDatePopup'));
@ -96,7 +96,7 @@ Template.dateBadge.helpers({
} }
_deleteDate() { _deleteDate() {
this.card.setEnd(null); this.card.unsetEnd();
} }
}.register('editCardEndDatePopup')); }.register('editCardEndDatePopup'));
@ -115,6 +115,10 @@ const CardDate = BlazeComponent.extendComponent({
}, 60000); }, 60000);
}, },
showWeek() {
return this.date.get().week().toString();
},
showDate() { showDate() {
// this will start working once mquandalle:moment // this will start working once mquandalle:moment
// is updated to at least moment.js 2.10.5 // is updated to at least moment.js 2.10.5
@ -284,12 +288,25 @@ class CardCustomFieldDate extends CardDate {
}); });
} }
classes() { showWeek() {
return 'customfield-date'; 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() { showTitle() {
return ''; return `${this.date.get().format('LLLL')}`;
}
classes() {
return 'customfield-date';
} }
events() { events() {

View file

@ -25,7 +25,10 @@ BlazeComponent.extendComponent({
// Pressing Ctrl+Enter should submit the form // Pressing Ctrl+Enter should submit the form
'keydown form textarea'(evt) { 'keydown form textarea'(evt) {
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
this.find('button[type=submit]').click(); const submitButton = this.find('button[type=submit]');
if (submitButton) {
submitButton.click();
}
} }
}, },
}, },

View file

@ -1,27 +1,40 @@
template(name="cardDetailsPopup")
+cardDetails(popupCard)
template(name="cardDetails") template(name="cardDetails")
section.card-details.js-card-details(class='{{#if cardMaximized}}card-details-maximized{{/if}}'): .card-details-canvas section.card-details.js-card-details(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}'): .card-details-canvas
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}') .card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
+inlinedForm(classNames="js-card-details-title") +inlinedForm(classNames="js-card-details-title")
+editCardTitleForm +editCardTitleForm
else else
unless isMiniScreen unless isMiniScreen
a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}") unless isPopup
unless cardMaximized a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}") if cardMaximized
if cardMaximized a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}") else
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
if currentUser.isBoardMember if currentUser.isBoardMember
a.fa.fa-navicon.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") a.fa.fa-navicon.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
input.inline-input(type="text" id="cardURL_copy" value="{{ originRelativeUrl }}")
a.fa.fa-link.card-copy-button.js-copy-link( a.fa.fa-link.card-copy-button.js-copy-link(
id="cardURL_copy"
class="fa-link" class="fa-link"
title="{{_ 'copy-card-link-to-clipboard'}}" title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
) )
if isMiniScreen span.copied-tooltip {{_ 'copied'}}
a.fa.fa-times-thin.close-card-details-mobile-web.js-close-card-details(title="{{_ 'close-card'}}") else
unless isPopup
a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
if currentUser.isBoardMember if currentUser.isBoardMember
a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
a.fa.fa-link.card-copy-mobile-button a.fa.fa-link.card-copy-mobile-button.js-copy-link(
id="cardURL_copy"
class="fa-link"
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
span.copied-tooltip {{_ 'copied'}}
h2.card-details-title.js-card-title( h2.card-details-title.js-card-title(
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}") class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
+viewer +viewer
@ -66,8 +79,10 @@ template(name="cardDetails")
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}") a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
i.fa.fa-plus i.fa.fa-plus
if currentBoard.allowsReceivedDate if currentBoard.hasAnyAllowsDate
hr hr
if currentBoard.allowsReceivedDate
.card-details-item.card-details-item-received .card-details-item.card-details-item-received
h3.card-details-item-title h3.card-details-item-title
i.fa.fa-sign-out i.fa.fa-sign-out
@ -119,7 +134,9 @@ template(name="cardDetails")
a.card-label.add-label.js-end-date a.card-label.add-label.js-end-date
i.fa.fa-plus i.fa.fa-plus
hr if currentBoard.hasAnyAllowsUser
hr
if currentBoard.allowsCreator if currentBoard.allowsCreator
.card-details-item.card-details-item-creator .card-details-item.card-details-item-creator
h3.card-details-item-title h3.card-details-item-title
@ -160,17 +177,6 @@ template(name="cardDetails")
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}") a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
i.fa.fa-plus i.fa.fa-plus
//.card-details-items
if getSpentTime
.card-details-item.card-details-item-spent
if getIsOvertime
h3.card-details-item-title
| {{_ 'overtime-hours'}}
else
h3.card-details-item-title
| {{_ 'spent-time-hours'}}
+cardSpentTime
//.card-details-items //.card-details-items
if currentBoard.allowsRequestedBy if currentBoard.allowsRequestedBy
.card-details-item.card-details-item-name .card-details-item.card-details-item-name
@ -212,6 +218,9 @@ template(name="cardDetails")
+viewer +viewer
= getAssignedBy = getAssignedBy
if $or currentBoard.allowsCardSortingByNumber getSpentTime
hr
if currentBoard.allowsCardSortingByNumber if currentBoard.allowsCardSortingByNumber
.card-details-item.card-details-sort-order .card-details-item.card-details-sort-order
h3.card-details-item-title h3.card-details-item-title
@ -225,15 +234,36 @@ template(name="cardDetails")
+viewer +viewer
= sort = sort
//.card-details-items
if getSpentTime
.card-details-item.card-details-item-spent
if getIsOvertime
h3.card-details-item-title
| {{_ 'overtime-hours'}}
else
h3.card-details-item-title
| {{_ 'spent-time-hours'}}
+cardSpentTime
//.card-details-items //.card-details-items
if customFieldsWD if customFieldsWD
hr unless customFieldsGrid
hr
each customFieldsWD each customFieldsWD
if customFieldsGrid
hr
.card-details-item.card-details-item-customfield .card-details-item.card-details-item-customfield
h3.card-details-item-title h3.card-details-item-title
i.fa.fa-list-alt i.fa.fa-list-alt
= definition.name = definition.name
+cardCustomField +cardCustomField
.material-toggle-switch(title="{{_ 'change'}} {{_ 'custom-fields'}} {{_ 'layout'}}")
if customFieldsGrid
input.toggle-switch(type="checkbox" id="toggleCustomFieldsGridButton" checked="checked")
else
input.toggle-switch(type="checkbox" id="toggleCustomFieldsGridButton")
label.toggle-label(for="toggleCustomFieldsGridButton")
a.fa.fa-plus.js-custom-fields.card-details-item.custom-fields(title="{{_ 'custom-fields'}}")
if getVoteQuestion if getVoteQuestion
hr hr
@ -519,6 +549,7 @@ template(name="cardDetails")
.card-details-right .card-details-right
unless currentUser.isNoComments unless currentUser.isNoComments
hr
.activity-title .activity-title
h3.card-details-item-title h3.card-details-item-title
i.fa.fa-history i.fa.fa-history
@ -708,8 +739,9 @@ template(name="boardsAndLists")
button.primary.confirm.js-done {{_ 'done'}} button.primary.confirm.js-done {{_ 'done'}}
template(name="cardMembersPopup") template(name="cardMembersPopup")
input.card-members-filter(type="text" placeholder="{{_ 'search'}}")
ul.pop-over-list.js-card-member-list ul.pop-over-list.js-card-member-list
each board.activeMembers each members
li.item(class="{{#if isCardMember}}active{{/if}}") li.item(class="{{#if isCardMember}}active{{/if}}")
a.name.js-select-member(href="#") a.name.js-select-member(href="#")
+userAvatar(userId=user._id) +userAvatar(userId=user._id)
@ -720,9 +752,10 @@ template(name="cardMembersPopup")
i.fa.fa-check i.fa.fa-check
template(name="cardAssigneesPopup") template(name="cardAssigneesPopup")
input.card-assignees-filter(type="text" placeholder="{{_ 'search'}}")
unless currentUser.isWorker unless currentUser.isWorker
ul.pop-over-list.js-card-assignee-list ul.pop-over-list.js-card-assignee-list
each board.activeMembers each members
li.item(class="{{#if isCardAssignee}}active{{/if}}") li.item(class="{{#if isCardAssignee}}active{{/if}}")
a.name.js-select-assignee(href="#") a.name.js-select-assignee(href="#")
+userAvatar(userId=user._id) +userAvatar(userId=user._id)
@ -767,6 +800,7 @@ template(name="cardMorePopup")
i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}") i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
input.inline-input(type="text" id="cardURL" readonly value="{{ originRelativeUrl }}" autofocus="autofocus") input.inline-input(type="text" id="cardURL" readonly value="{{ originRelativeUrl }}" autofocus="autofocus")
button.js-copy-card-link-to-clipboard(class="btn" id="clipboard") {{_ 'copy-card-link-to-clipboard'}} button.js-copy-card-link-to-clipboard(class="btn" id="clipboard") {{_ 'copy-card-link-to-clipboard'}}
.copied-tooltip {{_ 'copied'}}
span.clearfix span.clearfix
br br
h2 {{_ 'change-card-parent'}} h2 {{_ 'change-card-parent'}}
@ -815,6 +849,12 @@ template(name="cardDeletePopup")
p {{_ "card-delete-suggest-archive"}} p {{_ "card-delete-suggest-archive"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}} button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="cardArchivePopup")
p {{_ "card-archive-pop"}}
unless archived
p {{_ "card-archive-suggest-cancel"}}
button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
template(name="deleteVotePopup") template(name="deleteVotePopup")
p {{_ "vote-delete-pop"}} p {{_ "vote-delete-pop"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}} button.js-confirm.negate.full(type="submit") {{_ 'delete'}}

View file

@ -34,15 +34,25 @@ BlazeComponent.extendComponent({
onCreated() { onCreated() {
this.currentBoard = Boards.findOne(Session.get('currentBoard')); this.currentBoard = Boards.findOne(Session.get('currentBoard'));
this.isLoaded = new ReactiveVar(false); this.isLoaded = new ReactiveVar(false);
const boardBody = this.parentComponent().parentComponent();
//in Miniview parent is Board, not BoardBody. if (this.parentComponent() && this.parentComponent().parentComponent()) {
if (boardBody !== null) { const boardBody = this.parentComponent().parentComponent();
boardBody.showOverlay.set(true); //in Miniview parent is Board, not BoardBody.
boardBody.mouseHasEnterCardDetails = false; if (boardBody !== null) {
boardBody.showOverlay.set(true);
boardBody.mouseHasEnterCardDetails = false;
}
} }
this.calculateNextPeak(); this.calculateNextPeak();
Meteor.subscribe('unsaved-edits'); Meteor.subscribe('unsaved-edits');
// this.findUsersOptions = new ReactiveVar({});
// this.page = new ReactiveVar(1);
// this.autorun(() => {
// const limitUsers = this.page.get() * Number.MAX_SAFE_INTEGER;
// this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
// });
}, },
isWatching() { isWatching() {
@ -54,6 +64,11 @@ BlazeComponent.extendComponent({
return Meteor.user().hasHiddenSystemMessages(); return Meteor.user().hasHiddenSystemMessages();
}, },
customFieldsGrid() {
return Meteor.user().hasCustomFieldsGrid();
},
cardMaximized() { cardMaximized() {
return Meteor.user().hasCardMaximized(); return Meteor.user().hasCardMaximized();
}, },
@ -180,7 +195,7 @@ BlazeComponent.extendComponent({
integration, integration,
'CardSelected', 'CardSelected',
params, params,
() => {}, () => { },
); );
}); });
} }
@ -203,7 +218,7 @@ BlazeComponent.extendComponent({
distance: 7, distance: 7,
start(evt, ui) { start(evt, ui) {
ui.placeholder.height(ui.helper.height()); ui.placeholder.height(ui.helper.height());
EscapeActions.executeUpTo('popup-close'); EscapeActions.clickExecute(evt.target, 'inlinedForm');
}, },
stop(evt, ui) { stop(evt, ui) {
let prevChecklist = ui.item.prev('.js-checklist').get(0); let prevChecklist = ui.item.prev('.js-checklist').get(0);
@ -285,6 +300,7 @@ BlazeComponent.extendComponent({
}, },
onDestroyed() { onDestroyed() {
if (this.parentComponent() === null) return;
const parentComponent = this.parentComponent().parentComponent(); const parentComponent = this.parentComponent().parentComponent();
//on mobile view parent is Board, not board body. //on mobile view parent is Board, not board body.
if (parentComponent === null) return; if (parentComponent === null) return;
@ -307,30 +323,12 @@ BlazeComponent.extendComponent({
'click .js-close-card-details'() { 'click .js-close-card-details'() {
Utils.goBoardId(this.data().boardId); Utils.goBoardId(this.data().boardId);
}, },
'click .js-copy-link'() { 'click .js-copy-link'(event) {
const StringToCopyElement = document.getElementById('cardURL_copy'); event.preventDefault();
StringToCopyElement.value = const promise = Utils.copyTextToClipboard(event.target.href);
window.location.origin + window.location.pathname;
StringToCopyElement.select(); const $tooltip = this.$('.card-details-header .copied-tooltip');
if (document.execCommand('copy')) { Utils.showCopied(promise, $tooltip);
StringToCopyElement.blur();
} else {
document.getElementById('cardURL_copy').selectionStart = 0;
document.getElementById('cardURL_copy').selectionEnd = 999;
document.execCommand('copy');
if (window.getSelection) {
if (window.getSelection().empty) {
// Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) {
// Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) {
// IE?
document.selection.empty();
}
}
}, },
'click .js-open-card-details-menu': Popup.open('cardDetailsActions'), 'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
'submit .js-card-description'(event) { 'submit .js-card-description'(event) {
@ -365,6 +363,12 @@ BlazeComponent.extendComponent({
this.data().setRequestedBy(''); this.data().setRequestedBy('');
} }
}, },
'keydown input.js-edit-card-sort'(evt) {
// enter = save
if (evt.keyCode === 13) {
this.find('button[type=submit]').click();
}
},
'submit .js-card-details-sort'(event) { 'submit .js-card-details-sort'(event) {
event.preventDefault(); event.preventDefault();
const sort = parseFloat(this.currentComponent() const sort = parseFloat(this.currentComponent()
@ -389,7 +393,9 @@ BlazeComponent.extendComponent({
'click .js-end-date': Popup.open('editCardEndDate'), 'click .js-end-date': Popup.open('editCardEndDate'),
'click .js-show-positive-votes': Popup.open('positiveVoteMembers'), 'click .js-show-positive-votes': Popup.open('positiveVoteMembers'),
'click .js-show-negative-votes': Popup.open('negativeVoteMembers'), 'click .js-show-negative-votes': Popup.open('negativeVoteMembers'),
'click .js-custom-fields': Popup.open('cardCustomFields'),
'mouseenter .js-card-details'() { 'mouseenter .js-card-details'() {
if (this.parentComponent() === null) return;
const parentComponent = this.parentComponent().parentComponent(); const parentComponent = this.parentComponent().parentComponent();
//on mobile view parent is Board, not BoardBody. //on mobile view parent is Board, not BoardBody.
if (parentComponent === null) return; if (parentComponent === null) return;
@ -412,6 +418,9 @@ BlazeComponent.extendComponent({
'click #toggleButton'() { 'click #toggleButton'() {
Meteor.call('toggleSystemMessages'); Meteor.call('toggleSystemMessages');
}, },
'click #toggleCustomFieldsGridButton'() {
Meteor.call('toggleCustomFieldsGrid');
},
'click .js-maximize-card-details'() { 'click .js-maximize-card-details'() {
Meteor.call('toggleCardMaximized'); Meteor.call('toggleCardMaximized');
autosize($('.card-details')); autosize($('.card-details'));
@ -511,6 +520,23 @@ BlazeComponent.extendComponent({
}, },
}).register('cardDetails'); }).register('cardDetails');
Template.cardDetails.helpers({
isPopup() {
let ret = !!Utils.getPopupCardId();
return ret;
}
});
Template.cardDetailsPopup.onDestroyed(() => {
Session.delete('popupCardId');
Session.delete('popupCardBoardId');
});
Template.cardDetailsPopup.helpers({
popupCard() {
const ret = Utils.getPopupCard();
return ret;
},
});
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
template() { template() {
return 'exportCard'; return 'exportCard';
@ -541,8 +567,8 @@ BlazeComponent.extendComponent({
}).register('exportCardPopup'); }).register('exportCardPopup');
// only allow number input // only allow number input
Template.editCardSortOrderForm.onRendered(function() { Template.editCardSortOrderForm.onRendered(function () {
this.$('input').on("keypress paste", function(event) { this.$('input').on("keypress paste", function (event) {
let keyCode = event.keyCode; let keyCode = event.keyCode;
let charCode = String.fromCharCode(keyCode); let charCode = String.fromCharCode(keyCode);
let regex = new RegExp('[-0-9.]'); let regex = new RegExp('[-0-9.]');
@ -561,16 +587,15 @@ Template.editCardSortOrderForm.onRendered(function() {
// XXX Recovering the currentCard identifier form a session variable is // XXX Recovering the currentCard identifier form a session variable is
// fragile because this variable may change for instance if the route // fragile because this variable may change for instance if the route
// change. We should use some component props instead. // change. We should use some component props instead.
docId: Session.get('currentCard'), docId: Utils.getCurrentCardId(),
}; };
} }
close(isReset = false) { close(isReset = false) {
if (this.isOpen.get() && !isReset) { if (this.isOpen.get() && !isReset) {
const draft = this.getValue().trim(); const draft = this.getValue().trim();
if ( let card = Utils.getCurrentCard();
draft !== Cards.findOne(Session.get('currentCard')).getDescription() if (card && draft !== card.getDescription()) {
) {
UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue()); UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue());
} }
} }
@ -615,7 +640,6 @@ Template.cardDetailsActionsPopup.events({
'click .js-export-card': Popup.open('exportCard'), 'click .js-export-card': Popup.open('exportCard'),
'click .js-members': Popup.open('cardMembers'), 'click .js-members': Popup.open('cardMembers'),
'click .js-assignees': Popup.open('cardAssignees'), 'click .js-assignees': Popup.open('cardAssignees'),
'click .js-labels': Popup.open('cardLabels'),
'click .js-attachments': Popup.open('cardAttachments'), 'click .js-attachments': Popup.open('cardAttachments'),
'click .js-start-voting': Popup.open('cardStartVoting'), 'click .js-start-voting': Popup.open('cardStartVoting'),
'click .js-start-planning-poker': Popup.open('cardStartPlanningPoker'), 'click .js-start-planning-poker': Popup.open('cardStartPlanningPoker'),
@ -634,25 +658,27 @@ Template.cardDetailsActionsPopup.events({
event.preventDefault(); event.preventDefault();
const minOrder = _.min( const minOrder = _.min(
this.list() this.list()
.cards(this.swimlaneId) .cardsUnfiltered(this.swimlaneId)
.map((c) => c.sort), .map((c) => c.sort),
); );
this.move(this.boardId, this.swimlaneId, this.listId, minOrder - 1); this.move(this.boardId, this.swimlaneId, this.listId, minOrder - 1);
Popup.back();
}, },
'click .js-move-card-to-bottom'(event) { 'click .js-move-card-to-bottom'(event) {
event.preventDefault(); event.preventDefault();
const maxOrder = _.max( const maxOrder = _.max(
this.list() this.list()
.cards(this.swimlaneId) .cardsUnfiltered(this.swimlaneId)
.map((c) => c.sort), .map((c) => c.sort),
); );
this.move(this.boardId, this.swimlaneId, this.listId, maxOrder + 1); this.move(this.boardId, this.swimlaneId, this.listId, maxOrder + 1);
Popup.back();
}, },
'click .js-archive'(event) { 'click .js-archive': Popup.afterConfirm('cardArchive', function () {
event.preventDefault();
this.archive();
Popup.close(); Popup.close();
}, this.archive();
Utils.goBoardId(this.boardId);
}),
'click .js-more': Popup.open('cardMore'), 'click .js-more': Popup.open('cardMore'),
'click .js-toggle-watch-card'() { 'click .js-toggle-watch-card'() {
const currentCard = this; const currentCard = this;
@ -667,6 +693,64 @@ Template.editCardTitleForm.onRendered(function () {
autosize(this.$('.js-edit-card-title')); autosize(this.$('.js-edit-card-title'));
}); });
Template.cardMembersPopup.onCreated(function () {
let currBoard = Boards.findOne(Session.get('currentBoard'));
let members = currBoard.activeMembers();
// let query = {
// "teams.teamId": { $in: currBoard.teams.map(t => t.teamId) },
// };
// let boardTeamUsers = Users.find(query, {
// sort: { sort: 1 },
// });
// members = currBoard.activeMembers2(members, boardTeamUsers);
this.members = new ReactiveVar(members);
});
Template.cardMembersPopup.events({
'keyup .card-members-filter'(event) {
const members = filterMembers(event.target.value);
Template.instance().members.set(members);
}
});
Template.cardMembersPopup.helpers({
members() {
return Template.instance().members.get();
},
});
const filterMembers = (filterTerm) => {
let currBoard = Boards.findOne(Session.get('currentBoard'));
let members = currBoard.activeMembers();
// let query = {
// "teams.teamId": { $in: currBoard.teams.map(t => t.teamId) },
// };
// let boardTeamUsers = Users.find(query, {
// sort: { sort: 1 },
// });
// members = currBoard.activeMembers2(members, boardTeamUsers);
if (filterTerm) {
members = members
.map(member => ({
member,
user: Users.findOne(member.userId)
}))
.filter(({ user }) =>
(user.profile.fullname !== undefined && user.profile.fullname.toLowerCase().indexOf(filterTerm.toLowerCase()) !== -1)
|| user.profile.fullname === undefined && user.profile.username !== undefined && user.profile.username.toLowerCase().indexOf(filterTerm.toLowerCase()) !== -1)
.map(({ member }) => member);
}
return members;
}
Template.editCardTitleForm.events({ Template.editCardTitleForm.events({
'keydown .js-edit-card-title'(event) { 'keydown .js-edit-card-title'(event) {
// If enter key was pressed, submit the data // If enter key was pressed, submit the data
@ -707,7 +791,7 @@ Template.moveCardPopup.events({
'click .js-done'() { 'click .js-done'() {
// XXX We should *not* get the currentCard from the global state, but // XXX We should *not* get the currentCard from the global state, but
// instead from a “component” state. // instead from a “component” state.
const card = Cards.findOne(Session.get('currentCard')); const card = Utils.getCurrentCard();
const bSelect = $('.js-select-boards')[0]; const bSelect = $('.js-select-boards')[0];
let boardId; let boardId;
// if we are a worker, we won't have a board select so we just use the // if we are a worker, we won't have a board select so we just use the
@ -719,7 +803,13 @@ Template.moveCardPopup.events({
const slSelect = $('.js-select-swimlanes')[0]; const slSelect = $('.js-select-swimlanes')[0];
const swimlaneId = slSelect.options[slSelect.selectedIndex].value; const swimlaneId = slSelect.options[slSelect.selectedIndex].value;
card.move(boardId, swimlaneId, listId, 0); card.move(boardId, swimlaneId, listId, 0);
Popup.close();
// set new id's to card object in case the card is moved to top by the comment "moveCard" after this command (.js-move-card)
this.boardId = boardId;
this.swimlaneId = swimlaneId;
this.listId = listId;
Popup.back();
}, },
}); });
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
@ -765,7 +855,7 @@ BlazeComponent.extendComponent({
Template.copyCardPopup.events({ Template.copyCardPopup.events({
'click .js-done'() { 'click .js-done'() {
const card = Cards.findOne(Session.get('currentCard')); const card = Utils.getCurrentCard();
const lSelect = $('.js-select-lists')[0]; const lSelect = $('.js-select-lists')[0];
const listId = lSelect.options[lSelect.selectedIndex].value; const listId = lSelect.options[lSelect.selectedIndex].value;
const slSelect = $('.js-select-swimlanes')[0]; const slSelect = $('.js-select-swimlanes')[0];
@ -787,14 +877,14 @@ Template.copyCardPopup.events({
// See https://github.com/wekan/wekan/issues/80 // See https://github.com/wekan/wekan/issues/80
Filter.addException(_id); Filter.addException(_id);
Popup.close(); Popup.back();
} }
}, },
}); });
Template.convertChecklistItemToCardPopup.events({ Template.convertChecklistItemToCardPopup.events({
'click .js-done'() { 'click .js-done'() {
const card = Cards.findOne(Session.get('currentCard')); const card = Utils.getCurrentCard();
const lSelect = $('.js-select-lists')[0]; const lSelect = $('.js-select-lists')[0];
const listId = lSelect.options[lSelect.selectedIndex].value; const listId = lSelect.options[lSelect.selectedIndex].value;
const slSelect = $('.js-select-swimlanes')[0]; const slSelect = $('.js-select-swimlanes')[0];
@ -814,7 +904,7 @@ Template.convertChecklistItemToCardPopup.events({
}); });
Filter.addException(_id); Filter.addException(_id);
Popup.close(); Popup.back();
} }
}, },
@ -822,7 +912,7 @@ Template.convertChecklistItemToCardPopup.events({
Template.copyChecklistToManyCardsPopup.events({ Template.copyChecklistToManyCardsPopup.events({
'click .js-done'() { 'click .js-done'() {
const card = Cards.findOne(Session.get('currentCard')); const card = Utils.getCurrentCard();
const oldId = card._id; const oldId = card._id;
card._id = null; card._id = null;
const lSelect = $('.js-select-lists')[0]; const lSelect = $('.js-select-lists')[0];
@ -870,7 +960,7 @@ Template.copyChecklistToManyCardsPopup.events({
cmt.copy(_id); cmt.copy(_id);
}); });
} }
Popup.close(); Popup.back();
} }
}, },
}); });
@ -941,7 +1031,7 @@ BlazeComponent.extendComponent({
}, },
cards() { cards() {
const currentId = Session.get('currentCard'); const currentId = Utils.getCurrentCardId();
if (this.parentBoard.get()) { if (this.parentBoard.get()) {
return Cards.find({ return Cards.find({
boardId: this.parentBoard.get(), boardId: this.parentBoard.get(),
@ -980,30 +1070,11 @@ BlazeComponent.extendComponent({
events() { events() {
return [ return [
{ {
'click .js-copy-card-link-to-clipboard'() { 'click .js-copy-card-link-to-clipboard'(event) {
// Clipboard code from: const promise = Utils.copyTextToClipboard(location.origin + document.getElementById('cardURL').value);
// https://stackoverflow.com/questions/6300213/copy-selected-text-to-the-clipboard-without-using-flash-must-be-cross-browser
const StringToCopyElement = document.getElementById('cardURL'); const $tooltip = this.$('.copied-tooltip');
StringToCopyElement.select(); Utils.showCopied(promise, $tooltip);
if (document.execCommand('copy')) {
StringToCopyElement.blur();
} else {
document.getElementById('cardURL').selectionStart = 0;
document.getElementById('cardURL').selectionEnd = 999;
document.execCommand('copy');
if (window.getSelection) {
if (window.getSelection().empty) {
// Chrome
window.getSelection().empty();
} else if (window.getSelection().removeAllRanges) {
// Firefox
window.getSelection().removeAllRanges();
}
} else if (document.selection) {
// IE?
document.selection.empty();
}
}
}, },
'click .js-delete': Popup.afterConfirm('cardDelete', function () { 'click .js-delete': Popup.afterConfirm('cardDelete', function () {
Popup.close(); Popup.close();
@ -1019,9 +1090,8 @@ BlazeComponent.extendComponent({
// https://github.com/wekan/wekan/issues/2785 // https://github.com/wekan/wekan/issues/2785
const message = `${TAPi18n.__( const message = `${TAPi18n.__(
'delete-linked-card-before-this-card', 'delete-linked-card-before-this-card',
)} linkedId: ${ )} linkedId: ${this._id
this._id } at client/components/cards/cardDetails.js and https://github.com/wekan/wekan/issues/2785`;
} at client/components/cards/cardDetails.js and https://github.com/wekan/wekan/issues/2785`;
alert(message); alert(message);
} }
Utils.goBoardId(this.boardId); Utils.goBoardId(this.boardId);
@ -1074,12 +1144,12 @@ BlazeComponent.extendComponent({
if (endString) { if (endString) {
this.currentCard.setVoteEnd(endString); this.currentCard.setVoteEnd(endString);
} }
Popup.close(); Popup.back();
}, },
'click .js-remove-vote': Popup.afterConfirm('deleteVote', () => { 'click .js-remove-vote': Popup.afterConfirm('deleteVote', () => {
event.preventDefault(); event.preventDefault();
this.currentCard.unsetVote(); this.currentCard.unsetVote();
Popup.close(); Popup.back();
}), }),
'click a.js-toggle-vote-public'(event) { 'click a.js-toggle-vote-public'(event) {
event.preventDefault(); event.preventDefault();
@ -1119,7 +1189,7 @@ BlazeComponent.extendComponent({
// if active vote - store it // if active vote - store it
if (this.currentData().getVoteQuestion()) { if (this.currentData().getVoteQuestion()) {
this._storeDate(newDate.toDate()); this._storeDate(newDate.toDate());
Popup.close(); Popup.back();
} else { } else {
this.currentData().vote = { end: newDate.toDate() }; // set vote end temp this.currentData().vote = { end: newDate.toDate() }; // set vote end temp
Popup.back(); Popup.back();
@ -1153,86 +1223,77 @@ BlazeComponent.extendComponent({
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(usaDate.toDate()); this._storeDate(usaDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: usaDate.toDate() }; // set poker end temp this.currentData().poker = { end: usaDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (euroAmDate.isValid()) { } else if (euroAmDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(euroAmDate.toDate()); this._storeDate(euroAmDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: euroAmDate.toDate() }; // set poker end temp this.currentData().poker = { end: euroAmDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (euro24hDate.isValid()) { } else if (euro24hDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(euro24hDate.toDate()); this._storeDate(euro24hDate.toDate());
this.card.setPokerEnd(euro24hDate.toDate()); this.card.setPokerEnd(euro24hDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: euro24hDate.toDate() }; // set poker end temp this.currentData().poker = { end: euro24hDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (eurodotDate.isValid()) { } else if (eurodotDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(eurodotDate.toDate()); this._storeDate(eurodotDate.toDate());
this.card.setPokerEnd(eurodotDate.toDate()); this.card.setPokerEnd(eurodotDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: eurodotDate.toDate() }; // set poker end temp this.currentData().poker = { end: eurodotDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (minusDate.isValid()) { } else if (minusDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(minusDate.toDate()); this._storeDate(minusDate.toDate());
this.card.setPokerEnd(minusDate.toDate()); this.card.setPokerEnd(minusDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: minusDate.toDate() }; // set poker end temp this.currentData().poker = { end: minusDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (slashDate.isValid()) { } else if (slashDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(slashDate.toDate()); this._storeDate(slashDate.toDate());
this.card.setPokerEnd(slashDate.toDate()); this.card.setPokerEnd(slashDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: slashDate.toDate() }; // set poker end temp this.currentData().poker = { end: slashDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (dotDate.isValid()) { } else if (dotDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(dotDate.toDate()); this._storeDate(dotDate.toDate());
this.card.setPokerEnd(dotDate.toDate()); this.card.setPokerEnd(dotDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: dotDate.toDate() }; // set poker end temp this.currentData().poker = { end: dotDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (brezhonegDate.isValid()) { } else if (brezhonegDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(brezhonegDate.toDate()); this._storeDate(brezhonegDate.toDate());
this.card.setPokerEnd(brezhonegDate.toDate()); this.card.setPokerEnd(brezhonegDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: brezhonegDate.toDate() }; // set poker end temp this.currentData().poker = { end: brezhonegDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (hrvatskiDate.isValid()) { } else if (hrvatskiDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(hrvatskiDate.toDate()); this._storeDate(hrvatskiDate.toDate());
this.card.setPokerEnd(hrvatskiDate.toDate()); this.card.setPokerEnd(hrvatskiDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: hrvatskiDate.toDate() }; // set poker end temp this.currentData().poker = { end: hrvatskiDate.toDate() }; // set poker end temp
Popup.back(); Popup.back();
@ -1242,41 +1303,37 @@ BlazeComponent.extendComponent({
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(latviaDate.toDate()); this._storeDate(latviaDate.toDate());
this.card.setPokerEnd(latviaDate.toDate()); this.card.setPokerEnd(latviaDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: latviaDate.toDate() }; // set poker end temp this.currentData().poker = { end: latviaDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (nederlandsDate.isValid()) { } else if (nederlandsDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(nederlandsDate.toDate()); this._storeDate(nederlandsDate.toDate());
this.card.setPokerEnd(nederlandsDate.toDate()); this.card.setPokerEnd(nederlandsDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: nederlandsDate.toDate() }; // set poker end temp this.currentData().poker = { end: nederlandsDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (greekDate.isValid()) { } else if (greekDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(greekDate.toDate()); this._storeDate(greekDate.toDate());
this.card.setPokerEnd(greekDate.toDate()); this.card.setPokerEnd(greekDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: greekDate.toDate() }; // set poker end temp this.currentData().poker = { end: greekDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (macedonianDate.isValid()) { } else if (macedonianDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(macedonianDate.toDate()); this._storeDate(macedonianDate.toDate());
this.card.setPokerEnd(macedonianDate.toDate()); this.card.setPokerEnd(macedonianDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: macedonianDate.toDate() }; // set poker end temp this.currentData().poker = { end: macedonianDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else { } else {
this.error.set('invalid-date'); this.error.set('invalid-date');
evt.target.date.focus(); evt.target.date.focus();
@ -1285,7 +1342,7 @@ BlazeComponent.extendComponent({
'click .js-delete-date'(evt) { 'click .js-delete-date'(evt) {
evt.preventDefault(); evt.preventDefault();
this._deleteDate(); this._deleteDate();
Popup.close(); Popup.back();
}, },
}, },
]; ];
@ -1323,11 +1380,11 @@ BlazeComponent.extendComponent({
if (endString) { if (endString) {
this.currentCard.setPokerEnd(endString); this.currentCard.setPokerEnd(endString);
} }
Popup.close(); Popup.back();
}, },
'click .js-remove-poker': Popup.afterConfirm('deletePoker', (event) => { 'click .js-remove-poker': Popup.afterConfirm('deletePoker', (event) => {
this.currentCard.unsetPoker(); this.currentCard.unsetPoker();
Popup.close(); Popup.back();
}), }),
'click a.js-toggle-poker-allow-non-members'(event) { 'click a.js-toggle-poker-allow-non-members'(event) {
event.preventDefault(); event.preventDefault();
@ -1390,7 +1447,7 @@ BlazeComponent.extendComponent({
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(newDate.toDate()); this._storeDate(newDate.toDate());
Popup.close(); Popup.back();
} else { } else {
this.currentData().poker = { end: newDate.toDate() }; // set poker end temp this.currentData().poker = { end: newDate.toDate() }; // set poker end temp
Popup.back(); Popup.back();
@ -1422,130 +1479,117 @@ BlazeComponent.extendComponent({
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(usaDate.toDate()); this._storeDate(usaDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: usaDate.toDate() }; // set poker end temp this.currentData().poker = { end: usaDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (euroAmDate.isValid()) { } else if (euroAmDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(euroAmDate.toDate()); this._storeDate(euroAmDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: euroAmDate.toDate() }; // set poker end temp this.currentData().poker = { end: euroAmDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (euro24hDate.isValid()) { } else if (euro24hDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(euro24hDate.toDate()); this._storeDate(euro24hDate.toDate());
this.card.setPokerEnd(euro24hDate.toDate()); this.card.setPokerEnd(euro24hDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: euro24hDate.toDate() }; // set poker end temp this.currentData().poker = { end: euro24hDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (eurodotDate.isValid()) { } else if (eurodotDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(eurodotDate.toDate()); this._storeDate(eurodotDate.toDate());
this.card.setPokerEnd(eurodotDate.toDate()); this.card.setPokerEnd(eurodotDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: eurodotDate.toDate() }; // set poker end temp this.currentData().poker = { end: eurodotDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (minusDate.isValid()) { } else if (minusDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(minusDate.toDate()); this._storeDate(minusDate.toDate());
this.card.setPokerEnd(minusDate.toDate()); this.card.setPokerEnd(minusDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: minusDate.toDate() }; // set poker end temp this.currentData().poker = { end: minusDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (slashDate.isValid()) { } else if (slashDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(slashDate.toDate()); this._storeDate(slashDate.toDate());
this.card.setPokerEnd(slashDate.toDate()); this.card.setPokerEnd(slashDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: slashDate.toDate() }; // set poker end temp this.currentData().poker = { end: slashDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (dotDate.isValid()) { } else if (dotDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(dotDate.toDate()); this._storeDate(dotDate.toDate());
this.card.setPokerEnd(dotDate.toDate()); this.card.setPokerEnd(dotDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: dotDate.toDate() }; // set poker end temp this.currentData().poker = { end: dotDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (brezhonegDate.isValid()) { } else if (brezhonegDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(brezhonegDate.toDate()); this._storeDate(brezhonegDate.toDate());
this.card.setPokerEnd(brezhonegDate.toDate()); this.card.setPokerEnd(brezhonegDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: brezhonegDate.toDate() }; // set poker end temp this.currentData().poker = { end: brezhonegDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (hrvatskiDate.isValid()) { } else if (hrvatskiDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(hrvatskiDate.toDate()); this._storeDate(hrvatskiDate.toDate());
this.card.setPokerEnd(hrvatskiDate.toDate()); this.card.setPokerEnd(hrvatskiDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: hrvatskiDate.toDate() }; // set poker end temp this.currentData().poker = { end: hrvatskiDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (latviaDate.isValid()) { } else if (latviaDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(latviaDate.toDate()); this._storeDate(latviaDate.toDate());
this.card.setPokerEnd(latviaDate.toDate()); this.card.setPokerEnd(latviaDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: latviaDate.toDate() }; // set poker end temp this.currentData().poker = { end: latviaDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (nederlandsDate.isValid()) { } else if (nederlandsDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(nederlandsDate.toDate()); this._storeDate(nederlandsDate.toDate());
this.card.setPokerEnd(nederlandsDate.toDate()); this.card.setPokerEnd(nederlandsDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: nederlandsDate.toDate() }; // set poker end temp this.currentData().poker = { end: nederlandsDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (greekDate.isValid()) { } else if (greekDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(greekDate.toDate()); this._storeDate(greekDate.toDate());
this.card.setPokerEnd(greekDate.toDate()); this.card.setPokerEnd(greekDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: greekDate.toDate() }; // set poker end temp this.currentData().poker = { end: greekDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else if (macedonianDate.isValid()) { } else if (macedonianDate.isValid()) {
// if active poker - store it // if active poker - store it
if (this.currentData().getPokerQuestion()) { if (this.currentData().getPokerQuestion()) {
this._storeDate(macedonianDate.toDate()); this._storeDate(macedonianDate.toDate());
this.card.setPokerEnd(macedonianDate.toDate()); this.card.setPokerEnd(macedonianDate.toDate());
Popup.close();
} else { } else {
this.currentData().poker = { end: macedonianDate.toDate() }; // set poker end temp this.currentData().poker = { end: macedonianDate.toDate() }; // set poker end temp
Popup.back();
} }
Popup.back();
} else { } else {
// this.error.set('invalid-date); // this.error.set('invalid-date);
this.error.set('invalid-date' + ' ' + dateString); this.error.set('invalid-date' + ' ' + dateString);
@ -1555,7 +1599,7 @@ BlazeComponent.extendComponent({
'click .js-delete-date'(evt) { 'click .js-delete-date'(evt) {
evt.preventDefault(); evt.preventDefault();
this._deleteDate(); this._deleteDate();
Popup.close(); Popup.back();
}, },
}, },
]; ];
@ -1589,13 +1633,34 @@ EscapeActions.register(
}, },
); );
Template.cardAssigneesPopup.onCreated(function () {
let currBoard = Boards.findOne(Session.get('currentBoard'));
let members = currBoard.activeMembers();
// let query = {
// "teams.teamId": { $in: currBoard.teams.map(t => t.teamId) },
// };
// let boardTeamUsers = Users.find(query, {
// sort: { sort: 1 },
// });
// members = currBoard.activeMembers2(members, boardTeamUsers);
this.members = new ReactiveVar(members);
});
Template.cardAssigneesPopup.events({ Template.cardAssigneesPopup.events({
'click .js-select-assignee'(event) { 'click .js-select-assignee'(event) {
const card = Cards.findOne(Session.get('currentCard')); const card = Utils.getCurrentCard();
const assigneeId = this.userId; const assigneeId = this.userId;
card.toggleAssignee(assigneeId); card.toggleAssignee(assigneeId);
event.preventDefault(); event.preventDefault();
}, },
'keyup .card-assignees-filter'(event) {
const members = filterMembers(event.target.value);
Template.instance().members.set(members);
},
}); });
Template.cardAssigneesPopup.helpers({ Template.cardAssigneesPopup.helpers({
@ -1606,6 +1671,10 @@ Template.cardAssigneesPopup.helpers({
return _.contains(cardAssignees, this.userId); return _.contains(cardAssignees, this.userId);
}, },
members() {
return Template.instance().members.get();
},
user() { user() {
return Users.findOne(this.userId); return Users.findOne(this.userId);
}, },
@ -1657,7 +1726,7 @@ Template.cardAssigneePopup.helpers({
Template.cardAssigneePopup.events({ Template.cardAssigneePopup.events({
'click .js-remove-assignee'() { 'click .js-remove-assignee'() {
Cards.findOne(this.cardId).unassignAssignee(this.userId); Cards.findOne(this.cardId).unassignAssignee(this.userId);
Popup.close(); Popup.back();
}, },
'click .js-edit-profile': Popup.open('editProfile'), 'click .js-edit-profile': Popup.open('editProfile'),
}); });

View file

@ -4,15 +4,6 @@
avatar-radius = 50% avatar-radius = 50%
#cardURL_copy
// Have clipboard text not visible by moving it to far left
position: absolute
left: -2000px
top: 0px
#clipboard
white-space: normal
.assignee .assignee
border-radius: 3px border-radius: 3px
display: block display: block
@ -85,6 +76,12 @@ avatar-radius = 50%
box-shadow: 0 0 0 2px darken(white, 60%) inset box-shadow: 0 0 0 2px darken(white, 60%) inset
// Other card details // Other card details
.copied-tooltip
display: none
padding: 0px 10px;
background-color: #000000df;
color: #fff;
border-radius: 5px;
.card-details .card-details
padding: 0 padding: 0
@ -127,7 +124,8 @@ avatar-radius = 50%
.card-copy-button, .card-copy-button,
.card-copy-mobile-button, .card-copy-mobile-button,
.close-card-details-mobile-web, .close-card-details-mobile-web,
.card-details-menu-mobile-web .card-details-menu-mobile-web,
.copied-tooltip
float: right float: right
.close-card-details, .close-card-details,
@ -196,6 +194,14 @@ avatar-radius = 50%
border-radius: 3px border-radius: 3px
padding: 0px 5px padding: 0px 5px
.copied-tooltip
display: none
margin-right: 10px
padding: 10px;
background-color: #000000df;
color: #fff;
border-radius: 5px;
.card-description textarea .card-description textarea
min-height: 100px min-height: 100px
@ -230,55 +236,54 @@ avatar-radius = 50%
word-wrap: break-word word-wrap: break-word
max-width: 28% max-width: 28%
flex-grow: 1 flex-grow: 1
&.custom-fields
padding-left: 10px
.card-details-item-title .card-details-item-title
font-size: 16px font-size: 16px
font-weight: bold font-weight: bold
color: #4d4d4d color: #4d4d4d
.card-label
padding-top: 5px
padding-bottom: 5px
.activities .activities
padding-top: 10px padding-top: 10px
.card-details-maximized @media screen and (min-width: 801px)
padding: 0 .card-details-maximized
flex-shrink: 0 padding: 0
flex-basis: calc(100% - 20px) flex-shrink: 0
will-change: flex-basis flex-basis: calc(100% - 20px)
overflow-y: scroll will-change: flex-basis
overflow-x: scroll overflow-y: scroll
background: darken(white, 3%) overflow-x: scroll
border-radius: bottom 3px background: darken(white, 3%)
z-index: 1000 !important border-radius: bottom 3px
animation: flexGrowIn 0.1s z-index: 1000 !important
box-shadow: 0 0 7px 0 darken(white, 30%) animation: flexGrowIn 0.1s
transition: flex-basis 0.1s box-shadow: 0 0 7px 0 darken(white, 30%)
box-sizing: border-box transition: flex-basis 0.1s
position: absolute box-sizing: border-box
top: 0
left: 0
height: calc(100% - 20px)
width: calc(100% - 20px)
float: left
.card-details-left
position: absolute position: absolute
top: 0
left: 0
height: calc(100% - 20px)
width: calc(100% - 20px)
float: left float: left
top: 60px
left: 20px
width: 47%
.card-details-right .card-details-left
position: absolute float: left
float: right top: 60px
top: 20px left: 20px
left: 50% width: 47%
.card-details-header .card-details-right
width: 47% position: absolute
float: right
top: 20px
left: 50%
.card-details-header
width: 47%
input[type="text"].attachment-add-link-input input[type="text"].attachment-add-link-input
float: left float: left
@ -297,6 +302,8 @@ input[type="submit"].attachment-add-link-submit
padding: 0px 20px 0px 20px padding: 0px 20px 0px 20px
margin: 0px margin: 0px
transition: none transition: none
overflow-y: revert
overflow-x: revert
.card-details-canvas .card-details-canvas
width: 100% width: 100%
@ -315,6 +322,21 @@ input[type="submit"].attachment-add-link-submit
.minimize-card-details .minimize-card-details
margin-right: 40px margin-right: 40px
.card-details-popup
padding: 0px 10px
.pop-over > .content-wrapper > .popup-container-depth-0
width: 100%
& > .content
width: calc(100% - 10px)
& > .content > .card-details-popup hr
margin: 15px 0px
.card-details-header
margin: 0
card-details-color(background, color...) card-details-color(background, color...)
background: background !important background: background !important
if color if color

View file

@ -9,7 +9,6 @@ BlazeComponent.extendComponent({
toggleOvertime() { toggleOvertime() {
this.card.setIsOvertime(!this.card.getIsOvertime()); this.card.setIsOvertime(!this.card.getIsOvertime());
$('#overtime .materialCheckBox').toggleClass('is-checked'); $('#overtime .materialCheckBox').toggleClass('is-checked');
$('#overtime').toggleClass('is-checked'); $('#overtime').toggleClass('is-checked');
}, },
storeTime(spentTime, isOvertime) { storeTime(spentTime, isOvertime) {
@ -18,6 +17,7 @@ BlazeComponent.extendComponent({
}, },
deleteTime() { deleteTime() {
this.card.setSpentTime(null); this.card.setSpentTime(null);
this.card.setIsOvertime(false);
}, },
events() { events() {
return [ return [
@ -27,11 +27,14 @@ BlazeComponent.extendComponent({
evt.preventDefault(); evt.preventDefault();
const spentTime = parseFloat(evt.target.time.value); const spentTime = parseFloat(evt.target.time.value);
const isOvertime = this.card.getIsOvertime(); //const isOvertime = this.card.getIsOvertime();
let isOvertime = false;
if ($('#overtime').attr('class').indexOf('is-checked') >= 0) {
isOvertime = true;
}
if (spentTime >= 0) { if (spentTime >= 0) {
this.storeTime(spentTime, isOvertime); this.storeTime(spentTime, isOvertime);
Popup.close(); Popup.back();
} else { } else {
this.error.set('invalid-time'); this.error.set('invalid-time');
evt.target.time.focus(); evt.target.time.focus();
@ -40,7 +43,7 @@ BlazeComponent.extendComponent({
'click .js-delete-time'(evt) { 'click .js-delete-time'(evt) {
evt.preventDefault(); evt.preventDefault();
this.deleteTime(); this.deleteTime();
Popup.close(); Popup.back();
}, },
'click a.js-toggle-overtime': this.toggleOvertime, 'click a.js-toggle-overtime': this.toggleOvertime,
}, },

View file

@ -12,20 +12,15 @@ template(name="checklists")
input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton") input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton")
label.toggle-label(for="toggleHideCheckedItemsButton") label.toggle-label(for="toggleHideCheckedItemsButton")
if toggleDeleteDialog.get
.board-overlay#card-details-overlay
+checklistDeleteDialog(checklist = checklistToDelete)
.card-checklist-items .card-checklist-items
each checklist in currentCard.checklists each checklist in checklists
+checklistDetail(checklist = checklist) +checklistDetail(checklist = checklist)
if canModifyCard if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId) +inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId)
+addChecklistItemForm +addChecklistItemForm
else else
a.js-open-inlined-form(title="{{_ 'add-checklist'}}") a.add-checklist.js-open-inlined-form(title="{{_ 'add-checklist'}}")
i.fa.fa-plus i.fa.fa-plus
template(name="checklistDetail") template(name="checklistDetail")
@ -50,25 +45,21 @@ template(name="checklistDetail")
= checklist.title = checklist.title
+checklistItems(checklist = checklist) +checklistItems(checklist = checklist)
template(name="checklistDeleteDialog") template(name="checklistDeletePopup")
.js-confirm-checklist-delete p {{_ 'confirm-checklist-delete-popup'}}
p button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
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") template(name="addChecklistItemForm")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.js-add-checklist-item(rows='1' autofocus) textarea.js-add-checklist-item(rows='1' autofocus)
.edit-controls.clearfix .edit-controls.clearfix
button.primary.confirm.js-submit-add-checklist-item-form(type="submit") {{_ 'save'}} button.primary.confirm.js-submit-add-checklist-item-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form a.fa.fa-times-thin.js-close-inlined-form
template(name="editChecklistItemForm") 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") textarea.js-edit-checklist-item(rows='1' autofocus dir="auto")
if $eq type 'item' if $eq type 'item'
= item.title = item.title

View file

@ -13,10 +13,10 @@ function initSorting(items) {
appendTo: 'parent', appendTo: 'parent',
distance: 7, distance: 7,
placeholder: 'checklist-item placeholder', placeholder: 'checklist-item placeholder',
scroll: false, scroll: true,
start(evt, ui) { start(evt, ui) {
ui.placeholder.height(ui.helper.height()); ui.placeholder.height(ui.helper.height());
EscapeActions.executeUpTo('popup-close'); EscapeActions.clickExecute(evt.target, 'inlinedForm');
}, },
stop(evt, ui) { stop(evt, ui) {
const parent = ui.item.parents('.js-checklist-items'); const parent = ui.item.parents('.js-checklist-items');
@ -55,7 +55,7 @@ BlazeComponent.extendComponent({
return Meteor.user() && Meteor.user().isBoardMember(); return Meteor.user() && Meteor.user().isBoardMember();
} }
// Disable sorting if the current user is not a board member or is a miniscreen // Disable sorting if the current user is not a board member
self.autorun(() => { self.autorun(() => {
const $itemsDom = $(self.itemsDom); const $itemsDom = $(self.itemsDom);
if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) { if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
@ -94,16 +94,14 @@ BlazeComponent.extendComponent({
title, title,
sort: card.checklists().count(), sort: card.checklists().count(),
}); });
this.closeAllInlinedForms();
setTimeout(() => { setTimeout(() => {
this.$('.add-checklist-item') this.$('.add-checklist-item')
.last() .last()
.click(); .click();
}, 100); }, 100);
} }
textarea.value = '';
textarea.focus();
}, },
addChecklistItem(event) { addChecklistItem(event) {
event.preventDefault(); event.preventDefault();
const textarea = this.find('textarea.js-add-checklist-item'); const textarea = this.find('textarea.js-add-checklist-item');
@ -132,14 +130,6 @@ BlazeComponent.extendComponent({
); );
}, },
deleteChecklist() {
const checklist = this.currentData().checklist;
if (checklist && checklist._id) {
Checklists.remove(checklist._id);
this.toggleDeleteDialog.set(false);
}
},
deleteItem() { deleteItem() {
const checklist = this.currentData().checklist; const checklist = this.currentData().checklist;
const item = this.currentData().item; const item = this.currentData().item;
@ -165,11 +155,6 @@ BlazeComponent.extendComponent({
item.setTitle(title); item.setTitle(title);
}, },
onCreated() {
this.toggleDeleteDialog = new ReactiveVar(false);
this.checklistToDelete = null; //Store data context to pass to checklistDeleteDialog template
},
pressKey(event) { pressKey(event) {
//If user press enter key inside a form, submit it //If user press enter key inside a form, submit it
//Unless the user is also holding down the 'shift' key //Unless the user is also holding down the 'shift' key
@ -190,14 +175,13 @@ BlazeComponent.extendComponent({
} }
}, },
/** closes all inlined forms (checklist and checklist-item input fields) */
closeAllInlinedForms() {
this.$('.js-close-inlined-form').click();
},
events() { events() {
const events = { const events = {
'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());
},
'click #toggleHideCheckedItemsButton'() { 'click #toggleHideCheckedItemsButton'() {
Meteor.call('toggleHideCheckedItems'); Meteor.call('toggleHideCheckedItems');
}, },
@ -206,14 +190,22 @@ BlazeComponent.extendComponent({
return [ return [
{ {
...events, ...events,
'click .toggle-delete-checklist-dialog' : Popup.afterConfirm('checklistDelete', function () {
Popup.close();
const checklist = this.checklist;
if (checklist && checklist._id) {
Checklists.remove(checklist._id);
}
}),
'submit .js-add-checklist': this.addChecklist, 'submit .js-add-checklist': this.addChecklist,
'submit .js-edit-checklist-title': this.editChecklist, 'submit .js-edit-checklist-title': this.editChecklist,
'submit .js-add-checklist-item': this.addChecklistItem, 'submit .js-add-checklist-item': this.addChecklistItem,
'submit .js-edit-checklist-item': this.editChecklistItem, 'submit .js-edit-checklist-item': this.editChecklistItem,
'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'), 'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'),
'click .js-delete-checklist-item': this.deleteItem, 'click .js-delete-checklist-item': this.deleteItem,
'click .confirm-checklist-delete': this.deleteChecklist,
'focus .js-add-checklist-item': this.focusChecklistItem, 'focus .js-add-checklist-item': this.focusChecklistItem,
// add and delete checklist / checklist-item
'click .js-open-inlined-form': this.closeAllInlinedForms,
keydown: this.pressKey, keydown: this.pressKey,
}, },
]; ];
@ -262,6 +254,11 @@ BlazeComponent.extendComponent({
}).register('boardsSwimlanesAndLists'); }).register('boardsSwimlanesAndLists');
Template.checklists.helpers({ Template.checklists.helpers({
checklists() {
const card = Cards.findOne(this.cardId);
const ret = card.checklists();
return ret;
},
hideCheckedItems() { hideCheckedItems() {
const currentUser = Meteor.user(); const currentUser = Meteor.user();
if (currentUser) return currentUser.hasHideCheckedItems(); if (currentUser) return currentUser.hasHideCheckedItems();
@ -269,39 +266,59 @@ Template.checklists.helpers({
}, },
}); });
Template.addChecklistItemForm.onRendered(() => { BlazeComponent.extendComponent({
autosize($('textarea.js-add-checklist-item')); 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);
Template.editChecklistItemForm.onRendered(() => { const $tooltip = this.$('.copied-tooltip');
autosize($('textarea.js-edit-checklist-item')); Utils.showCopied(promise, $tooltip);
}); },
}
];
}
}).register('addChecklistItemForm');
Template.checklistDeleteDialog.onCreated(() => { BlazeComponent.extendComponent({
const $cardDetails = this.$('.card-details'); onRendered() {
this.scrollState = { autosize(this.$('textarea.js-edit-checklist-item'));
position: $cardDetails.scrollTop(), //save current scroll position },
top: false, //required for smooth scroll animation canModifyCard() {
}; return (
//Callback's purpose is to only prevent scrolling after animation is complete Meteor.user() &&
$cardDetails.animate({ scrollTop: 0 }, 500, () => { Meteor.user().isBoardMember() &&
this.scrollState.top = true; !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);
//Prevent scrolling while dialog is open const $tooltip = this.$('.copied-tooltip');
$cardDetails.on('scroll', () => { Utils.showCopied(promise, $tooltip);
if (this.scrollState.top) { },
//If it's already in position, keep it there. Otherwise let animation scroll }
$cardDetails.scrollTop(0); ];
} }
}); }).register('editChecklistItemForm');
});
Template.checklistDeleteDialog.onDestroyed(() => {
const $cardDetails = this.$('.card-details');
$cardDetails.off('scroll'); //Reactivate scrolling
$cardDetails.animate({ scrollTop: this.scrollState.position });
});
Template.checklistItemDetail.helpers({ Template.checklistItemDetail.helpers({
canModifyCard() { canModifyCard() {

View file

@ -47,41 +47,6 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
padding-top: 3px padding-top: 3px
float: left float: left
.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 #card-details-overlay
top: 0 top: 0
bottom: -600px bottom: -600px
@ -167,4 +132,13 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
.add-checklist-item .add-checklist-item
margin: 0.2em 0 0.5em 1.33em margin: 0.2em 0 0.5em 1.33em
display: inline-block
.add-checklist-item,.add-checklist
&.js-open-inlined-form
display: block
width: 50%
&:hover
background: #dbdbdb
color: #222
box-shadow: 0 1px 2px rgba(0,0,0,.2)

View file

@ -27,9 +27,11 @@ template(name="deleteLabelPopup")
template(name="cardLabelsPopup") template(name="cardLabelsPopup")
ul.edit-labels-pop-over ul.edit-labels-pop-over
each board.labels each board.labels
li li.js-card-label-item
a.card-label-edit-button.fa.fa-pencil.js-edit-label a.card-label-edit-button.fa.fa-pencil.js-edit-label
span.card-label.card-label-selectable.js-select-label(class="card-label-{{color}}" if isMiniScreenOrShowDesktopDragHandles
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}}"
class="{{# if isLabelSelected ../_id }}active{{/if}}") class="{{# if isLabelSelected ../_id }}active{{/if}}")
+viewer +viewer
= name = name

View file

@ -39,15 +39,67 @@ Template.createLabelPopup.helpers({
}, },
}); });
Template.cardLabelsPopup.events({ BlazeComponent.extendComponent({
'click .js-select-label'(event) { onRendered() {
const card = Cards.findOne(Session.get('currentCard')); const itemsSelector = 'li.js-card-label-item:not(.placeholder)';
const labelId = this._id; const $labels = this.$('.edit-labels-pop-over');
card.toggleLabel(labelId);
event.preventDefault(); $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.isMiniScreenOrShowDesktopDragHandles()) {
$labels.sortable({
handle: '.label-handle',
});
}
});
}, },
'click .js-edit-label': Popup.open('editLabel'), events() {
'click .js-add-label': Popup.open('createLabel'), 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({
}); });
Template.formLabel.events({ Template.formLabel.events({

View file

@ -2,6 +2,7 @@
// XXX Use .board-widget-labels as a flexbox container // XXX Use .board-widget-labels as a flexbox container
.card-label .card-label
border: 1px solid #000000
border-radius: 4px border-radius: 4px
color: white //Default white text, in select cases, changed to black to improve contrast between label colour and text color: white //Default white text, in select cases, changed to black to improve contrast between label colour and text
display: inline-block display: inline-block
@ -12,10 +13,11 @@
padding: 3px 8px padding: 3px 8px
max-width: 210px max-width: 210px
min-width: 8px min-width: 8px
overflow: ellipsis
word-wrap: break-word word-wrap: break-word
height: 18px min-height: 18px
vertical-align: bottom vertical-align: middle
white-space: initial
overflow: initial
&:hover &:hover
color: white color: white
@ -27,12 +29,13 @@
&.add-label &.add-label
box-shadow: 0 0 0 2px darken(white, 25%) inset box-shadow: 0 0 0 2px darken(white, 25%) inset
border: initial
&:hover, &.is-active &:hover, &.is-active
box-shadow: 0 0 0 2px darken(white, 60%) inset box-shadow: 0 0 0 2px darken(white, 60%) inset
i.fa-plus p
margin-top: 3px margin: 0px
.palette-colors .palette-colors
display: flex display: flex
@ -47,7 +50,6 @@
.card-label-white .card-label-white
background-color: #ffffff background-color: #ffffff
color: #000000 //Black text for better visibility color: #000000 //Black text for better visibility
border: 1px solid #c0c0c0
.card-label-white:hover .card-label-white:hover
color: #aaaaaa //grey text for better visibility color: #aaaaaa //grey text for better visibility
@ -144,6 +146,7 @@
height: 25px height: 25px
margin: 0px 3% 7px 0px margin: 0px 3% 7px 0px
width: 10.5% width: 10.5%
max-width: 10.5%
cursor: pointer cursor: pointer
.edit-labels .edit-labels
@ -220,3 +223,9 @@
&:hover &:hover
background: #dbdbdb background: #dbdbdb
ul.edit-labels-pop-over
span.fa.label-handle
padding-right: 10px;
span.fa.label-handle + .card-label
max-width: 180px

View file

@ -2,21 +2,17 @@ template(name="minicard")
.minicard( .minicard(
class="{{#if isLinkedCard}}linked-card{{/if}}" class="{{#if isLinkedCard}}linked-card{{/if}}"
class="{{#if isLinkedBoard}}linked-board{{/if}}" class="{{#if isLinkedBoard}}linked-board{{/if}}"
class="minicard-{{colorClass}}") class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
if isMiniScreen if isMiniScreenOrShowDesktopDragHandles
.handle .handle
.fa.fa-arrows .fa.fa-arrows
unless isMiniScreen
if showDesktopDragHandles
.handle
.fa.fa-arrows
if cover if cover
.minicard-cover(style="background-image: url('{{cover.url}}');") .minicard-cover(style="background-image: url('{{cover.url}}');")
if labels if labels
.minicard-labels .minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
each labels each labels
unless hiddenMinicardLabelText unless hiddenMinicardLabelText
span.card-label(class="card-label-{{color}}" title=name) span.js-card-label.card-label(class="card-label-{{color}}" title=name)
+viewer +viewer
= name = name
if hiddenMinicardLabelText if hiddenMinicardLabelText
@ -92,15 +88,17 @@ template(name="minicard")
+viewer +viewer
= trueValue = trueValue
if getAssignees if showAssignee
.minicard-assignees.js-minicard-assignees if getAssignees
each getAssignees .minicard-assignees.js-minicard-assignees
+userAvatar(userId=this) each getAssignees
+userAvatar(userId=this)
if getMembers if showMembers
.minicard-members.js-minicard-members if getMembers
each getMembers .minicard-members.js-minicard-members
+userAvatar(userId=this) each getMembers
+userAvatar(userId=this)
if showCreator if showCreator
.minicard-creator .minicard-creator
@ -145,4 +143,9 @@ template(name="minicard")
if currentBoard.allowsCardSortingByNumber if currentBoard.allowsCardSortingByNumber
.badge .badge
span.badge-icon.fa.fa-sort span.badge-icon.fa.fa-sort
span.badge-text {{ sort }} span.badge-text.check-list-sort {{ sort }}
template(name="editCardSortOrderPopup")
input.js-edit-card-sort-popup(type='text' autofocus value=sort dir="auto")
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-card-sort-popup(type="submit") {{_ 'save'}}

View file

@ -49,6 +49,38 @@ BlazeComponent.extendComponent({
return false; return false;
}, },
showMembers() {
if (this.data().board()) {
return (
this.data().board.allowsMembers === null ||
this.data().board().allowsMembers === undefined ||
this.data().board().allowsMembers
);
}
return false;
},
showAssignee() {
if (this.data().board()) {
return (
this.data().board.allowsAssignee === null ||
this.data().board().allowsAssignee === undefined ||
this.data().board().allowsAssignee
);
}
return false;
},
/** opens the card label popup only if clicked onto a label
* <li> this is necessary to have the data context of the minicard.
* if .js-card-label is used at click event, then only the data context of the label itself is available at this.currentData()
*/
cardLabelsPopup(event) {
if (this.find('.js-card-label:hover')) {
Popup.open("cardLabels")(event, {dataContextIfCurrentDataIsUndefined: this.currentData()});
}
},
events() { events() {
return [ return [
{ {
@ -57,8 +89,6 @@ BlazeComponent.extendComponent({
else if (this.data().isLinkedBoard()) else if (this.data().isLinkedBoard())
Utils.goBoardId(this.data().linkedId); Utils.goBoardId(this.data().linkedId);
}, },
},
{
'click .js-toggle-minicard-label-text'() { 'click .js-toggle-minicard-label-text'() {
if (window.localStorage.getItem('hiddenMinicardLabelText')) { if (window.localStorage.getItem('hiddenMinicardLabelText')) {
window.localStorage.removeItem('hiddenMinicardLabelText'); //true window.localStorage.removeItem('hiddenMinicardLabelText'); //true
@ -66,22 +96,14 @@ BlazeComponent.extendComponent({
window.localStorage.setItem('hiddenMinicardLabelText', 'true'); //true window.localStorage.setItem('hiddenMinicardLabelText', 'true'); //true
} }
}, },
}, 'click span.badge-icon.fa.fa-sort, click span.badge-text.check-list-sort' : Popup.open("editCardSortOrder"),
'click .minicard-labels' : this.cardLabelsPopup,
}
]; ];
}, },
}).register('minicard'); }).register('minicard');
Template.minicard.helpers({ Template.minicard.helpers({
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
hiddenMinicardLabelText() { hiddenMinicardLabelText() {
currentUser = Meteor.user(); currentUser = Meteor.user();
if (currentUser) { if (currentUser) {
@ -93,3 +115,30 @@ Template.minicard.helpers({
} }
}, },
}); });
BlazeComponent.extendComponent({
events() {
return [
{
'keydown input.js-edit-card-sort-popup'(evt) {
// enter = save
if (evt.keyCode === 13) {
this.find('button[type=submit]').click();
}
},
'click button.js-submit-edit-card-sort-popup'(event) {
// save button pressed
event.preventDefault();
const sort = this.$('.js-edit-card-sort-popup')[0]
.value
.trim();
if (!Number.isNaN(sort)) {
let card = this.data();
card.move(card.boardId, card.swimlaneId, card.listId, sort);
Popup.back();
}
},
}
]
}
}).register('editCardSortOrderPopup');

View file

@ -80,8 +80,6 @@
.minicard-labels .minicard-labels
float: none float: none
display: flex
flex-wrap: wrap
.minicard-label .minicard-label
width: 11px width: 11px
@ -90,6 +88,10 @@
margin-right: 3px margin-right: 3px
margin-bottom: 3px margin-bottom: 3px
.minicard-labels-no-text
display: flex
flex-wrap: wrap
.minicard-custom-fields .minicard-custom-fields
display:block; display:block;
.minicard-custom-field .minicard-custom-field

View file

@ -1,6 +1,6 @@
template(name="resultCard") template(name="resultCard")
.result-card-wrapper .result-card-wrapper
a.minicard-wrapper.card-title(href=originRelativeUrl) a.minicard-wrapper.js-minicard.card-title(href=originRelativeUrl)
+minicard(this) +minicard(this)
//= card.title //= card.title
ul.result-card-context-list ul.result-card-context-list

View file

@ -5,7 +5,31 @@ Template.resultCard.helpers({
}); });
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
clickOnMiniCard(evt) {
evt.preventDefault();
const this_ = this;
const cardId = this.currentData()._id;
const boardId = this.currentData().boardId;
Meteor.subscribe('popupCardData', cardId, {
onReady() {
Session.set('popupCardId', cardId);
Session.set('popupCardBoardId', boardId);
this_.cardDetailsPopup(evt);
},
});
},
cardDetailsPopup(event) {
if (!Popup.isOpen()) {
Popup.open("cardDetails")(event);
}
},
events() { events() {
return [{}]; return [
{
'click .js-minicard': this.clickOnMiniCard,
},
];
}, },
}).register('resultCard'); }).register('resultCard');

View file

@ -37,6 +37,8 @@ BlazeComponent.extendComponent({
? targetBoard.getDefaultSwimline()._id ? targetBoard.getDefaultSwimline()._id
: targetSwimlane._id; : targetSwimlane._id;
const nextCardNumber = targetBoard.getNextCardNumber();
if (title) { if (title) {
const _id = Cards.insert({ const _id = Cards.insert({
title, title,
@ -49,6 +51,7 @@ BlazeComponent.extendComponent({
sort: sortIndex, sort: sortIndex,
swimlaneId, swimlaneId,
type: 'cardType-card', type: 'cardType-card',
cardNumber: nextCardNumber
}); });
// In case the filter is active we need to add the newly inserted card in // In case the filter is active we need to add the newly inserted card in

View file

@ -247,6 +247,7 @@ textarea
position: absolute position: absolute
left: -9999px left: -9999px
visibility: hidden visibility: hidden
display: none
.materialCheckBox .materialCheckBox
position: relative position: relative

View file

@ -1,3 +1,5 @@
require('/client/lib/jquery-ui.js')
const { calculateIndex } = Utils; const { calculateIndex } = Utils;
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
@ -93,7 +95,7 @@ BlazeComponent.extendComponent({
$cards.sortable('cancel'); $cards.sortable('cancel');
if (MultiSelection.isActive()) { if (MultiSelection.isActive()) {
Cards.find(MultiSelection.getMongoSelector()).forEach((card, i) => { Cards.find(MultiSelection.getMongoSelector(), {sort: ['sort']}).forEach((card, i) => {
const newSwimlaneId = targetSwimlaneId const newSwimlaneId = targetSwimlaneId
? targetSwimlaneId ? targetSwimlaneId
: card.swimlaneId || defaultSwimlaneId; : card.swimlaneId || defaultSwimlaneId;
@ -114,25 +116,51 @@ BlazeComponent.extendComponent({
} }
boardComponent.setIsDragging(false); boardComponent.setIsDragging(false);
}, },
sort(event, ui) {
const $boardCanvas = $('.board-canvas');
const boardCanvas = $boardCanvas[0];
if (event.pageX < 10)
{ // scroll to the left
boardCanvas.scrollLeft -= 15;
ui.helper[0].offsetLeft -= 15;
}
if (
event.pageX > boardCanvas.offsetWidth - 10 &&
boardCanvas.scrollLeft < $boardCanvas.data('scrollLeftMax') // don't scroll more than possible
)
{ // scroll to the right
boardCanvas.scrollLeft += 15;
}
if (
event.pageY > boardCanvas.offsetHeight - 10 &&
event.pageY + boardCanvas.scrollTop < $boardCanvas.data('scrollTopMax') // don't scroll more than possible
)
{ // scroll to the bottom
boardCanvas.scrollTop += 15;
}
if (event.pageY < 10)
{ // scroll to the top
boardCanvas.scrollTop -= 15;
}
},
activate(event, ui) {
const $boardCanvas = $('.board-canvas');
const boardCanvas = $boardCanvas[0];
// scrollTopMax and scrollLeftMax only available at Firefox (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTopMax)
// https://www.it-swarm.com.de/de/javascript/so-erhalten-sie-den-maximalen-dokument-scrolltop-wert/1069126844/
$boardCanvas.data('scrollTopMax', boardCanvas.scrollHeight - boardCanvas.clientTop);
// https://stackoverflow.com/questions/5138373/how-do-i-get-the-max-value-of-scrollleft/5704386#5704386
$boardCanvas.data('scrollLeftMax', boardCanvas.scrollWidth - boardCanvas.clientWidth);
},
}); });
this.autorun(() => { this.autorun(() => {
let showDesktopDragHandles = false; if (Utils.isMiniScreenOrShowDesktopDragHandles()) {
currentUser = Meteor.user();
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
showDesktopDragHandles = true;
} else {
showDesktopDragHandles = false;
}
if (Utils.isMiniScreen() || showDesktopDragHandles) {
$cards.sortable({ $cards.sortable({
handle: '.handle', handle: '.handle',
}); });
} else if (!Utils.isMiniScreen() && !showDesktopDragHandles) { } else {
$cards.sortable({ $cards.sortable({
handle: '.minicard', handle: '.minicard',
}); });
@ -178,19 +206,6 @@ BlazeComponent.extendComponent({
}, },
}).register('list'); }).register('list');
Template.list.helpers({
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
});
Template.miniList.events({ Template.miniList.events({
'click .js-select-list'() { 'click .js-select-list'() {
const listId = this._id; const listId = this._id;

View file

@ -85,13 +85,9 @@
color: #a6a6a6 color: #a6a6a6
.list-header-menu .list-header-menu
position: absolute float: right
padding: 27px 19px
margin-top: 1px
top: -7px
right: 3px
.list-header-plus-icon .list-header-plus-top
color: #a6a6a6 color: #a6a6a6
margin-right: 15px margin-right: 15px
@ -100,9 +96,10 @@
.cardCount .cardCount
color: #8c8c8c color: #8c8c8c
font-size: 0.8em font-size: 12px
font-weight: bold
.list-header .list-header-plus-icon, .js-open-list-menu, .list-header-menu a .list-header .list-header-plus-top, .js-open-list-menu, .list-header-menu a
color #4d4d4d color #4d4d4d
padding-left 4px padding-left 4px
@ -160,18 +157,6 @@
float: left float: left
@media screen and (max-width: 800px) @media screen and (max-width: 800px)
.list-header-menu
position: absolute
padding: 27px 19px
margin-top: 1px
top: -7px
margin-right: 7px
right: -3px
.list-header
.list-header-name
margin-left: 1.4rem
.mini-list .mini-list
flex: 0 0 60px flex: 0 0 60px
height: auto height: auto
@ -215,7 +200,6 @@
display: flex display: flex
align-items: center align-items: center
.list-header-left-icon .list-header-left-icon
display: inline
padding: 7px padding: 7px
padding-right: 27px padding-right: 27px
margin-top: 1px margin-top: 1px
@ -238,6 +222,30 @@
right: 10px right: 10px
font-size: 24px font-size: 24px
.list-header
display: grid
grid-template-columns: 30px 5fr 1fr
.list-header-left-icon
display: grid
grid-row: 1/3
grid-column: 1
.list-header-name
grid-row: 1
grid-column: 2
align-self: end
.cardCount
grid-row: 2
grid-column: 2
align-self: start
.list-header-menu
grid-row: 1/3
grid-column: 3
.inlined-form
grid-row: 1/3
grid-column: 1/4
.edit-controls
align-items: initial
.link-board-wrapper .link-board-wrapper
display: flex display: flex
align-items: baseline align-items: baseline

View file

@ -4,6 +4,17 @@ template(name="listBody")
if cards.count if cards.count
+inlinedForm(autoclose=false position="top") +inlinedForm(autoclose=false position="top")
+addCardForm(listId=_id position="top") +addCardForm(listId=_id position="top")
ul.sidebar-list
each customFieldsSum
li
+viewer
= name
if $eq customFieldsSum.type "number"
+viewer
= value
if $eq customFieldsSum.type "currency"
+viewer
= formattedCurrencyCustomFieldValue(value)
each (cardsWithLimit (idOrNull ../../_id)) each (cardsWithLimit (idOrNull ../../_id))
a.minicard-wrapper.js-minicard(href=originRelativeUrl a.minicard-wrapper.js-minicard(href=originRelativeUrl
class="{{#if cardIsSelected}}is-selected{{/if}}" class="{{#if cardIsSelected}}is-selected{{/if}}"
@ -42,6 +53,7 @@ template(name="addCardForm")
.add-controls.clearfix .add-controls.clearfix
button.primary.confirm(type="submit") {{_ 'add'}} button.primary.confirm(type="submit") {{_ 'add'}}
a.fa.fa-times-thin.js-close-inlined-form
unless currentBoard.isTemplatesBoard unless currentBoard.isTemplatesBoard
unless currentBoard.isTemplateBoard unless currentBoard.isTemplateBoard
span.quiet span.quiet

View file

@ -13,6 +13,13 @@ BlazeComponent.extendComponent({
return []; return [];
}, },
customFieldsSum() {
return CustomFields.find({
boardIds: { $in: [Session.get('currentBoard')] },
showSumAtTopOfList: true,
});
},
openForm(options) { openForm(options) {
options = options || {}; options = options || {};
options.position = options.position || 'top'; options.position = options.position || 'top';
@ -141,6 +148,10 @@ BlazeComponent.extendComponent({
// If the card is already selected, we want to de-select it. // If the card is already selected, we want to de-select it.
// XXX We should probably modify the minicard href attribute instead of // XXX We should probably modify the minicard href attribute instead of
// overwriting the event in case the card is already selected. // overwriting the event in case the card is already selected.
} else if (Utils.isMiniScreen()) {
evt.preventDefault();
Session.set('popupCardId', this.currentData()._id);
this.cardDetailsPopup(evt);
} else if (Session.equals('currentCard', this.currentData()._id)) { } else if (Session.equals('currentCard', this.currentData()._id)) {
evt.stopImmediatePropagation(); evt.stopImmediatePropagation();
evt.preventDefault(); evt.preventDefault();
@ -209,6 +220,12 @@ BlazeComponent.extendComponent({
); );
}, },
cardDetailsPopup(event) {
if (!Popup.isOpen()) {
Popup.open("cardDetails")(event);
}
},
events() { events() {
return [ return [
{ {
@ -479,7 +496,7 @@ BlazeComponent.extendComponent({
evt.preventDefault(); evt.preventDefault();
const linkedId = $('.js-select-cards option:selected').val(); const linkedId = $('.js-select-cards option:selected').val();
if (!linkedId) { if (!linkedId) {
Popup.close(); Popup.back();
return; return;
} }
const _id = Cards.insert({ const _id = Cards.insert({
@ -494,7 +511,7 @@ BlazeComponent.extendComponent({
linkedId, linkedId,
}); });
Filter.addException(_id); Filter.addException(_id);
Popup.close(); Popup.back();
}, },
'click .js-link-board'(evt) { 'click .js-link-board'(evt) {
//LINK BOARD //LINK BOARD
@ -505,7 +522,7 @@ BlazeComponent.extendComponent({
!impBoardId || !impBoardId ||
Cards.findOne({ linkedId: impBoardId, archived: false }) Cards.findOne({ linkedId: impBoardId, archived: false })
) { ) {
Popup.close(); Popup.back();
return; return;
} }
const _id = Cards.insert({ const _id = Cards.insert({
@ -520,7 +537,7 @@ BlazeComponent.extendComponent({
linkedId: impBoardId, linkedId: impBoardId,
}); });
Filter.addException(_id); Filter.addException(_id);
Popup.close(); Popup.back();
}, },
}, },
]; ];
@ -567,7 +584,7 @@ BlazeComponent.extendComponent({
}); });
} }
if (!board) { if (!board) {
Popup.close(); Popup.back();
return; return;
} }
const boardId = board._id; const boardId = board._id;
@ -694,7 +711,7 @@ BlazeComponent.extendComponent({
}, },
); );
} }
Popup.close(); Popup.back();
}, },
}, },
]; ];
@ -782,17 +799,12 @@ BlazeComponent.extendComponent({
return false; return false;
} }
const spinnerViewPosition = this.spinner.offsetTop - this.container.offsetTop + this.spinner.clientHeight;
const parentViewHeight = this.container.clientHeight; const parentViewHeight = this.container.clientHeight;
const bottomViewPosition = this.container.scrollTop + parentViewHeight; const bottomViewPosition = this.container.scrollTop + parentViewHeight;
let spinnerOffsetTop = this.spinner.offsetTop; return bottomViewPosition > spinnerViewPosition;
const addCard = $(this.container).find("a.open-minicard-composer").first()[0];
if (addCard !== undefined) {
spinnerOffsetTop -= addCard.clientHeight;
}
return bottomViewPosition > spinnerOffsetTop;
} }
getSkSpinnerName() { getSkSpinnerName() {

View file

@ -18,9 +18,9 @@ template(name="listHeader")
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.count}} span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.count}}
|/#{wipLimit.value}) |/#{wipLimit.value})
if showCardsCountForList cards.count if showCardsCountForList cards.count
|&nbsp; span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.count}}
span(class="cardCount") {{cardsCount}} {{_ 'cards-count'}}
if isMiniScreen if isMiniScreen
if currentList if currentList
if isWatching if isWatching
@ -28,7 +28,7 @@ template(name="listHeader")
div.list-header-menu div.list-header-menu
unless currentUser.isCommentOnly unless currentUser.isCommentOnly
if canSeeAddCard if canSeeAddCard
a.js-add-card.fa.fa-plus.list-header-plus-icon(title="{{_ 'add-card-to-top-of-list'}}") a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
else else
a.list-header-menu-icon.fa.fa-angle-right.js-select-list a.list-header-menu-icon.fa.fa-angle-right.js-select-list
@ -39,12 +39,12 @@ template(name="listHeader")
div.list-header-menu div.list-header-menu
unless currentUser.isCommentOnly unless currentUser.isCommentOnly
//if isBoardAdmin //if isBoardAdmin
// a.fa.js-list-star.list-header-plus-icon(class="fa-star{{#unless starred}}-o{{/unless}}") // a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}")
if canSeeAddCard if canSeeAddCard
a.js-add-card.fa.fa-plus.list-header-plus-icon(title="{{_ 'add-card-to-top-of-list'}}") a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
if currentUser.isBoardAdmin if currentUser.isBoardAdmin
if showDesktopDragHandles if isShowDesktopDragHandles
a.list-header-handle.handle.fa.fa-arrows.js-list-handle a.list-header-handle.handle.fa.fa-arrows.js-list-handle
template(name="editListTitleForm") template(name="editListTitleForm")
@ -55,6 +55,13 @@ template(name="editListTitleForm")
a.fa.fa-times-thin.js-close-inlined-form a.fa.fa-times-thin.js-close-inlined-form
template(name="listActionPopup") template(name="listActionPopup")
ul.pop-over-list
li
a.js-add-card.list-header-plus-bottom
i.fa.fa-plus
i.fa.fa-arrow-down
| {{_ 'add-card-to-bottom-of-list'}}
hr
ul.pop-over-list ul.pop-over-list
li li
a.js-toggle-watch-list a.js-toggle-watch-list

View file

@ -85,6 +85,14 @@ BlazeComponent.extendComponent({
return limit >= 0 && count >= limit; return limit >= 0 && count >= limit;
}, },
cardsCountForListIsOne(count) {
if (count === 1) {
return TAPi18n.__('cards-count-one');
} else {
return TAPi18n.__('cards-count');
}
},
events() { events() {
return [ return [
{ {
@ -93,7 +101,7 @@ BlazeComponent.extendComponent({
this.starred(!this.starred()); this.starred(!this.starred());
}, },
'click .js-open-list-menu': Popup.open('listAction'), 'click .js-open-list-menu': Popup.open('listAction'),
'click .js-add-card'(event) { 'click .js-add-card.list-header-plus-top'(event) {
const listDom = $(event.target).parents( const listDom = $(event.target).parents(
`#js-list-${this.currentData()._id}`, `#js-list-${this.currentData()._id}`,
)[0]; )[0];
@ -114,18 +122,7 @@ BlazeComponent.extendComponent({
Template.listHeader.helpers({ Template.listHeader.helpers({
isBoardAdmin() { isBoardAdmin() {
return Meteor.user().isBoardAdmin(); return Meteor.user().isBoardAdmin();
}, }
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
}); });
Template.listActionPopup.helpers({ Template.listActionPopup.helpers({
@ -144,23 +141,31 @@ Template.listActionPopup.helpers({
Template.listActionPopup.events({ Template.listActionPopup.events({
'click .js-list-subscribe'() {}, 'click .js-list-subscribe'() {},
'click .js-add-card.list-header-plus-bottom'(event) {
const listDom = $(`#js-list-${this._id}`)[0];
const listComponent = BlazeComponent.getComponentForElement(listDom);
listComponent.openForm({
position: 'bottom',
});
Popup.back();
},
'click .js-set-color-list': Popup.open('setListColor'), 'click .js-set-color-list': Popup.open('setListColor'),
'click .js-select-cards'() { 'click .js-select-cards'() {
const cardIds = this.allCards().map(card => card._id); const cardIds = this.allCards().map(card => card._id);
MultiSelection.add(cardIds); MultiSelection.add(cardIds);
Popup.close(); Popup.back();
}, },
'click .js-toggle-watch-list'() { 'click .js-toggle-watch-list'() {
const currentList = this; const currentList = this;
const level = currentList.findWatcher(Meteor.userId()) ? null : 'watching'; const level = currentList.findWatcher(Meteor.userId()) ? null : 'watching';
Meteor.call('watch', 'list', currentList._id, level, (err, ret) => { Meteor.call('watch', 'list', currentList._id, level, (err, ret) => {
if (!err && ret) Popup.close(); if (!err && ret) Popup.back();
}); });
}, },
'click .js-close-list'(event) { 'click .js-close-list'(event) {
event.preventDefault(); event.preventDefault();
this.archive(); this.archive();
Popup.close(); Popup.back();
}, },
'click .js-set-wip-limit': Popup.open('setWipLimit'), 'click .js-set-wip-limit': Popup.open('setWipLimit'),
'click .js-more': Popup.open('listMore'), 'click .js-more': Popup.open('listMore'),
@ -236,7 +241,7 @@ BlazeComponent.extendComponent({
Template.listMorePopup.events({ Template.listMorePopup.events({
'click .js-delete': Popup.afterConfirm('listDelete', function() { 'click .js-delete': Popup.afterConfirm('listDelete', function() {
Popup.close(); Popup.back();
// TODO how can we avoid the fetch call? // TODO how can we avoid the fetch call?
const allCards = this.allCards().fetch(); const allCards = this.allCards().fetch();
const allCardIds = _.pluck(allCards, '_id'); const allCardIds = _.pluck(allCards, '_id');
@ -302,11 +307,11 @@ BlazeComponent.extendComponent({
}, },
'click .js-submit'() { 'click .js-submit'() {
this.currentList.setColor(this.currentColor.get()); this.currentList.setColor(this.currentColor.get());
Popup.close(); Popup.back();
}, },
'click .js-remove-color'() { 'click .js-remove-color'() {
this.currentList.setColor(null); this.currentList.setColor(null);
Popup.close(); Popup.back();
}, },
}, },
]; ];

View file

@ -38,12 +38,12 @@ BlazeComponent.extendComponent({
{ {
'click .js-due-cards-view-me'() { 'click .js-due-cards-view-me'() {
Utils.setDueCardsView('me'); Utils.setDueCardsView('me');
Popup.close(); Popup.back();
}, },
'click .js-due-cards-view-all'() { 'click .js-due-cards-view-all'() {
Utils.setDueCardsView('all'); Utils.setDueCardsView('all');
Popup.close(); Popup.back();
}, },
}, },
]; ];

View file

@ -1,4 +1,6 @@
template(name="editor") template(name="editor")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.editor( textarea.editor(
dir="auto" dir="auto"
class="{{class}}" class="{{class}}"

View file

@ -4,281 +4,299 @@ const specialHandles = [
]; ];
const specialHandleNames = specialHandles.map(m => m.username); const specialHandleNames = specialHandles.map(m => m.username);
Template.editor.onRendered(() => {
const textareaSelector = 'textarea';
const mentions = [
// User mentions
{
match: /\B@([\w.]*)$/,
search(term, callback) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
callback(
_.union(
currentBoard
.activeMembers()
.map(member => {
const username = Users.findOne(member.userId).username;
return username.includes(term) ? username : null;
})
.filter(Boolean), [...specialHandleNames])
);
},
template(value) {
return value;
},
replace(username) {
return `@${username} `;
},
index: 1,
},
];
const enableTextarea = function() {
const $textarea = this.$(textareaSelector);
autosize($textarea);
$textarea.escapeableTextComplete(mentions);
};
if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false) {
const isSmall = Utils.isMiniScreen();
const toolbar = isSmall
? [
['view', ['fullscreen']],
['table', ['table']],
['font', ['bold', 'underline']],
//['fontsize', ['fontsize']],
['color', ['color']],
]
: [
['style', ['style']],
['font', ['bold', 'underline', 'clear']],
['fontsize', ['fontsize']],
['fontname', ['fontname']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
//['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
['insert', ['link']], //, 'picture']], // modal popup has issue somehow :(
['view', ['fullscreen', 'codeview', 'help']],
];
const cleanPastedHTML = function(input) {
const badTags = [
'style',
'script',
'applet',
'embed',
'noframes',
'noscript',
'meta',
'link',
'button',
'form',
].join('|');
const badPatterns = new RegExp(
`(?:${[
`<(${badTags})s*[^>][\\s\\S]*?<\\/\\1>`,
`<(${badTags})[^>]*?\\/>`,
].join('|')})`,
'gi',
);
let output = input;
// remove bad Tags
output = output.replace(badPatterns, '');
// remove attributes ' style="..."'
const badAttributes = new RegExp(
`(?:${[
'on\\S+=([\'"]?).*?\\1',
'href=([\'"]?)javascript:.*?\\2',
'style=([\'"]?).*?\\3',
'target=\\S+',
].join('|')})`,
'gi',
);
output = output.replace(badAttributes, '');
output = output.replace(/(<a )/gi, '$1target=_ '); // always to new target
return output;
};
const editor = '.editor';
const selectors = [
`.js-new-description-form ${editor}`,
`.js-new-comment-form ${editor}`,
`.js-edit-comment ${editor}`,
].join(','); // only new comment and edit comment
const inputs = $(selectors);
if (inputs.length === 0) {
// only enable richereditor to new comment or edit comment no others
enableTextarea();
} else {
const placeholder = inputs.attr('placeholder') || '';
const mSummernotes = [];
const getSummernote = function(input) {
const idx = inputs.index(input);
if (idx > -1) {
return mSummernotes[idx];
}
return undefined;
};
inputs.each(function(idx, input) {
mSummernotes[idx] = $(input).summernote({
placeholder,
callbacks: {
onInit(object) {
const originalInput = this;
$(originalInput).on('submitted', function() {
// when comment is submitted, the original textarea will be set to '', so shall we
if (!this.value) {
const sn = getSummernote(this);
sn && sn.summernote('code', '');
}
});
const jEditor = object && object.editable;
const toolbar = object && object.toolbar;
if (jEditor !== undefined) {
jEditor.escapeableTextComplete(mentions);
}
if (toolbar !== undefined) {
const fBtn = toolbar.find('.btn-fullscreen');
fBtn.on('click', function() {
const $this = $(this),
isActive = $this.hasClass('active');
$('.minicards,#header-quick-access').toggle(!isActive); // mini card is still showing when editor is in fullscreen mode, we hide here manually
});
}
},
onImageUpload(files) { BlazeComponent.extendComponent({
const $summernote = getSummernote(this); onRendered() {
if (files && files.length > 0) { const textareaSelector = 'textarea';
const image = files[0]; const mentions = [
const currentCard = Cards.findOne(Session.get('currentCard')); // User mentions
const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL; {
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO; match: /\B@([\w.]*)$/,
const insertImage = src => { search(term, callback) {
// process all image upload types to the description/comment window const currentBoard = Boards.findOne(Session.get('currentBoard'));
const img = document.createElement('img'); callback(
img.src = src; _.union(
img.setAttribute('width', '100%'); currentBoard
$summernote.summernote('insertNode', img); .activeMembers()
}; .map(member => {
const processData = function(fileObj) { const user = Users.findOne(member.userId);
Utils.processUploadedAttachment( const username = user.username;
currentCard, const fullName = user.profile && user.profile !== undefined ? user.profile.fullname : "";
fileObj, return username.includes(term) || fullName.includes(term) ? fullName + "(" + username + ")" : null;
attachment => { })
if ( .filter(Boolean), [...specialHandleNames])
attachment && );
attachment._id && },
attachment.isImage() template(value) {
) { return value;
attachment.one('uploaded', function() { },
const maxTry = 3; replace(username) {
const checkItvl = 500; return `@${username} `;
let retry = 0; },
const checkUrl = function() { index: 1,
// even though uploaded event fired, attachment.url() is still null somehow //TODO },
const url = attachment.url(); ];
if (url) { const enableTextarea = function() {
insertImage( const $textarea = this.$(textareaSelector);
`${location.protocol}//${location.host}${url}`, autosize($textarea);
); $textarea.escapeableTextComplete(mentions);
} else { };
retry++; if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false) {
if (retry < maxTry) { const isSmall = Utils.isMiniScreen();
setTimeout(checkUrl, checkItvl); const toolbar = isSmall
? [
['view', ['fullscreen']],
['table', ['table']],
['font', ['bold', 'underline']],
//['fontsize', ['fontsize']],
['color', ['color']],
]
: [
['style', ['style']],
['font', ['bold', 'underline', 'clear']],
['fontsize', ['fontsize']],
['fontname', ['fontname']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
//['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
['insert', ['link']], //, 'picture']], // modal popup has issue somehow :(
['view', ['fullscreen', 'codeview', 'help']],
];
const cleanPastedHTML = function(input) {
const badTags = [
'style',
'script',
'applet',
'embed',
'noframes',
'noscript',
'meta',
'link',
'button',
'form',
].join('|');
const badPatterns = new RegExp(
`(?:${[
`<(${badTags})s*[^>][\\s\\S]*?<\\/\\1>`,
`<(${badTags})[^>]*?\\/>`,
].join('|')})`,
'gi',
);
let output = input;
// remove bad Tags
output = output.replace(badPatterns, '');
// remove attributes ' style="..."'
const badAttributes = new RegExp(
`(?:${[
'on\\S+=([\'"]?).*?\\1',
'href=([\'"]?)javascript:.*?\\2',
'style=([\'"]?).*?\\3',
'target=\\S+',
].join('|')})`,
'gi',
);
output = output.replace(badAttributes, '');
output = output.replace(/(<a )/gi, '$1target=_ '); // always to new target
return output;
};
const editor = '.editor';
const selectors = [
`.js-new-description-form ${editor}`,
`.js-new-comment-form ${editor}`,
`.js-edit-comment ${editor}`,
].join(','); // only new comment and edit comment
const inputs = $(selectors);
if (inputs.length === 0) {
// only enable richereditor to new comment or edit comment no others
enableTextarea();
} else {
const placeholder = inputs.attr('placeholder') || '';
const mSummernotes = [];
const getSummernote = function(input) {
const idx = inputs.index(input);
if (idx > -1) {
return mSummernotes[idx];
}
return undefined;
};
inputs.each(function(idx, input) {
mSummernotes[idx] = $(input).summernote({
placeholder,
callbacks: {
onInit(object) {
const originalInput = this;
$(originalInput).on('submitted', function() {
// when comment is submitted, the original textarea will be set to '', so shall we
if (!this.value) {
const sn = getSummernote(this);
sn && sn.summernote('code', '');
}
});
const jEditor = object && object.editable;
const toolbar = object && object.toolbar;
if (jEditor !== undefined) {
jEditor.escapeableTextComplete(mentions);
}
if (toolbar !== undefined) {
const fBtn = toolbar.find('.btn-fullscreen');
fBtn.on('click', function() {
const $this = $(this),
isActive = $this.hasClass('active');
$('.minicards,#header-quick-access').toggle(!isActive); // mini card is still showing when editor is in fullscreen mode, we hide here manually
});
}
},
onImageUpload(files) {
const $summernote = getSummernote(this);
if (files && files.length > 0) {
const image = files[0];
const currentCard = Utils.getCurrentCard();
const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
const insertImage = src => {
// process all image upload types to the description/comment window
const img = document.createElement('img');
img.src = src;
img.setAttribute('width', '100%');
$summernote.summernote('insertNode', img);
};
const processData = function(fileObj) {
Utils.processUploadedAttachment(
currentCard,
fileObj,
attachment => {
if (
attachment &&
attachment._id &&
attachment.isImage()
) {
attachment.one('uploaded', function() {
const maxTry = 3;
const checkItvl = 500;
let retry = 0;
const checkUrl = function() {
// even though uploaded event fired, attachment.url() is still null somehow //TODO
const url = attachment.url();
if (url) {
insertImage(
`${location.protocol}//${location.host}${url}`,
);
} else {
retry++;
if (retry < maxTry) {
setTimeout(checkUrl, checkItvl);
}
} }
};
checkUrl();
});
}
},
);
};
if (MAX_IMAGE_PIXEL) {
const reader = new FileReader();
reader.onload = function(e) {
const dataurl = e && e.target && e.target.result;
if (dataurl !== undefined) {
// need to shrink image
Utils.shrinkImage({
dataurl,
maxSize: MAX_IMAGE_PIXEL,
ratio: COMPRESS_RATIO,
toBlob: true,
callback(blob) {
if (blob !== false) {
blob.name = image.name;
processData(blob);
} }
}; },
checkUrl();
}); });
} }
}, };
); reader.readAsDataURL(image);
}; } else {
if (MAX_IMAGE_PIXEL) { processData(image);
const reader = new FileReader(); }
reader.onload = function(e) {
const dataurl = e && e.target && e.target.result;
if (dataurl !== undefined) {
// need to shrink image
Utils.shrinkImage({
dataurl,
maxSize: MAX_IMAGE_PIXEL,
ratio: COMPRESS_RATIO,
toBlob: true,
callback(blob) {
if (blob !== false) {
blob.name = image.name;
processData(blob);
}
},
});
}
};
reader.readAsDataURL(image);
} else {
processData(image);
} }
} },
}, onPaste(e) {
onPaste(e) { var clipboardData = e.clipboardData;
var clipboardData = e.clipboardData; var pastedData = clipboardData.getData('Text');
var pastedData = clipboardData.getData('Text');
//if pasted data is an image, exit //if pasted data is an image, exit
if (!pastedData.length) { if (!pastedData.length) {
e.preventDefault(); e.preventDefault();
return; return;
} }
// clear up unwanted tag info when user pasted in text // clear up unwanted tag info when user pasted in text
const thisNote = this; const thisNote = this;
const updatePastedText = function(object) { const updatePastedText = function(object) {
const someNote = getSummernote(object); const someNote = getSummernote(object);
// Fix Pasting text into a card is adding a line before and after // Fix Pasting text into a card is adding a line before and after
// (and multiplies by pasting more) by changing paste "p" to "br". // (and multiplies by pasting more) by changing paste "p" to "br".
// Fixes https://github.com/wekan/wekan/2890 . // Fixes https://github.com/wekan/wekan/2890 .
// == Fix Start == // == Fix Start ==
someNote.execCommand('defaultParagraphSeparator', false, 'br'); someNote.execCommand('defaultParagraphSeparator', false, 'br');
// == Fix End == // == Fix End ==
const original = someNote.summernote('code'); const original = someNote.summernote('code');
const cleaned = cleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML. const cleaned = cleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML.
someNote.summernote('code', ''); //clear original someNote.summernote('code', ''); //clear original
someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code. someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code.
}; };
setTimeout(function() { setTimeout(function() {
//this kinda sucks, but if you don't do a setTimeout, //this kinda sucks, but if you don't do a setTimeout,
//the function is called before the text is really pasted. //the function is called before the text is really pasted.
updatePastedText(thisNote); updatePastedText(thisNote);
}, 10); }, 10);
},
}, },
}, dialogsInBody: true,
dialogsInBody: true, spellCheck: true,
spellCheck: true, disableGrammar: false,
disableGrammar: false, disableDragAndDrop: false,
disableDragAndDrop: false, toolbar,
toolbar, popover: {
popover: { image: [
image: [ ['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']],
['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']], ['float', ['floatLeft', 'floatRight', 'floatNone']],
['float', ['floatLeft', 'floatRight', 'floatNone']], ['remove', ['removeMedia']],
['remove', ['removeMedia']], ],
], link: [['link', ['linkDialogShow', 'unlink']]],
link: [['link', ['linkDialogShow', 'unlink']]], table: [
table: [ ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']],
['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']], ['delete', ['deleteRow', 'deleteCol', 'deleteTable']],
['delete', ['deleteRow', 'deleteCol', 'deleteTable']], ],
], air: [
air: [ ['color', ['color']],
['color', ['color']], ['font', ['bold', 'underline', 'clear']],
['font', ['bold', 'underline', 'clear']], ],
], },
}, height: 200,
height: 200, });
}); });
}); }
} else {
enableTextarea();
} }
} else { },
enableTextarea(); events() {
return [
{
'click a.fa.fa-copy'(event) {
const $editor = this.$('textarea.editor');
const promise = Utils.copyTextToClipboard($editor[0].value);
const $tooltip = this.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
}
]
} }
}); }).register('editor');
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';

View file

@ -0,0 +1,7 @@
.new-comment,
.inlined-form
a.fa.fa-copy
float: right
position: relative
top: 20px
right: 6px

View file

@ -16,12 +16,14 @@ template(name="header")
each currentBoard.lists each currentBoard.lists
li(class="{{#if $.Session.equals 'currentList' _id}}current{{/if}}") li(class="{{#if $.Session.equals 'currentList' _id}}current{{/if}}")
a.js-select-list a.js-select-list
= title +viewer
= title
else else
each currentUser.starredBoards each currentUser.starredBoards
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}") li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
a(href="{{pathFor 'board' id=_id slug=slug}}") a(href="{{pathFor 'board' id=_id slug=slug}}")
= title +viewer
= title
#header-new-board-icon #header-new-board-icon
else else
//- //-
@ -36,7 +38,8 @@ template(name="header")
unless currentSetting.customTopLeftCornerLogoLinkUrl unless currentSetting.customTopLeftCornerLogoLinkUrl
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}") img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
unless currentSetting.customTopLeftCornerLogoImageUrl unless currentSetting.customTopLeftCornerLogoImageUrl
img(src="{{pathFor '/logo-header.png'}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}") div#headerIsSettingDatabaseCallDone
img(src="{{pathFor '/logo-header.png'}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
span.allBoards span.allBoards
a(href="{{pathFor 'home'}}") a(href="{{pathFor 'home'}}")
span.fa.fa-home span.fa.fa-home
@ -49,7 +52,8 @@ template(name="header")
each currentUser.starredBoards each currentUser.starredBoards
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}") li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
a(href="{{pathFor 'board' id=_id slug=slug}}") a(href="{{pathFor 'board' id=_id slug=slug}}")
= title +viewer
= title
else else
li.current.empty {{_ 'quick-access-description'}} li.current.empty {{_ 'quick-access-description'}}
@ -90,3 +94,5 @@ template(name="offlineWarning")
p p
i.fa.fa-warning i.fa.fa-warning
| {{_ 'app-is-offline'}} | {{_ 'app-is-offline'}}
a.app-try-reconnect {{_ 'app-try-reconnect'}}

View file

@ -1,7 +1,23 @@
Meteor.subscribe('user-admin'); Meteor.subscribe('user-admin');
Meteor.subscribe('boards'); Meteor.subscribe('boards');
Meteor.subscribe('setting'); Meteor.subscribe('setting');
Template.header.onCreated(function(){
const templateInstance = this;
templateInstance.currentSetting = new ReactiveVar();
templateInstance.isLoading = new ReactiveVar(false);
Meteor.subscribe('setting', {
onReady() {
templateInstance.currentSetting.set(Settings.findOne());
let currSetting = templateInstance.currentSetting.curValue;
if(currSetting && currSetting !== undefined && currSetting.customLoginLogoImageUrl !== undefined && document.getElementById("headerIsSettingDatabaseCallDone") != null)
document.getElementById("headerIsSettingDatabaseCallDone").style.display = 'none';
else if(document.getElementById("headerIsSettingDatabaseCallDone") != null)
document.getElementById("headerIsSettingDatabaseCallDone").style.display = 'block';
return this.stop();
},
});
});
Template.header.helpers({ Template.header.helpers({
wrappedHeader() { wrappedHeader() {
return !Session.get('currentBoard'); return !Session.get('currentBoard');
@ -41,3 +57,10 @@ Template.header.events({
Session.set('currentCard', null); Session.set('currentCard', null);
}, },
}); });
Template.offlineWarning.events({
'click a.app-try-reconnect'(event) {
event.preventDefault();
Meteor.reconnect();
},
});

View file

@ -135,6 +135,14 @@
padding: 12px 10px padding: 12px 10px
margin: -10px 0px margin: -10px 0px
.viewer
display: inline
white-space: nowrap
p
display: inline
white-space: nowrap
&.current &.current
color: darken(white, 5%) color: darken(white, 5%)
@ -242,3 +250,6 @@
p p
margin: 7px margin: 7px
padding: 0 padding: 0
#headerIsSettingDatabaseCallDone
display: none;

View file

@ -34,8 +34,9 @@ template(name="userFormsLayout")
img(src="{{currentSetting.customLoginLogoImageUrl}}" width="300" height="auto") img(src="{{currentSetting.customLoginLogoImageUrl}}" width="300" height="auto")
br br
unless currentSetting.customLoginLogoImageUrl unless currentSetting.customLoginLogoImageUrl
img(src="{{pathFor '/wekan-logo.svg'}}" alt="" width="300" height="auto") div#isSettingDatabaseCallDone
br img(src="{{pathFor '/wekan-logo.svg'}}" alt="" width="300" height="auto")
br
if currentSetting.textBelowCustomLoginLogo if currentSetting.textBelowCustomLoginLogo
+viewer +viewer
| {{currentSetting.textBelowCustomLoginLogo}} | {{currentSetting.textBelowCustomLoginLogo}}
@ -47,6 +48,13 @@ template(name="userFormsLayout")
+Template.dynamic(template=content) +Template.dynamic(template=content)
if currentSetting.displayAuthenticationMethod if currentSetting.displayAuthenticationMethod
+connectionMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod) +connectionMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)
if isLegalNoticeLinkExist
div#legalNoticeDiv
span#legalNoticeSpan {{_ 'acceptance_of_our_legalNotice'}}
a#legalNoticeAtLink.at-link(href="{{currentSetting.legalNotice}}", target="_blank", rel="noopener noreferrer")
| {{_ 'legalNotice'}}
if getLegalNoticeWithWritTraduction
div
div.at-form-lang div.at-form-lang
select.select-lang.js-userform-set-language select.select-lang.js-userform-set-language
each languages each languages

View file

@ -6,6 +6,9 @@ const i18nTagToT9n = i18nTag => {
return i18nTag; return i18nTag;
}; };
let alreadyCheck = 1;
let isCheckDone = false;
const validator = { const validator = {
set(obj, prop, value) { set(obj, prop, value) {
if (prop === 'state' && value !== 'signIn') { if (prop === 'state' && value !== 'signIn') {
@ -20,6 +23,8 @@ const validator = {
}, },
}; };
// let isSettingDatabaseFctCallDone = false;
Template.userFormsLayout.onCreated(function() { Template.userFormsLayout.onCreated(function() {
const templateInstance = this; const templateInstance = this;
templateInstance.currentSetting = new ReactiveVar(); templateInstance.currentSetting = new ReactiveVar();
@ -28,6 +33,18 @@ Template.userFormsLayout.onCreated(function() {
Meteor.subscribe('setting', { Meteor.subscribe('setting', {
onReady() { onReady() {
templateInstance.currentSetting.set(Settings.findOne()); templateInstance.currentSetting.set(Settings.findOne());
let currSetting = templateInstance.currentSetting.curValue;
let oidcBtnElt = $("#at-oidc");
if(currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined){
let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
oidcBtnElt.html(htmlvalue);
}
// isSettingDatabaseFctCallDone = true;
if(currSetting && currSetting !== undefined && currSetting.customLoginLogoImageUrl !== undefined)
document.getElementById("isSettingDatabaseCallDone").style.display = 'none';
else
document.getElementById("isSettingDatabaseCallDone").style.display = 'block';
return this.stop(); return this.stop();
}, },
}); });
@ -56,6 +73,31 @@ Template.userFormsLayout.helpers({
return Template.instance().currentSetting.get(); return Template.instance().currentSetting.get();
}, },
// isSettingDatabaseCallDone(){
// return isSettingDatabaseFctCallDone;
// },
isLegalNoticeLinkExist(){
const currSet = Template.instance().currentSetting.get();
if(currSet && currSet !== undefined && currSet != null){
return currSet.legalNotice !== undefined && currSet.legalNotice.trim() != "";
}
else
return false;
},
getLegalNoticeWithWritTraduction(){
let spanLegalNoticeElt = $("#legalNoticeSpan");
if(spanLegalNoticeElt != null && spanLegalNoticeElt != undefined){
spanLegalNoticeElt.html(TAPi18n.__('acceptance_of_our_legalNotice', {}, T9n.getLanguage() || 'en'));
}
let atLinkLegalNoticeElt = $("#legalNoticeAtLink");
if(atLinkLegalNoticeElt != null && atLinkLegalNoticeElt != undefined){
atLinkLegalNoticeElt.html(TAPi18n.__('legalNotice', {}, T9n.getLanguage() || 'en'));
}
return true;
},
isLoading() { isLoading() {
return Template.instance().isLoading.get(); return Template.instance().isLoading.get();
}, },
@ -79,6 +121,10 @@ Template.userFormsLayout.helpers({
name = 'مَصرى'; name = 'مَصرى';
} else if (lang.name === 'de-CH') { } else if (lang.name === 'de-CH') {
name = 'Deutsch (Schweiz)'; name = 'Deutsch (Schweiz)';
} else if (lang.name === 'de-AT') {
name = 'Deutsch (Österreich)';
} else if (lang.name === 'en-DE') {
name = 'English (Germany)';
} else if (lang.name === 'fa-IR') { } else if (lang.name === 'fa-IR') {
// fa-IR = Persian (Iran) // fa-IR = Persian (Iran)
name = 'فارسی/پارسی (ایران‎)'; name = 'فارسی/پارسی (ایران‎)';
@ -86,14 +132,28 @@ Template.userFormsLayout.helpers({
name = 'Français (Belgique)'; name = 'Français (Belgique)';
} else if (lang.name === 'fr-CA') { } else if (lang.name === 'fr-CA') {
name = 'Français (Canada)'; name = 'Français (Canada)';
} else if (lang.name === 'fr-CH') {
name = 'Français (Schweiz)';
} else if (lang.name === 'gu-IN') {
// gu-IN = Gurajati (India)
name = 'ગુજરાતી';
} else if (lang.name === 'hi-IN') {
// hi-IN = Hindi (India)
name = 'हिंदी (भारत)';
} else if (lang.name === 'ig') { } else if (lang.name === 'ig') {
name = 'Igbo'; name = 'Igbo';
} else if (lang.name === 'lv') { } else if (lang.name === 'lv') {
name = 'Latviešu'; name = 'Latviešu';
} else if (lang.name === 'latviešu valoda') { } else if (lang.name === 'latviešu valoda') {
name = 'Latviešu'; name = 'Latviešu';
} else if (lang.name === 'ms-MY') {
// ms-MY = Malay (Malaysia)
name = 'بهاس ملايو';
} else if (lang.name === 'en-IT') { } else if (lang.name === 'en-IT') {
name = 'English (Italy)'; name = 'English (Italy)';
} else if (lang.name === 'el-GR') {
// el-GR = Greek (Greece)
name = 'Ελληνικά (Ελλάδα)';
} else if (lang.name === 'Español') { } else if (lang.name === 'Español') {
name = 'español'; name = 'español';
} else if (lang.name === 'es_419') { } else if (lang.name === 'es_419') {
@ -125,6 +185,7 @@ Template.userFormsLayout.helpers({
} else if (lang.name === 'st') { } else if (lang.name === 'st') {
name = 'Sãotomense'; name = 'Sãotomense';
} else if (lang.name === '繁体中文(台湾)') { } else if (lang.name === '繁体中文(台湾)') {
// Traditional Chinese (Taiwan)
name = '繁體中文(台灣)'; name = '繁體中文(台灣)';
} }
return { tag, name }; return { tag, name };
@ -157,6 +218,53 @@ Template.userFormsLayout.events({
templateInstance.isLoading.set(false); templateInstance.isLoading.set(false);
}); });
} }
isCheckDone = false;
},
'click #at-signUp'(event, templateInstance){
isCheckDone = false;
},
'DOMSubtreeModified #at-oidc'(event){
if(alreadyCheck <= 2){
let currSetting = Settings.findOne();
let oidcBtnElt = $("#at-oidc");
if(currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined){
let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
if(alreadyCheck == 1){
alreadyCheck++;
oidcBtnElt.html("");
}
else{
alreadyCheck++;
oidcBtnElt.html(htmlvalue);
}
}
}
else{
alreadyCheck = 1;
}
},
'DOMSubtreeModified .at-form'(event){
if(alreadyCheck <= 2 && !isCheckDone){
if(document.getElementById("at-oidc") != null){
let currSetting = Settings.findOne();
let oidcBtnElt = $("#at-oidc");
if(currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined){
let htmlvalue = "<i class='fa fa-oidc'></i>" + currSetting.oidcBtnText;
if(alreadyCheck == 1){
alreadyCheck++;
oidcBtnElt.html("");
}
else{
alreadyCheck++;
isCheckDone = true;
oidcBtnElt.html(htmlvalue);
}
}
}
}
else{
alreadyCheck = 1;
}
}, },
}); });

View file

@ -433,7 +433,7 @@ a
margin-top: 0px margin-top: 0px
.wrapper .wrapper
height: 100% height: calc(100% - 31px)
margin: 0px margin: 0px
.panel-default .panel-default
@ -542,3 +542,11 @@ a
100% 100%
transform: rotate(360deg) transform: rotate(360deg)
#isSettingDatabaseCallDone
display: none;
.at-link
color: #17683a;
text-decoration: underline;
text-decoration-color: #17683a;

View file

@ -14,8 +14,7 @@ $popupWidth = 300px
margin-top: 5px margin-top: 5px
hr hr
margin: 4px -10px margin: 4px 0px
width: $popupWidth
p, p,
textarea, textarea,
@ -23,7 +22,6 @@ $popupWidth = 300px
input[type="email"], input[type="email"],
input[type="password"], input[type="password"],
input[type="file"] input[type="file"]
margin: 4px 0 12px
width: 100% width: 100%
select select
@ -313,22 +311,13 @@ $popupWidth = 300px
input[type="email"], input[type="email"],
input[type="password"], input[type="password"],
input[type="file"] input[type="file"]
margin: 4px 0 12px
width: 100% width: 100%
box-sizing: border-box box-sizing: border-box
.pop-over-list .pop-over-list
li > a li > a
width: calc(100% - 20px) width: calc(100% - 20px)
padding: 10px 10px
margin: 0px 0px margin: 0px 0px
border-bottom: 1px solid #eee
hr
width: 100%
height: 20px
margin: 0px 0px
color: #eee
for depth in (1..6) for depth in (1..6)
.popup-container-depth-{depth} .popup-container-depth-{depth}

View file

@ -232,7 +232,7 @@ BlazeComponent.extendComponent({
}, },
'click .js-submit'() { 'click .js-submit'() {
this.colorButtonValue.set(this.currentColor.get()); this.colorButtonValue.set(this.currentColor.get());
Popup.close(); Popup.back();
}, },
}, },
]; ];

View file

@ -116,6 +116,6 @@ Template.boardCardTitlePopup.events({
.trim(); .trim();
Popup.getOpenerComponent().setNameFilter(title); Popup.getOpenerComponent().setNameFilter(title);
event.preventDefault(); event.preventDefault();
Popup.close(); Popup.back();
}, },
}); });

View file

@ -21,7 +21,7 @@ template(name='statistics')
table table
tbody tbody
tr tr
th Wekan {{_ 'info'}} th WeKan ® {{_ 'info'}}
td {{statistics.version}} td {{statistics.version}}
tr tr
th {{_ 'Meteor_version'}} th {{_ 'Meteor_version'}}
@ -65,3 +65,49 @@ template(name='statistics')
tr tr
th {{_ 'OS_Cpus'}} th {{_ 'OS_Cpus'}}
td {{statistics.os.cpus.length}} td {{statistics.os.cpus.length}}
unless isSandstorm
tr
th {{_ 'Node_heap_total_heap_size'}}
td {{bytesToSize statistics.nodeHeapStats.totalHeapSize}}
tr
th {{_ 'Node_heap_total_heap_size_executable'}}
td {{bytesToSize statistics.nodeHeapStats.totalHeapSizeExecutable}}
tr
th {{_ 'Node_heap_total_physical_size'}}
td {{bytesToSize statistics.nodeHeapStats.totalPhysicalSize}}
tr
th {{_ 'Node_heap_total_available_size'}}
td {{bytesToSize statistics.nodeHeapStats.totalAvailableSize}}
tr
th {{_ 'Node_heap_used_heap_size'}}
td {{bytesToSize statistics.nodeHeapStats.usedHeapSize}}
tr
th {{_ 'Node_heap_heap_size_limit'}}
td {{bytesToSize statistics.nodeHeapStats.heapSizeLimit}}
tr
th {{_ 'Node_heap_malloced_memory'}}
td {{bytesToSize statistics.nodeHeapStats.mallocedMemory}}
tr
th {{_ 'Node_heap_peak_malloced_memory'}}
td {{bytesToSize statistics.nodeHeapStats.peakMallocedMemory}}
tr
th {{_ 'Node_heap_does_zap_garbage'}}
td {{statistics.nodeHeapStats.doesZapGarbage}}
tr
th {{_ 'Node_heap_number_of_native_contexts'}}
td {{statistics.nodeHeapStats.numberOfNativeContexts}}
tr
th {{_ 'Node_heap_number_of_detached_contexts'}}
td {{statistics.nodeHeapStats.numberOfDetachedContexts}}
tr
th {{_ 'Node_memory_usage_rss'}}
td {{bytesToSize statistics.nodeMemoryUsage.rss}}
tr
th {{_ 'Node_memory_usage_heap_total'}}
td {{bytesToSize statistics.nodeMemoryUsage.heapTotal}}
tr
th {{_ 'Node_memory_usage_heap_used'}}
td {{bytesToSize statistics.nodeMemoryUsage.heapUsed}}
tr
th {{_ 'Node_memory_usage_external'}}
td {{bytesToSize statistics.nodeMemoryUsage.external}}

View file

@ -40,6 +40,11 @@ template(name="people")
| {{_ 'search'}} | {{_ 'search'}}
.ext-box-right .ext-box-right
span {{#unless isMiniScreen}}{{_ 'people-number'}}{{/unless}} #{peopleNumber} span {{#unless isMiniScreen}}{{_ 'people-number'}}{{/unless}} #{peopleNumber}
.divAddOrRemoveTeam#divAddOrRemoveTeam
button#addOrRemoveTeam
i.fa.fa-edit
| {{_ 'add'}} / {{_ 'delete'}} {{_ 'teams'}}
.content-body .content-body
.side-menu .side-menu
ul ul
@ -97,9 +102,13 @@ template(name="teamGeneral")
+teamRow(teamId=team._id) +teamRow(teamId=team._id)
template(name="peopleGeneral") template(name="peopleGeneral")
#divAddOrRemoveTeamContainer
+modifyTeamsUsers
table table
tbody tbody
tr tr
th
+selectAllUser
th {{_ 'username'}} th {{_ 'username'}}
th {{_ 'fullname'}} th {{_ 'fullname'}}
th {{_ 'initials'}} th {{_ 'initials'}}
@ -117,6 +126,10 @@ template(name="peopleGeneral")
each user in peopleList each user in peopleList
+peopleRow(userId=user._id) +peopleRow(userId=user._id)
template(name="selectAllUser")
| {{_ 'dueCardsViewChange-choice-all'}}
input.allUserChkBox(type="checkbox", id="chkSelectAll")
template(name="newOrgRow") template(name="newOrgRow")
a.new-org a.new-org
i.fa.fa-plus-square i.fa.fa-plus-square
@ -202,6 +215,12 @@ template(name="teamRow")
template(name="peopleRow") template(name="peopleRow")
tr tr
if userData.loginDisabled
td
input.selectUserChkBox(type="checkbox", disabled="disabled", id="{{userData._id}}")
else
td
input.selectUserChkBox(type="checkbox", id="{{userData._id}}")
if userData.loginDisabled if userData.loginDisabled
td.username <s>{{ userData.username }}</s> td.username <s>{{ userData.username }}</s>
else else
@ -342,7 +361,7 @@ template(name="editUserPopup")
input.js-profile-fullname(type="text" value=user.profile.fullname required) input.js-profile-fullname(type="text" value=user.profile.fullname required)
label label
| {{_ 'initials'}} | {{_ 'initials'}}
input.js-profile-initials(type="text" value=user.profile.initials required) input.js-profile-initials(type="text" value=user.profile.initials)
label label
| {{_ 'admin'}} | {{_ 'admin'}}
select.select-role.js-profile-isadmin select.select-role.js-profile-isadmin
@ -453,6 +472,24 @@ template(name="newTeamPopup")
div.buttonsContainer div.buttonsContainer
input.primary.wide(type="submit" value="{{_ 'save'}}") input.primary.wide(type="submit" value="{{_ 'save'}}")
template(name="modifyTeamsUsers")
label
| {{_ 'teams'}}
select.js-teamsUser#jsteamsUser
each value in teamsDatas
option(value="{{value._id}}") {{_ value.teamDisplayName}}
hr
label
| {{_ 'r-action'}}
.form-group.flex
input.wekan-form-control#addAction(type="radio" name="action" value="true" checked="checked")
span {{_ 'add'}}
input.wekan-form-control#deleteAction(type="radio" name="action" value="false")
span {{_ 'delete'}}
div.buttonsContainer
input.primary.wide#addTeamBtn(type="submit" value="{{_ 'save'}}")
input.primary.wide#cancelBtn(type="submit" value="{{_ 'cancel'}}")
template(name="newUserPopup") template(name="newUserPopup")
form form
//label.hide.userId(type="text" value=user._id) //label.hide.userId(type="text" value=user._id)
@ -469,7 +506,7 @@ template(name="newUserPopup")
input.js-profile-username(type="text" value="" required) input.js-profile-username(type="text" value="" required)
label label
| {{_ 'initials'}} | {{_ 'initials'}}
input.js-profile-initials(type="text" value="" required) input.js-profile-initials(type="text" value="")
label label
| {{_ 'email'}} | {{_ 'email'}}
span.error.hide.email-taken span.error.hide.email-taken
@ -571,10 +608,14 @@ template(name="settingsUserPopup")
a.impersonate-user a.impersonate-user
i.fa.fa-user i.fa.fa-user
| {{_ 'impersonate-user'}} | {{_ 'impersonate-user'}}
br
hr hr
li li
form form
label.hide.userId(type="text" value=user._id) label.hide.userId(type="text" value=user._id)
label
| {{_ 'delete-user-confirm-popup' }}
br
div.buttonsContainer div.buttonsContainer
input#deleteButton.card-details-red.right.wide(type="button" value="{{_ 'delete'}}") input#deleteButton.card-details-red.right.wide(type="button" value="{{_ 'delete'}}")
// Delete is enabled, but there is still bug of leaving empty user avatars // Delete is enabled, but there is still bug of leaving empty user avatars

View file

@ -2,6 +2,7 @@ const orgsPerPage = 25;
const teamsPerPage = 25; const teamsPerPage = 25;
const usersPerPage = 25; const usersPerPage = 25;
let userOrgsTeamsAction = ""; //poosible actions 'addOrg', 'addTeam', 'removeOrg' or 'removeTeam' when adding or modifying a user let userOrgsTeamsAction = ""; //poosible actions 'addOrg', 'addTeam', 'removeOrg' or 'removeTeam' when adding or modifying a user
let selectedUserChkBoxUserIds = [];
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
mixins() { mixins() {
@ -81,6 +82,9 @@ BlazeComponent.extendComponent({
'click #searchButton'() { 'click #searchButton'() {
this.filterPeople(); this.filterPeople();
}, },
'click #addOrRemoveTeam'(){
document.getElementById("divAddOrRemoveTeamContainer").style.display = 'block';
},
'keydown #searchInput'(event) { 'keydown #searchInput'(event) {
if (event.keyCode === 13 && !event.shiftKey) { if (event.keyCode === 13 && !event.shiftKey) {
this.filterPeople(); this.filterPeople();
@ -140,6 +144,7 @@ BlazeComponent.extendComponent({
}, },
orgList() { orgList() {
const orgs = Org.find(this.findOrgsOptions.get(), { const orgs = Org.find(this.findOrgsOptions.get(), {
sort: { orgDisplayName: 1 },
fields: { _id: true }, fields: { _id: true },
}); });
this.numberOrgs.set(orgs.count(false)); this.numberOrgs.set(orgs.count(false));
@ -147,6 +152,7 @@ BlazeComponent.extendComponent({
}, },
teamList() { teamList() {
const teams = Team.find(this.findTeamsOptions.get(), { const teams = Team.find(this.findTeamsOptions.get(), {
sort: { teamDisplayName: 1 },
fields: { _id: true }, fields: { _id: true },
}); });
this.numberTeams.set(teams.count(false)); this.numberTeams.set(teams.count(false));
@ -154,6 +160,7 @@ BlazeComponent.extendComponent({
}, },
peopleList() { peopleList() {
const users = Users.find(this.findUsersOptions.get(), { const users = Users.find(this.findUsersOptions.get(), {
sort: { username: 1 },
fields: { _id: true }, fields: { _id: true },
}); });
this.numberPeople.set(users.count(false)); this.numberPeople.set(users.count(false));
@ -247,10 +254,10 @@ Template.editUserPopup.helpers({
return Template.instance().authenticationMethods.get(); return Template.instance().authenticationMethods.get();
}, },
orgsDatas() { orgsDatas() {
return Org.find({}, {sort: { createdAt: -1 }}); return Org.find({}, {sort: { orgDisplayName: 1 }});
}, },
teamsDatas() { teamsDatas() {
return Team.find({}, {sort: { createdAt: -1 }}); return Team.find({}, {sort: { teamDisplayName: 1 }});
}, },
isSelected(match) { isSelected(match) {
const userId = Template.instance().data.userId; const userId = Template.instance().data.userId;
@ -320,10 +327,10 @@ Template.newUserPopup.helpers({
return Template.instance().authenticationMethods.get(); return Template.instance().authenticationMethods.get();
}, },
orgsDatas() { orgsDatas() {
return Org.find({}, {sort: { createdAt: -1 }}); return Org.find({}, {sort: { orgDisplayName: 1 }});
}, },
teamsDatas() { teamsDatas() {
return Team.find({}, {sort: { createdAt: -1 }}); return Team.find({}, {sort: { teamDisplayName: 1 }});
}, },
isSelected(match) { isSelected(match) {
const userId = Template.instance().data.userId; const userId = Template.instance().data.userId;
@ -385,11 +392,111 @@ BlazeComponent.extendComponent({
{ {
'click a.edit-user': Popup.open('editUser'), 'click a.edit-user': Popup.open('editUser'),
'click a.more-settings-user': Popup.open('settingsUser'), 'click a.more-settings-user': Popup.open('settingsUser'),
'click .selectUserChkBox': function(ev){
if(ev.currentTarget){
if(ev.currentTarget.checked){
if(!selectedUserChkBoxUserIds.includes(ev.currentTarget.id)){
selectedUserChkBoxUserIds.push(ev.currentTarget.id);
}
}
else{
if(selectedUserChkBoxUserIds.includes(ev.currentTarget.id)){
let index = selectedUserChkBoxUserIds.indexOf(ev.currentTarget.id);
if(index > -1)
selectedUserChkBoxUserIds.splice(index, 1);
}
}
}
if(selectedUserChkBoxUserIds.length > 0)
document.getElementById("divAddOrRemoveTeam").style.display = 'block';
else
document.getElementById("divAddOrRemoveTeam").style.display = 'none';
},
}, },
]; ];
}, },
}).register('peopleRow'); }).register('peopleRow');
BlazeComponent.extendComponent({
onCreated() {},
teamsDatas() {
return Team.find({}, {sort: { teamDisplayName: 1 }});
},
events() {
return [
{
'click #cancelBtn': function(){
let selectedElt = document.getElementById("jsteamsUser");
document.getElementById("divAddOrRemoveTeamContainer").style.display = 'none';
},
'click #addTeamBtn': function(){
let selectedElt;
let selectedEltValue;
let selectedEltValueId;
let userTms = [];
let currentUser;
let currUserTeamIndex;
selectedElt = document.getElementById("jsteamsUser");
selectedEltValue = selectedElt.options[selectedElt.selectedIndex].text;
selectedEltValueId = selectedElt.options[selectedElt.selectedIndex].value;
if(document.getElementById('addAction').checked){
for(let i = 0; i < selectedUserChkBoxUserIds.length; i++){
currentUser = Users.findOne(selectedUserChkBoxUserIds[i]);
userTms = currentUser.teams;
if(userTms == undefined || userTms.length == 0){
userTms = [];
userTms.push({
"teamId": selectedEltValueId,
"teamDisplayName": selectedEltValue,
})
}
else if(userTms.length > 0)
{
currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId});
if(currUserTeamIndex == -1){
userTms.push({
"teamId": selectedEltValueId,
"teamDisplayName": selectedEltValue,
});
}
}
Users.update(selectedUserChkBoxUserIds[i], {
$set:{
teams: userTms
}
});
}
}
else{
for(let i = 0; i < selectedUserChkBoxUserIds.length; i++){
currentUser = Users.findOne(selectedUserChkBoxUserIds[i]);
userTms = currentUser.teams;
if(userTms !== undefined || userTms.length > 0)
{
currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId});
if(currUserTeamIndex != -1){
userTms.splice(currUserTeamIndex, 1);
}
}
Users.update(selectedUserChkBoxUserIds[i], {
$set:{
teams: userTms
}
});
}
}
document.getElementById("divAddOrRemoveTeamContainer").style.display = 'none';
},
},
];
},
}).register('modifyTeamsUsers');
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
events() { events() {
return [ return [
@ -420,6 +527,41 @@ BlazeComponent.extendComponent({
}, },
}).register('newUserRow'); }).register('newUserRow');
BlazeComponent.extendComponent({
events() {
return [
{
'click .allUserChkBox': function(ev){
selectedUserChkBoxUserIds = [];
const checkboxes = document.getElementsByClassName("selectUserChkBox");
if(ev.currentTarget){
if(ev.currentTarget.checked){
for (let i=0; i<checkboxes.length; i++) {
if (!checkboxes[i].disabled) {
selectedUserChkBoxUserIds.push(checkboxes[i].id);
checkboxes[i].checked = true;
}
}
}
else{
for (let i=0; i<checkboxes.length; i++) {
if (!checkboxes[i].disabled) {
checkboxes[i].checked = false;
}
}
}
}
if(selectedUserChkBoxUserIds.length > 0)
document.getElementById("divAddOrRemoveTeam").style.display = 'block';
else
document.getElementById("divAddOrRemoveTeam").style.display = 'none';
},
},
];
},
}).register('selectAllUser');
Template.editOrgPopup.events({ Template.editOrgPopup.events({
submit(event, templateInstance) { submit(event, templateInstance) {
event.preventDefault(); event.preventDefault();
@ -431,8 +573,7 @@ Template.editOrgPopup.events({
const orgDesc = templateInstance.find('.js-orgDesc').value.trim(); const orgDesc = templateInstance.find('.js-orgDesc').value.trim();
const orgShortName = templateInstance.find('.js-orgShortName').value.trim(); const orgShortName = templateInstance.find('.js-orgShortName').value.trim();
const orgWebsite = templateInstance.find('.js-orgWebsite').value.trim(); const orgWebsite = templateInstance.find('.js-orgWebsite').value.trim();
const orgIsActive = const orgIsActive = templateInstance.find('.js-org-isactive').value.trim() == 'true';
templateInstance.find('.js-org-isactive').value.trim() == 'true';
const isChangeOrgDisplayName = orgDisplayName !== org.orgDisplayName; const isChangeOrgDisplayName = orgDisplayName !== org.orgDisplayName;
const isChangeOrgDesc = orgDesc !== org.orgDesc; const isChangeOrgDesc = orgDesc !== org.orgDesc;
@ -458,7 +599,7 @@ Template.editOrgPopup.events({
); );
} }
Popup.close(); Popup.back();
}, },
}); });
@ -502,7 +643,7 @@ Template.editTeamPopup.events({
); );
} }
Popup.close(); Popup.back();
}, },
}); });
@ -617,7 +758,7 @@ Template.editUserPopup.events({
} else { } else {
usernameMessageElement.hide(); usernameMessageElement.hide();
emailMessageElement.hide(); emailMessageElement.hide();
Popup.close(); Popup.back();
} }
}, },
); );
@ -631,7 +772,7 @@ Template.editUserPopup.events({
} }
} else { } else {
usernameMessageElement.hide(); usernameMessageElement.hide();
Popup.close(); Popup.back();
} }
}); });
} else if (isChangeEmail) { } else if (isChangeEmail) {
@ -648,11 +789,11 @@ Template.editUserPopup.events({
} }
} else { } else {
emailMessageElement.hide(); emailMessageElement.hide();
Popup.close(); Popup.back();
} }
}, },
); );
} else Popup.close(); } else Popup.back();
}, },
'click #addUserOrg'(event) { 'click #addUserOrg'(event) {
event.preventDefault(); event.preventDefault();
@ -787,7 +928,7 @@ Template.newOrgPopup.events({
orgWebsite, orgWebsite,
orgIsActive, orgIsActive,
); );
Popup.close(); Popup.back();
}, },
}); });
@ -813,7 +954,7 @@ Template.newTeamPopup.events({
teamWebsite, teamWebsite,
teamIsActive, teamIsActive,
); );
Popup.close(); Popup.back();
}, },
}); });
@ -839,20 +980,24 @@ Template.newUserPopup.events({
let userTeamsIdsList = userTeamsIds.split(","); let userTeamsIdsList = userTeamsIds.split(",");
let userTms = []; let userTms = [];
for(let i = 0; i < userTeamsList.length; i++){ for(let i = 0; i < userTeamsList.length; i++){
userTms.push({ if(!!userTeamsIdsList[i] && !!userTeamsList[i]) {
"teamId": userTeamsIdsList[i], userTms.push({
"teamDisplayName": userTeamsList[i], "teamId": userTeamsIdsList[i],
}) "teamDisplayName": userTeamsList[i],
})
}
} }
let userOrgsList = userOrgs.split(","); let userOrgsList = userOrgs.split(",");
let userOrgsIdsList = userOrgsIds.split(","); let userOrgsIdsList = userOrgsIds.split(",");
let userOrganizations = []; let userOrganizations = [];
for(let i = 0; i < userOrgsList.length; i++){ for(let i = 0; i < userOrgsList.length; i++){
userOrganizations.push({ if(!!userOrgsIdsList[i] && !!userOrgsList[i]) {
"orgId": userOrgsIdsList[i], userOrganizations.push({
"orgDisplayName": userOrgsList[i], "orgId": userOrgsIdsList[i],
}) "orgDisplayName": userOrgsList[i],
})
}
} }
Meteor.call( Meteor.call(
@ -882,11 +1027,11 @@ Template.newUserPopup.events({
} else { } else {
usernameMessageElement.hide(); usernameMessageElement.hide();
emailMessageElement.hide(); emailMessageElement.hide();
Popup.close(); Popup.back();
} }
}, },
); );
Popup.close(); Popup.back();
}, },
'click #addUserOrgNewUser'(event) { 'click #addUserOrgNewUser'(event) {
event.preventDefault(); event.preventDefault();
@ -940,7 +1085,7 @@ Template.settingsOrgPopup.events({
return; return;
} }
Org.remove(this.orgId); Org.remove(this.orgId);
Popup.close(); Popup.back();
} }
}); });
@ -958,7 +1103,7 @@ Template.settingsTeamPopup.events({
return; return;
} }
Team.remove(this.teamId); Team.remove(this.teamId);
Popup.close(); Popup.back();
} }
}); });
@ -975,10 +1120,13 @@ Template.settingsUserPopup.events({
}, },
'click #deleteButton'(event) { 'click #deleteButton'(event) {
event.preventDefault(); event.preventDefault();
Users.remove(this.userId);
/* /*
// Delete is not enabled yet, because it does leave empty user avatars // Delete user is enabled, but you should remove user from all boards
// to boards: boards members, card members and assignees have // before deleting user, because there is possibility of leaving empty user avatars
// empty users. See: // to boards. You can remove non-existing user ids manually from database,
// if that happens.
//. See:
// - wekan/client/components/settings/peopleBody.jade deleteButton // - wekan/client/components/settings/peopleBody.jade deleteButton
// - wekan/client/components/settings/peopleBody.js deleteButton // - wekan/client/components/settings/peopleBody.js deleteButton
// - wekan/client/components/sidebar/sidebar.js Popup.afterConfirm('removeMember' // - wekan/client/components/sidebar/sidebar.js Popup.afterConfirm('removeMember'
@ -986,9 +1134,9 @@ Template.settingsUserPopup.events({
// but that should be used to remove user from all boards similarly // but that should be used to remove user from all boards similarly
// - wekan/models/users.js Delete is not enabled // - wekan/models/users.js Delete is not enabled
// //
//Users.remove(this.userId); //
*/ */
Popup.close(); Popup.back();
}, },
}); });

View file

@ -55,3 +55,32 @@ table
.js-teams,.js-teamsNewUser .js-teams,.js-teamsNewUser
display: none; display: none;
.selectUserChkBox,.allUserChkBox
position: static !important;
visibility: visible !important;
left: 0 !important;
display: block !important;
#divAddOrRemoveTeam
background: green;
display: none;
#addOrRemoveTeam
background: green;
color: white;
#divAddOrRemoveTeamContainer
display: none;
margin: auto;
width: 50%;
border: 3px solid green;
padding: 10px;
#cancelBtn
margin-left: 5% !important;
background: orange;
color: white;
#deleteAction
margin-left: 5% !important;

View file

@ -22,6 +22,10 @@ template(name="setting")
a.js-setting-menu(data-id="account-setting") a.js-setting-menu(data-id="account-setting")
i.fa.fa-users i.fa.fa-users
| {{_ 'accounts'}} | {{_ 'accounts'}}
li
a.js-setting-menu(data-id="tableVisibilityMode-setting")
i.fa.fa-eye
| {{_ 'tableVisibilityMode'}}
li li
a.js-setting-menu(data-id="announcement-setting") a.js-setting-menu(data-id="announcement-setting")
i.fa.fa-bullhorn i.fa.fa-bullhorn
@ -44,6 +48,8 @@ template(name="setting")
+email +email
else if accountSetting.get else if accountSetting.get
+accountSettings +accountSettings
else if tableVisibilityModeSetting.get
+tableVisibilityModeSettings
else if announcementSetting.get else if announcementSetting.get
+announcementSettings +announcementSettings
else if layoutSetting.get else if layoutSetting.get
@ -96,7 +102,7 @@ template(name='email')
// li.smtp-form // li.smtp-form
// .title {{_ 'smtp-username'}} // .title {{_ 'smtp-username'}}
// .form-group // .form-group
// input.wekan-form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}") // input.wekan-form-control#mail-server-u"accounts-allowUserNameChange": "Allow Username Change",sername(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}")
// li.smtp-form // li.smtp-form
// .title {{_ 'smtp-password'}} // .title {{_ 'smtp-password'}}
// .form-group // .form-group
@ -120,6 +126,17 @@ template(name='email')
li li
button.js-send-smtp-test-email.primary {{_ 'send-smtp-test'}} button.js-send-smtp-test-email.primary {{_ 'send-smtp-test'}}
template(name='tableVisibilityModeSettings')
ul#tableVisibilityMode-setting.setting-detail
li.tableVisibilityMode-form
.title {{_ 'tableVisibilityMode-allowPrivateOnly'}}
.form-group.flex
input.wekan-form-control#accounts-allowPrivateOnly(type="radio" name="allowPrivateOnly" value="true" checked="{{#if allowPrivateOnly}}checked{{/if}}")
span {{_ 'yes'}}
input.wekan-form-control#accounts-allowPrivateOnly(type="radio" name="allowPrivateOnly" value="false" checked="{{#unless allowPrivateOnly}}checked{{/unless}}")
span {{_ 'no'}}
button.js-tableVisibilityMode-save.primary {{_ 'save'}}
template(name='accountSettings') template(name='accountSettings')
ul#account-setting.setting-detail ul#account-setting.setting-detail
li li
@ -163,6 +180,18 @@ template(name='announcementSettings')
template(name='layoutSettings') template(name='layoutSettings')
ul#layout-setting.setting-detail ul#layout-setting.setting-detail
li.layout-form
.title {{_ 'oidc-button-text'}}
.form-group
input.wekan-form-control#oidcBtnTextvalue(type="text", placeholder="" value="{{currentSetting.oidcBtnText}}")
li.layout-form
.title {{_ 'can-invite-if-same-mailDomainName'}}
.form-group
input.wekan-form-control#mailDomainNamevalue(type="text", placeholder="" value="{{currentSetting.mailDomainName}}")
li.layout-form
.title {{_ 'custom-legal-notice-link-url'}}
.form-group
input.wekan-form-control#legalNoticevalue(type="text", placeholder="" value="{{currentSetting.legalNotice}}")
li.layout-form li.layout-form
.title {{_ 'display-authentication-method'}} .title {{_ 'display-authentication-method'}}
.form-group.flex .form-group.flex

View file

@ -7,6 +7,7 @@ BlazeComponent.extendComponent({
this.generalSetting = new ReactiveVar(true); this.generalSetting = new ReactiveVar(true);
this.emailSetting = new ReactiveVar(false); this.emailSetting = new ReactiveVar(false);
this.accountSetting = new ReactiveVar(false); this.accountSetting = new ReactiveVar(false);
this.tableVisibilityModeSetting = new ReactiveVar(false);
this.announcementSetting = new ReactiveVar(false); this.announcementSetting = new ReactiveVar(false);
this.layoutSetting = new ReactiveVar(false); this.layoutSetting = new ReactiveVar(false);
this.webhookSetting = new ReactiveVar(false); this.webhookSetting = new ReactiveVar(false);
@ -14,6 +15,7 @@ BlazeComponent.extendComponent({
Meteor.subscribe('setting'); Meteor.subscribe('setting');
Meteor.subscribe('mailServer'); Meteor.subscribe('mailServer');
Meteor.subscribe('accountSettings'); Meteor.subscribe('accountSettings');
Meteor.subscribe('tableVisibilityModeSettings');
Meteor.subscribe('announcements'); Meteor.subscribe('announcements');
Meteor.subscribe('globalwebhooks'); Meteor.subscribe('globalwebhooks');
}, },
@ -88,6 +90,7 @@ BlazeComponent.extendComponent({
this.announcementSetting.set('announcement-setting' === targetID); this.announcementSetting.set('announcement-setting' === targetID);
this.layoutSetting.set('layout-setting' === targetID); this.layoutSetting.set('layout-setting' === targetID);
this.webhookSetting.set('webhook-setting' === targetID); this.webhookSetting.set('webhook-setting' === targetID);
this.tableVisibilityModeSetting.set('tableVisibilityMode-setting' === targetID);
} }
}, },
@ -196,6 +199,22 @@ BlazeComponent.extendComponent({
) )
.val() .val()
.trim(); .trim();
const oidcBtnText = $(
'#oidcBtnTextvalue',
)
.val()
.trim();
const mailDomainName = $(
'#mailDomainNamevalue',
)
.val()
.trim();
const legalNotice = $(
'#legalNoticevalue',
)
.val()
.trim();
const hideLogoChange = $('input[name=hideLogo]:checked').val() === 'true'; const hideLogoChange = $('input[name=hideLogo]:checked').val() === 'true';
const displayAuthenticationMethod = const displayAuthenticationMethod =
$('input[name=displayAuthenticationMethod]:checked').val() === 'true'; $('input[name=displayAuthenticationMethod]:checked').val() === 'true';
@ -218,6 +237,9 @@ BlazeComponent.extendComponent({
defaultAuthenticationMethod, defaultAuthenticationMethod,
automaticLinkedUrlSchemes, automaticLinkedUrlSchemes,
spinnerName, spinnerName,
oidcBtnText,
mailDomainName,
legalNotice,
}, },
}); });
} catch (e) { } catch (e) {
@ -317,6 +339,46 @@ BlazeComponent.extendComponent({
}, },
}).register('accountSettings'); }).register('accountSettings');
BlazeComponent.extendComponent({
saveTableVisibilityChange() {
const allowPrivateOnly =
$('input[name=allowPrivateOnly]:checked').val() === 'true';
TableVisibilityModeSettings.update('tableVisibilityMode-allowPrivateOnly', {
$set: { booleanValue: allowPrivateOnly },
});
},
allowPrivateOnly() {
return TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
},
allHideSystemMessages() {
Meteor.call('setAllUsersHideSystemMessages', (err, ret) => {
if (!err && ret) {
if (ret === true) {
const message = `${TAPi18n.__(
'now-system-messages-of-all-users-are-hidden',
)}`;
alert(message);
}
} else {
const reason = err.reason || '';
const message = `${TAPi18n.__(err.error)}\n${reason}`;
alert(message);
}
});
},
events() {
return [
{
'click button.js-tableVisibilityMode-save': this.saveTableVisibilityChange,
},
{
'click button.js-all-hide-system-messages': this.allHideSystemMessages,
},
];
},
}).register('tableVisibilityModeSettings');
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
onCreated() { onCreated() {
this.loading = new ReactiveVar(false); this.loading = new ReactiveVar(false);

View file

@ -11,7 +11,7 @@
color: #727479 color: #727479
background: #dedede background: #dedede
width 100% width 100%
height 100% height calc(100% - 80px)
position: absolute; position: absolute;
.content-title .content-title
@ -88,6 +88,8 @@
&.is-checked &.is-checked
background #fff background #fff
input[type=radio]
margin: 4px
.option .option
@extends .flex @extends .flex

View file

@ -31,26 +31,28 @@ template(name='homeSidebar')
+activities(mode="board") +activities(mode="board")
template(name="membersWidget") template(name="membersWidget")
.board-widget.board-widget-members if AtLeastOneOrgWasCreated
h3 .board-widget.board-widget-members
i.fa.fa-users h3
| {{_ 'organizations'}} i.fa.fa-users
| {{_ 'organizations'}}
.board-widget-content .board-widget-content
+boardOrgGeneral +boardOrgGeneral
.clearfix .clearfix
br br
hr hr
.board-widget.board-widget-members if AtLeastOneTeamWasCreated
h3 .board-widget.board-widget-members
i.fa.fa-users h3
| {{_ 'teams'}} i.fa.fa-users
| {{_ 'teams'}}
.board-widget-content .board-widget-content
+boardTeamGeneral +boardTeamGeneral
.clearfix .clearfix
br br
hr hr
.board-widget.board-widget-members .board-widget.board-widget-members
h3 h3
i.fa.fa-users i.fa.fa-users
@ -89,11 +91,20 @@ template(name="boardOrgGeneral")
table table
tbody tbody
tr tr
th {{_ 'displayName'}} th
| {{_ 'add-organizations'}}
br
i.addOrganizationsLabel
| {{_ 'to-create-organizations-contact-admin'}}
br
i.addOrganizationsLabel
| {{_ 'add-organizations-label'}}
th th
if currentUser.isBoardAdmin if currentUser.isBoardAdmin
a.member.orgOrTeamMember.add-member.js-manage-board-addOrg(title="{{_ 'add-members'}}") a.member.orgOrTeamMember.add-member.js-manage-board-addOrg(title="{{_ 'add-members'}}")
i.fa.fa-plus i.addTeamFaPlus.fa.fa-plus
.divaddfaplusminus
| {{_ 'add'}}
each org in currentBoard.activeOrgs each org in currentBoard.activeOrgs
+boardOrgRow(orgId=org.orgId) +boardOrgRow(orgId=org.orgId)
@ -101,11 +112,20 @@ template(name="boardTeamGeneral")
table table
tbody tbody
tr tr
th {{_ 'displayName'}} th
| {{_ 'add-teams'}}
br
i.addTeamsLabel
| {{_ 'to-create-teams-contact-admin'}}
br
i.addTeamsLabel
| {{_ 'add-teams-label'}}
th th
if currentUser.isBoardAdmin if currentUser.isBoardAdmin
a.member.orgOrTeamMember.add-member.js-manage-board-addTeam(title="{{_ 'add-members'}}") a.member.orgOrTeamMember.add-member.js-manage-board-addTeam(title="{{_ 'add-members'}}")
i.fa.fa-plus i.addTeamFaPlus.fa.fa-plus
.divaddfaplusminus
| {{_ 'add'}}
each currentBoard.activeTeams each currentBoard.activeTeams
+boardTeamRow(teamId=this.teamId) +boardTeamRow(teamId=this.teamId)
@ -398,7 +418,11 @@ template(name="exportBoard")
li li
a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}") a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}")
i.fa.fa-share-alt i.fa.fa-share-alt
| {{_ 'export-board-csv'}} | {{_ 'export-board-csv'}} ,
li
a(href="{{exportScsvUrl}}", download="{{exportCsvFilename}}")
i.fa.fa-share-alt
| {{_ 'export-board-csv'}} ;
li li
a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}") a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}")
i.fa.fa-share-alt i.fa.fa-share-alt
@ -470,12 +494,12 @@ template(name="removeBoardOrgPopup")
form form
input.hide#hideOrgId(type="text" value=org._id) input.hide#hideOrgId(type="text" value=org._id)
label label
| {{_ 'leave-board'}} ? | {{_ 'remove-organization-from-board'}}
br br
hr hr
div.buttonsContainer div.buttonsContainer
input.primary.wide.leaveBoardBtn#leaveBoardBtn(type="submit" value="{{_ 'leave-board'}}") input.primary.wide.leaveBoardBtn#leaveBoardBtn(type="submit" value="{{_ 'confirm-btn'}}")
input.primary.wide.cancelLeaveBoardBtn#cancelLeaveBoardBtn(type="submit" value="{{_ 'Cancel'}}") input.primary.wide.cancelLeaveBoardBtn#cancelLeaveBoardBtn(type="submit" value="{{_ 'cancel'}}")
template(name="addBoardTeamPopup") template(name="addBoardTeamPopup")
select.js-boardTeams#jsBoardTeams select.js-boardTeams#jsBoardTeams
@ -487,12 +511,12 @@ template(name="removeBoardTeamPopup")
form form
input.hide#hideTeamId(type="text" value=team._id) input.hide#hideTeamId(type="text" value=team._id)
label label
| {{_ 'leave-board'}} ? | {{_ 'remove-team-from-table'}}
br br
hr hr
div.buttonsContainer div.buttonsContainer
input.primary.wide.leaveBoardBtn#leaveBoardTeamBtn(type="submit" value="{{_ 'leave-board'}}") input.primary.wide.leaveBoardBtn#leaveBoardTeamBtn(type="submit" value="{{_ 'confirm-btn'}}")
input.primary.wide.cancelLeaveBoardBtn#cancelLeaveBoardTeamBtn(type="submit" value="{{_ 'Cancel'}}") input.primary.wide.cancelLeaveBoardBtn#cancelLeaveBoardTeamBtn(type="submit" value="{{_ 'cancel'}}")
template(name="addMemberPopup") template(name="addMemberPopup")
.js-search-member .js-search-member

View file

@ -183,19 +183,20 @@ Template.memberPopup.helpers({
}, },
}); });
Template.boardMenuPopup.events({ Template.boardMenuPopup.events({
'click .js-rename-board': Popup.open('boardChangeTitle'), 'click .js-rename-board': Popup.open('boardChangeTitle'),
'click .js-open-rules-view'() { 'click .js-open-rules-view'() {
Modal.openWide('rulesMain'); Modal.openWide('rulesMain');
Popup.close(); Popup.back();
}, },
'click .js-custom-fields'() { 'click .js-custom-fields'() {
Sidebar.setView('customFields'); Sidebar.setView('customFields');
Popup.close(); Popup.back();
}, },
'click .js-open-archives'() { 'click .js-open-archives'() {
Sidebar.setView('archives'); Sidebar.setView('archives');
Popup.close(); Popup.back();
}, },
'click .js-change-board-color': Popup.open('boardChangeColor'), 'click .js-change-board-color': Popup.open('boardChangeColor'),
'click .js-change-language': Popup.open('changeLanguage'), 'click .js-change-language': Popup.open('changeLanguage'),
@ -208,7 +209,7 @@ Template.boardMenuPopup.events({
}), }),
'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() { 'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() {
const currentBoard = Boards.findOne(Session.get('currentBoard')); const currentBoard = Boards.findOne(Session.get('currentBoard'));
Popup.close(); Popup.back();
Boards.remove(currentBoard._id); Boards.remove(currentBoard._id);
FlowRouter.go('home'); FlowRouter.go('home');
}), }),
@ -251,7 +252,7 @@ Template.boardMenuPopup.helpers({
Template.memberPopup.events({ Template.memberPopup.events({
'click .js-filter-member'() { 'click .js-filter-member'() {
Filter.members.toggle(this.userId); Filter.members.toggle(this.userId);
Popup.close(); Popup.back();
}, },
'click .js-change-role': Popup.open('changePermissions'), 'click .js-change-role': Popup.open('changePermissions'),
'click .js-remove-member': Popup.afterConfirm('removeMember', function() { 'click .js-remove-member': Popup.afterConfirm('removeMember', function() {
@ -265,12 +266,12 @@ Template.memberPopup.events({
card.unassignAssignee(memberId); card.unassignAssignee(memberId);
}); });
Boards.findOne(boardId).removeMember(memberId); Boards.findOne(boardId).removeMember(memberId);
Popup.close(); Popup.back();
}), }),
'click .js-leave-member': Popup.afterConfirm('leaveBoard', () => { 'click .js-leave-member': Popup.afterConfirm('leaveBoard', () => {
const boardId = Session.get('currentBoard'); const boardId = Session.get('currentBoard');
Meteor.call('quitBoard', boardId, () => { Meteor.call('quitBoard', boardId, () => {
Popup.close(); Popup.back();
FlowRouter.go('home'); FlowRouter.go('home');
}); });
}), }),
@ -290,6 +291,42 @@ Template.leaveBoardPopup.helpers({
return Boards.findOne(Session.get('currentBoard')); return Boards.findOne(Session.get('currentBoard'));
}, },
}); });
BlazeComponent.extendComponent({
onCreated() {
this.error = new ReactiveVar('');
this.loading = new ReactiveVar(false);
this.findOrgsOptions = new ReactiveVar({});
this.findTeamsOptions = new ReactiveVar({});
this.page = new ReactiveVar(1);
this.teamPage = new ReactiveVar(1);
this.autorun(() => {
const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {});
});
this.autorun(() => {
const limitTeams = this.teamPage.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('team', this.findTeamsOptions.get(), limitTeams, () => {});
});
},
onRendered() {
this.setLoading(false);
},
setError(error) {
this.error.set(error);
},
setLoading(w) {
this.loading.set(w);
},
isLoading() {
return this.loading.get();
},
}).register('membersWidget');
Template.membersWidget.helpers({ Template.membersWidget.helpers({
isInvited() { isInvited() {
@ -307,6 +344,21 @@ Template.membersWidget.helpers({
isBoardAdmin() { isBoardAdmin() {
return Meteor.user().isBoardAdmin(); return Meteor.user().isBoardAdmin();
}, },
AtLeastOneOrgWasCreated(){
let orgs = Org.find({}, {sort: { createdAt: -1 }});
if(orgs === undefined)
return false;
return orgs.count() > 0;
},
AtLeastOneTeamWasCreated(){
let teams = Team.find({}, {sort: { createdAt: -1 }});
if(teams === undefined)
return false;
return teams.count() > 0;
},
}); });
Template.membersWidget.events({ Template.membersWidget.events({
@ -408,7 +460,7 @@ BlazeComponent.extendComponent({
activities: ['all'], activities: ['all'],
}); });
} }
Popup.close(); Popup.back();
}, },
}, },
]; ];
@ -460,6 +512,21 @@ BlazeComponent.extendComponent({
}; };
const queryParams = { const queryParams = {
authToken: Accounts._storedLoginToken(), authToken: Accounts._storedLoginToken(),
delimiter: ',',
};
return FlowRouter.path(
'/api/boards/:boardId/export/csv',
params,
queryParams,
);
},
exportScsvUrl() {
const params = {
boardId: Session.get('currentBoard'),
};
const queryParams = {
authToken: Accounts._storedLoginToken(),
delimiter: ';',
}; };
return FlowRouter.path( return FlowRouter.path(
'/api/boards/:boardId/export/csv', '/api/boards/:boardId/export/csv',
@ -1162,7 +1229,7 @@ BlazeComponent.extendComponent({
self.setLoading(false); self.setLoading(false);
if (err) self.setError(err.error); if (err) self.setError(err.error);
else if (ret.email) self.setError('email-sent'); else if (ret.email) self.setError('email-sent');
else Popup.close(); else Popup.back();
}); });
}, },
@ -1249,7 +1316,7 @@ BlazeComponent.extendComponent({
} }
} }
Popup.close(); Popup.back();
}, },
}, },
]; ];
@ -1258,8 +1325,7 @@ BlazeComponent.extendComponent({
Template.addBoardOrgPopup.helpers({ Template.addBoardOrgPopup.helpers({
orgsDatas() { orgsDatas() {
// return Org.find({}, {sort: { createdAt: -1 }}); let orgs = Org.find({}, {sort: { orgDisplayName: 1 }});
let orgs = Org.find({}, {sort: { createdAt: -1 }});
return orgs; return orgs;
}, },
}); });
@ -1313,10 +1379,10 @@ BlazeComponent.extendComponent({
Meteor.call('setBoardOrgs', boardOrganizations, currentBoard._id); Meteor.call('setBoardOrgs', boardOrganizations, currentBoard._id);
Popup.close(); Popup.back();
}, },
'click #cancelLeaveBoardBtn'(){ 'click #cancelLeaveBoardBtn'(){
Popup.close(); Popup.back();
}, },
}, },
]; ];
@ -1340,6 +1406,13 @@ BlazeComponent.extendComponent({
const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER; const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {}); this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
}); });
this.findUsersOptions = new ReactiveVar({});
this.userPage = new ReactiveVar(1);
this.autorun(() => {
const limitUsers = this.userPage.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
});
}, },
onRendered() { onRendered() {
@ -1384,11 +1457,39 @@ BlazeComponent.extendComponent({
}) })
if (selectedTeamId != "-1") { if (selectedTeamId != "-1") {
Meteor.call('setBoardTeams', boardTeams, currentBoard._id); let members = currentBoard.members;
let query = {
"teams.teamId": { $in: boardTeams.map(t => t.teamId) },
};
const boardTeamUsers = Users.find(query, {
sort: { sort: 1 },
});
if(boardTeams !== undefined && boardTeams.length > 0){
let index;
if(boardTeamUsers && boardTeamUsers.count() > 0){
boardTeamUsers.forEach((u) => {
index = members.findIndex(function(m){ return m.userId == u._id});
if(index == -1){
members.push({
"isActive": true,
"isAdmin": u.isAdmin !== undefined ? u.isAdmin : false,
"isCommentOnly" : false,
"isNoComments" : false,
"userId": u._id,
});
}
});
}
}
Meteor.call('setBoardTeams', boardTeams, members, currentBoard._id);
} }
} }
Popup.close(); Popup.back();
}, },
}, },
]; ];
@ -1397,7 +1498,7 @@ BlazeComponent.extendComponent({
Template.addBoardTeamPopup.helpers({ Template.addBoardTeamPopup.helpers({
teamsDatas() { teamsDatas() {
let teams = Team.find({}, {sort: { createdAt: -1 }}); let teams = Team.find({}, {sort: { teamDisplayName: 1 }});
return teams; return teams;
}, },
}); });
@ -1413,6 +1514,13 @@ BlazeComponent.extendComponent({
const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER; const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {}); this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
}); });
this.findUsersOptions = new ReactiveVar({});
this.userPage = new ReactiveVar(1);
this.autorun(() => {
const limitUsers = this.userPage.get() * Number.MAX_SAFE_INTEGER;
this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
});
}, },
onRendered() { onRendered() {
@ -1449,12 +1557,33 @@ BlazeComponent.extendComponent({
} }
} }
Meteor.call('setBoardTeams', boardTeams, currentBoard._id); let members = currentBoard.members;
let query = {
"teams.teamId": stringTeamId
};
Popup.close(); const boardTeamUsers = Users.find(query, {
sort: { sort: 1 },
});
if(currentBoard.teams !== undefined && currentBoard.teams.length > 0){
let index;
if(boardTeamUsers && boardTeamUsers.count() > 0){
boardTeamUsers.forEach((u) => {
index = members.findIndex(function(m){ return m.userId == u._id});
if(index !== -1 && (u.isAdmin === undefined || u.isAdmin == false)){
members.splice(index, 1);
}
});
}
}
Meteor.call('setBoardTeams', boardTeams, members, currentBoard._id);
Popup.back();
}, },
'click #cancelLeaveBoardTeamBtn'(){ 'click #cancelLeaveBoardTeamBtn'(){
Popup.close(); Popup.back();
}, },
}, },
]; ];

View file

@ -224,3 +224,24 @@
.cancelLeaveBoardBtn .cancelLeaveBoardBtn
margin-left: 5% !important margin-left: 5% !important
background-color: red !important background-color: red !important
.addTeamsLabel, .addOrganizationsLabel
font-weight: normal
.js-manage-board-removeTeam:hover, .js-manage-board-removeTeam.is-active,
.js-manage-board-removeOrg:hover, .js-manage-board-removeOrg.is-active
box-shadow: 0 0 0 2px #e23210 inset !important
.js-manage-board-addTeam:hover, .js-manage-board-addTeam.is-active,
.js-manage-board-addOrg:hover , .js-manage-board-addOrg.is-active
box-shadow: 0 0 0 2px #73ea10 inset !important
.addTeamFaPlus
color: green !important
.removeTeamFaMinus
color: red !important
.divaddfaplusminus
padding-top: 5px;
margin-left: 40px;

View file

@ -1,4 +1,4 @@
archivedRequested = false; //archivedRequested = false;
const subManager = new SubsManager(); const subManager = new SubsManager();
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
@ -13,7 +13,7 @@ BlazeComponent.extendComponent({
const currentBoardId = Session.get('currentBoard'); const currentBoardId = Session.get('currentBoard');
if (!currentBoardId) return; if (!currentBoardId) return;
const handle = subManager.subscribe('board', currentBoardId, true); const handle = subManager.subscribe('board', currentBoardId, true);
archivedRequested = true; //archivedRequested = true;
Tracker.nonreactive(() => { Tracker.nonreactive(() => {
Tracker.autorun(() => { Tracker.autorun(() => {
this.isArchiveReady.set(handle.ready()); this.isArchiveReady.set(handle.ready());
@ -94,13 +94,13 @@ BlazeComponent.extendComponent({
'click .js-delete-card': Popup.afterConfirm('cardDelete', function() { 'click .js-delete-card': Popup.afterConfirm('cardDelete', function() {
const cardId = this._id; const cardId = this._id;
Cards.remove(cardId); Cards.remove(cardId);
Popup.close(); Popup.back();
}), }),
'click .js-delete-all-cards': Popup.afterConfirm('cardDelete', () => { 'click .js-delete-all-cards': Popup.afterConfirm('cardDelete', () => {
this.archivedCards().forEach(card => { this.archivedCards().forEach(card => {
Cards.remove(card._id); Cards.remove(card._id);
}); });
Popup.close(); Popup.back();
}), }),
'click .js-restore-list'() { 'click .js-restore-list'() {
@ -115,13 +115,13 @@ BlazeComponent.extendComponent({
'click .js-delete-list': Popup.afterConfirm('listDelete', function() { 'click .js-delete-list': Popup.afterConfirm('listDelete', function() {
this.remove(); this.remove();
Popup.close(); Popup.back();
}), }),
'click .js-delete-all-lists': Popup.afterConfirm('listDelete', () => { 'click .js-delete-all-lists': Popup.afterConfirm('listDelete', () => {
this.archivedLists().forEach(list => { this.archivedLists().forEach(list => {
list.remove(); list.remove();
}); });
Popup.close(); Popup.back();
}), }),
'click .js-restore-swimlane'() { 'click .js-restore-swimlane'() {
@ -138,7 +138,7 @@ BlazeComponent.extendComponent({
'swimlaneDelete', 'swimlaneDelete',
function() { function() {
this.remove(); this.remove();
Popup.close(); Popup.back();
}, },
), ),
'click .js-delete-all-swimlanes': Popup.afterConfirm( 'click .js-delete-all-swimlanes': Popup.afterConfirm(
@ -147,7 +147,7 @@ BlazeComponent.extendComponent({
this.archivedSwimlanes().forEach(swimlane => { this.archivedSwimlanes().forEach(swimlane => {
swimlane.remove(); swimlane.remove();
}); });
Popup.close(); Popup.back();
}, },
), ),
}, },

View file

@ -43,6 +43,14 @@ template(name="createCustomFieldPopup")
option(value=value selected="selected") {{name}} option(value=value selected="selected") {{name}}
else else
option(value=value) {{name}} option(value=value) {{name}}
a.flex.js-field-show-sum-at-top-of-list(class="{{#if showSumAtTopOfList}}is-checked{{/if}}")
.materialCheckBox(class="{{#if showSumAtTopOfList}}is-checked{{/if}}")
span {{_ 'showSum-field-on-list'}}
div.js-field-settings.js-field-settings-currency(class="{{#if isTypeNotSelected 'number'}}hide{{/if}}")
a.flex.js-field-show-sum-at-top-of-list(class="{{#if showSumAtTopOfList}}is-checked{{/if}}")
.materialCheckBox(class="{{#if showSumAtTopOfList}}is-checked{{/if}}")
span {{_ 'showSum-field-on-list'}}
div.js-field-settings.js-field-settings-dropdown(class="{{#if isTypeNotSelected 'dropdown'}}hide{{/if}}") div.js-field-settings.js-field-settings-dropdown(class="{{#if isTypeNotSelected 'dropdown'}}hide{{/if}}")
label label

View file

@ -234,6 +234,14 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
$target.find('.materialCheckBox').toggleClass('is-checked'); $target.find('.materialCheckBox').toggleClass('is-checked');
$target.toggleClass('is-checked'); $target.toggleClass('is-checked');
}, },
'click .js-field-show-sum-at-top-of-list'(evt) {
let $target = $(evt.target);
if (!$target.hasClass('js-field-show-sum-at-top-of-list')) {
$target = $target.parent();
}
$target.find('.materialCheckBox').toggleClass('is-checked');
$target.toggleClass('is-checked');
},
'click .primary'(evt) { 'click .primary'(evt) {
evt.preventDefault(); evt.preventDefault();
@ -248,6 +256,8 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
this.find('.js-field-automatically-on-card.is-checked') !== null, this.find('.js-field-automatically-on-card.is-checked') !== null,
alwaysOnCard: alwaysOnCard:
this.find('.js-field-always-on-card.is-checked') !== null, this.find('.js-field-always-on-card.is-checked') !== null,
showSumAtTopOfList:
this.find('.js-field-show-sum-at-top-of-list.is-checked') !== null,
}; };
// insert or update // insert or update
@ -273,7 +283,7 @@ const CreateCustomFieldPopup = BlazeComponent.extendComponent({
} else { } else {
CustomFields.remove(customField._id); CustomFields.remove(customField._id);
} }
Popup.close(); Popup.back();
}, },
), ),
}, },
@ -292,6 +302,6 @@ CreateCustomFieldPopup.register('createCustomFieldPopup');
'submit'(evt) { 'submit'(evt) {
const customFieldId = this._id; const customFieldId = this._id;
CustomFields.remove(customFieldId); CustomFields.remove(customFieldId);
Popup.close(); Popup.back();
} }
});*/ });*/

View file

@ -4,11 +4,18 @@
and #each x in y constructors to fix this. and #each x in y constructors to fix this.
template(name="filterSidebar") template(name="filterSidebar")
h3 {{_ 'list-filter-label'}} h3
i.fa.fa-trello
| {{_ 'list-filter-label'}}
ul.sidebar-list ul.sidebar-list
form.js-list-filter form.js-list-filter
input(type="text") input(type="text")
hr hr
h3
i.fa.fa-list-alt
| {{_ 'filter-card-title-label'}}
input.js-field-card-filter(type="text")
hr
h3 h3
i.fa.fa-tags i.fa.fa-tags
| {{_ 'filter-labels-label'}} | {{_ 'filter-labels-label'}}

View file

@ -8,6 +8,11 @@ BlazeComponent.extendComponent({
evt.preventDefault(); evt.preventDefault();
Filter.lists.set(this.find('.js-list-filter input').value.trim()); Filter.lists.set(this.find('.js-list-filter input').value.trim());
}, },
'change .js-field-card-filter'(evt) {
evt.preventDefault();
Filter.title.set(this.find('.js-field-card-filter').value.trim());
Filter.resetExceptions();
},
'click .js-toggle-label-filter'(evt) { 'click .js-toggle-label-filter'(evt) {
evt.preventDefault(); evt.preventDefault();
Filter.labelIds.toggle(this.currentData()._id); Filter.labelIds.toggle(this.currentData()._id);
@ -94,14 +99,14 @@ BlazeComponent.extendComponent({
}).register('filterSidebar'); }).register('filterSidebar');
function mutateSelectedCards(mutationName, ...args) { function mutateSelectedCards(mutationName, ...args) {
Cards.find(MultiSelection.getMongoSelector()).forEach(card => { Cards.find(MultiSelection.getMongoSelector(), {sort: ['sort']}).forEach(card => {
card[mutationName](...args); card[mutationName](...args);
}); });
} }
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
mapSelection(kind, _id) { mapSelection(kind, _id) {
return Cards.find(MultiSelection.getMongoSelector()).map(card => { return Cards.find(MultiSelection.getMongoSelector(), {sort: ['sort']}).map(card => {
const methodName = kind === 'label' ? 'hasLabel' : 'isAssigned'; const methodName = kind === 'label' ? 'hasLabel' : 'isAssigned';
return card[methodName](_id); return card[methodName](_id);
}); });
@ -171,22 +176,22 @@ Template.multiselectionSidebar.helpers({
Template.disambiguateMultiLabelPopup.events({ Template.disambiguateMultiLabelPopup.events({
'click .js-remove-label'() { 'click .js-remove-label'() {
mutateSelectedCards('removeLabel', this._id); mutateSelectedCards('removeLabel', this._id);
Popup.close(); Popup.back();
}, },
'click .js-add-label'() { 'click .js-add-label'() {
mutateSelectedCards('addLabel', this._id); mutateSelectedCards('addLabel', this._id);
Popup.close(); Popup.back();
}, },
}); });
Template.disambiguateMultiMemberPopup.events({ Template.disambiguateMultiMemberPopup.events({
'click .js-unassign-member'() { 'click .js-unassign-member'() {
mutateSelectedCards('assignMember', this._id); mutateSelectedCards('assignMember', this._id);
Popup.close(); Popup.back();
}, },
'click .js-assign-member'() { 'click .js-assign-member'() {
mutateSelectedCards('unassignMember', this._id); mutateSelectedCards('unassignMember', this._id);
Popup.close(); Popup.back();
}, },
}); });

View file

@ -3,10 +3,14 @@ template(name="searchSidebar")
input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto") input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus dir="auto")
.list-body .list-body
.minilists.clearfix.js-minilists .minilists.clearfix.js-minilists
hr
| {{_ 'lists' }}
each (lists) each (lists)
a.minilist-wrapper.js-minilist(href=originRelativeUrl) a.minilist-wrapper.js-minilist(href=originRelativeUrl)
+minilist(this) +minilist(this)
.minicards.clearfix.js-minicards .minicards.clearfix.js-minicards
each (results) hr
| {{_ 'cards' }}
each (cards)
a.minicard-wrapper.js-minicard(href=originRelativeUrl) a.minicard-wrapper.js-minicard(href=originRelativeUrl)
+minicard(this) +minicard(this)

View file

@ -3,7 +3,7 @@ BlazeComponent.extendComponent({
this.term = new ReactiveVar(''); this.term = new ReactiveVar('');
}, },
results() { cards() {
const currentBoard = Boards.findOne(Session.get('currentBoard')); const currentBoard = Boards.findOne(Session.get('currentBoard'));
return currentBoard.searchCards(this.term.get()); return currentBoard.searchCards(this.term.get());
}, },
@ -13,9 +13,24 @@ BlazeComponent.extendComponent({
return currentBoard.searchLists(this.term.get()); return currentBoard.searchLists(this.term.get());
}, },
clickOnMiniCard(evt) {
if (Utils.isMiniScreen()) {
evt.preventDefault();
Session.set('popupCardId', this.currentData()._id);
this.cardDetailsPopup(evt);
}
},
cardDetailsPopup(event) {
if (!Popup.isOpen()) {
Popup.open("cardDetails")(event);
}
},
events() { events() {
return [ return [
{ {
'click .js-minicard': this.clickOnMiniCard,
'submit .js-search-term-form'(evt) { 'submit .js-search-term-form'(evt) {
evt.preventDefault(); evt.preventDefault();
this.term.set(evt.target.searchTerm.value); this.term.set(evt.target.searchTerm.value);

View file

@ -26,7 +26,7 @@ template(name="swimlaneFixedHeader")
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}") a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}")
a.fa.fa-navicon.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}") a.fa.fa-navicon.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
unless isMiniScreen unless isMiniScreen
if showDesktopDragHandles if isShowDesktopDragHandles
a.swimlane-header-handle.handle.fa.fa-arrows.js-swimlane-header-handle a.swimlane-header-handle.handle.fa.fa-arrows.js-swimlane-header-handle
if isMiniScreen if isMiniScreen
a.swimlane-header-miniscreen-handle.handle.fa.fa-arrows.js-swimlane-header-handle a.swimlane-header-miniscreen-handle.handle.fa.fa-arrows.js-swimlane-header-handle

View file

@ -28,19 +28,6 @@ BlazeComponent.extendComponent({
}, },
}).register('swimlaneHeader'); }).register('swimlaneHeader');
Template.swimlaneHeader.helpers({
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
});
Template.swimlaneFixedHeader.helpers({ Template.swimlaneFixedHeader.helpers({
isBoardAdmin() { isBoardAdmin() {
return Meteor.user().isBoardAdmin(); return Meteor.user().isBoardAdmin();
@ -52,7 +39,7 @@ Template.swimlaneActionPopup.events({
'click .js-close-swimlane'(event) { 'click .js-close-swimlane'(event) {
event.preventDefault(); event.preventDefault();
this.archive(); this.archive();
Popup.close(); Popup.back();
}, },
'click .js-move-swimlane': Popup.open('moveSwimlane'), 'click .js-move-swimlane': Popup.open('moveSwimlane'),
'click .js-copy-swimlane': Popup.open('copySwimlane'), 'click .js-copy-swimlane': Popup.open('copySwimlane'),
@ -101,7 +88,7 @@ BlazeComponent.extendComponent({
// XXX ideally, we should move the popup to the newly // XXX ideally, we should move the popup to the newly
// created swimlane so a user can add more than one swimlane // created swimlane so a user can add more than one swimlane
// with a minimum of interactions // with a minimum of interactions
Popup.close(); Popup.back();
}, },
'click .js-swimlane-template': Popup.open('searchElement'), 'click .js-swimlane-template': Popup.open('searchElement'),
}, },
@ -131,11 +118,11 @@ BlazeComponent.extendComponent({
}, },
'click .js-submit'() { 'click .js-submit'() {
this.currentSwimlane.setColor(this.currentColor.get()); this.currentSwimlane.setColor(this.currentColor.get());
Popup.close(); Popup.back();
}, },
'click .js-remove-color'() { 'click .js-remove-color'() {
this.currentSwimlane.setColor(null); this.currentSwimlane.setColor(null);
Popup.close(); Popup.back();
}, },
}, },
]; ];

View file

@ -14,7 +14,8 @@ template(name="swimlane")
+addListForm +addListForm
else else
each lists each lists
+list(this) if visible this
+list(this)
if currentCardIsInThisList _id ../_id if currentCardIsInThisList _id ../_id
+cardDetails(currentCard) +cardDetails(currentCard)
if currentUser.isBoardMember if currentUser.isBoardMember
@ -52,6 +53,7 @@ template(name="addListForm")
autocomplete="off" autofocus) autocomplete="off" autofocus)
.edit-controls.clearfix .edit-controls.clearfix
button.primary.confirm(type="submit") {{_ 'save'}} button.primary.confirm(type="submit") {{_ 'save'}}
.fa.fa-times-thin.js-close-inlined-form
unless currentBoard.isTemplatesBoard unless currentBoard.isTemplatesBoard
unless currentBoard.isTemplateBoard unless currentBoard.isTemplateBoard
span.quiet span.quiet

View file

@ -9,7 +9,7 @@ function currentListIsInThisSwimlane(swimlaneId) {
} }
function currentCardIsInThisList(listId, swimlaneId) { function currentCardIsInThisList(listId, swimlaneId) {
const currentCard = Cards.findOne(Session.get('currentCard')); const currentCard = Utils.getCurrentCard();
const currentUser = Meteor.user(); const currentUser = Meteor.user();
if ( if (
currentUser && currentUser &&
@ -57,7 +57,7 @@ function initSortable(boardComponent, $listsDom) {
tolerance: 'pointer', tolerance: 'pointer',
helper: 'clone', helper: 'clone',
items: '.js-list:not(.js-list-composer)', items: '.js-list:not(.js-list-composer)',
placeholder: 'list placeholder', placeholder: 'js-list placeholder',
distance: 7, distance: 7,
start(evt, ui) { start(evt, ui) {
ui.placeholder.height(ui.helper.height()); ui.placeholder.height(ui.helper.height());
@ -95,22 +95,11 @@ function initSortable(boardComponent, $listsDom) {
//} //}
boardComponent.autorun(() => { boardComponent.autorun(() => {
let showDesktopDragHandles = false; if (Utils.isMiniScreenOrShowDesktopDragHandles()) {
currentUser = Meteor.user();
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
showDesktopDragHandles = true;
} else {
showDesktopDragHandles = false;
}
if (Utils.isMiniScreen() || showDesktopDragHandles) {
$listsDom.sortable({ $listsDom.sortable({
handle: '.js-list-handle', handle: '.js-list-handle',
}); });
} else if (!Utils.isMiniScreen() && !showDesktopDragHandles) { } else {
$listsDom.sortable({ $listsDom.sortable({
handle: '.js-list-header', handle: '.js-list-header',
}); });
@ -123,7 +112,7 @@ function initSortable(boardComponent, $listsDom) {
'disabled', 'disabled',
// Disable drag-dropping when user is not member/is worker // Disable drag-dropping when user is not member/is worker
//!userIsMember() || Meteor.user().isWorker(), //!userIsMember() || Meteor.user().isWorker(),
!Meteor.user().isBoardAdmin(), !Meteor.user() || !Meteor.user().isBoardAdmin(),
// Not disable drag-dropping while in multi-selection mode // Not disable drag-dropping while in multi-selection mode
// MultiSelection.isActive() || !userIsMember(), // MultiSelection.isActive() || !userIsMember(),
); );
@ -136,7 +125,7 @@ BlazeComponent.extendComponent({
const boardComponent = this.parentComponent(); const boardComponent = this.parentComponent();
const $listsDom = this.$('.js-lists'); const $listsDom = this.$('.js-lists');
if (!Session.get('currentCard')) { if (!Utils.getCurrentCardId()) {
boardComponent.scrollLeft(); boardComponent.scrollLeft();
} }
@ -148,19 +137,38 @@ BlazeComponent.extendComponent({
this._isDragging = false; this._isDragging = false;
this._lastDragPositionX = 0; this._lastDragPositionX = 0;
}, },
id() { id() {
return this._id; return this._id;
}, },
currentCardIsInThisList(listId, swimlaneId) { currentCardIsInThisList(listId, swimlaneId) {
return currentCardIsInThisList(listId, swimlaneId); return currentCardIsInThisList(listId, swimlaneId);
}, },
currentListIsInThisSwimlane(swimlaneId) { currentListIsInThisSwimlane(swimlaneId) {
return currentListIsInThisSwimlane(swimlaneId); return currentListIsInThisSwimlane(swimlaneId);
}, },
visible(list) {
if (list.archived) {
// Show archived list only when filter archive is on
if (!Filter.archive.isSelected()) {
return false;
}
}
if (Filter.lists._isActive()) {
if (!list.title.match(Filter.lists.getRegexSelector())) {
return false;
}
}
if (Filter.hideEmpty.isSelected()) {
const swimlaneId = this.parentComponent()
.parentComponent()
.data()._id;
const cards = list.cards(swimlaneId);
if (cards.count() === 0) {
return false;
}
}
return true;
},
events() { events() {
return [ return [
{ {
@ -172,19 +180,8 @@ BlazeComponent.extendComponent({
// the user will legitimately expect to be able to select some text with // the user will legitimately expect to be able to select some text with
// his mouse. // his mouse.
let showDesktopDragHandles = false;
currentUser = Meteor.user();
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
showDesktopDragHandles = true;
} else {
showDesktopDragHandles = false;
}
const noDragInside = ['a', 'input', 'textarea', 'p'].concat( const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
Utils.isMiniScreen() || showDesktopDragHandles Utils.isMiniScreenOrShowDesktopDragHandles()
? ['.js-list-handle', '.js-swimlane-header-handle'] ? ['.js-list-handle', '.js-swimlane-header-handle']
: ['.js-list-header'], : ['.js-list-header'],
); );
@ -240,13 +237,15 @@ BlazeComponent.extendComponent({
{ {
submit(evt) { submit(evt) {
evt.preventDefault(); evt.preventDefault();
const lastList = this.currentBoard.getLastList();
const sortIndex = Utils.calculateIndexData(lastList, null).base;
const titleInput = this.find('.list-name-input'); const titleInput = this.find('.list-name-input');
const title = titleInput.value.trim(); const title = titleInput.value.trim();
if (title) { if (title) {
Lists.insert({ Lists.insert({
title, title,
boardId: Session.get('currentBoard'), boardId: Session.get('currentBoard'),
sort: $('.list').length, sort: sortIndex,
type: this.isListTemplatesSwimlane ? 'template-list' : 'list', type: this.isListTemplatesSwimlane ? 'template-list' : 'list',
swimlaneId: this.currentBoard.isTemplatesBoard() swimlaneId: this.currentBoard.isTemplatesBoard()
? this.currentSwimlane._id ? this.currentSwimlane._id
@ -264,16 +263,6 @@ BlazeComponent.extendComponent({
}).register('addListForm'); }).register('addListForm');
Template.swimlane.helpers({ Template.swimlane.helpers({
showDesktopDragHandles() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
return true;
} else {
return false;
}
},
canSeeAddList() { canSeeAddList() {
return Meteor.user().isBoardAdmin(); return Meteor.user().isBoardAdmin();
/* /*
@ -291,8 +280,8 @@ BlazeComponent.extendComponent({
}, },
visible(list) { visible(list) {
if (list.archived) { if (list.archived) {
// Show archived list only when filter archive is on or archive is selected // Show archived list only when filter archive is on
if (!(Filter.archive.isSelected() || archivedRequested)) { if (!Filter.archive.isSelected()) {
return false; return false;
} }
} }
@ -316,7 +305,7 @@ BlazeComponent.extendComponent({
const boardComponent = this.parentComponent(); const boardComponent = this.parentComponent();
const $listsDom = this.$('.js-lists'); const $listsDom = this.$('.js-lists');
if (!Session.get('currentCard')) { if (!Utils.getCurrentCardId()) {
boardComponent.scrollLeft(); boardComponent.scrollLeft();
} }
@ -359,7 +348,7 @@ class MoveSwimlaneComponent extends BlazeComponent {
boardId = bSelect.options[bSelect.selectedIndex].value; boardId = bSelect.options[bSelect.selectedIndex].value;
Meteor.call(this.serverMethod, this.currentSwimlane._id, boardId); Meteor.call(this.serverMethod, this.currentSwimlane._id, boardId);
} }
Popup.close(); Popup.back();
}, },
}, },
]; ];

View file

@ -32,7 +32,9 @@ template(name="boardOrgRow")
td td
if currentUser.isBoardAdmin if currentUser.isBoardAdmin
a.member.orgOrTeamMember.add-member.js-manage-board-removeOrg(title="{{_ 'remove-from-board'}}") a.member.orgOrTeamMember.add-member.js-manage-board-removeOrg(title="{{_ 'remove-from-board'}}")
i.fa.fa-minus i.removeTeamFaMinus.fa.fa-minus
.divaddfaplusminus
| {{_ 'remove-btn'}}
template(name="boardTeamRow") template(name="boardTeamRow")
tr tr
@ -43,7 +45,9 @@ template(name="boardTeamRow")
td td
if currentUser.isBoardAdmin if currentUser.isBoardAdmin
a.member.orgOrTeamMember.add-member.js-manage-board-removeTeam(title="{{_ 'remove-from-board'}}") a.member.orgOrTeamMember.add-member.js-manage-board-removeTeam(title="{{_ 'remove-from-board'}}")
i.fa.fa-minus i.removeTeamFaMinus.fa.fa-minus
.divaddfaplusminus
| {{_ 'remove-btn'}}
template(name="boardOrgName") template(name="boardOrgName")
svg.avatar.avatar-initials(viewBox="0 0 {{orgViewPortWidth}} 15") svg.avatar.avatar-initials(viewBox="0 0 {{orgViewPortWidth}} 15")

Some files were not shown because too many files have changed in this diff Show more