Compare commits

..

No commits in common. "main" and "v7.09" have entirely different histories.
main ... v7.09

961 changed files with 19291 additions and 340307 deletions

View file

@ -1,30 +1,25 @@
FROM ubuntu:24.04 FROM ubuntu:21.10
LABEL maintainer="wekan" LABEL maintainer="sgr"
LABEL org.opencontainers.image.ref.name="ubuntu"
LABEL org.opencontainers.image.version="24.04"
LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
# 2022-04-25: # 2022-04-25:
# - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling, # - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling,
# so changing to 21.10. https://github.com/wekan/wekan/issues/4488 # so changing to 21.10. https://github.com/wekan/wekan/issues/4488
ENV BUILD_DEPS="apt-utils gnupg gosu wget bzip2 g++ iproute2 apt-transport-https libarchive-tools" ENV BUILD_DEPS="gnupg gosu libarchive-tools wget curl bzip2 g++ build-essential python3 git ca-certificates iproute2"
ENV DEV_DEPS="curl python3 ca-certificates build-essential git" ENV DEBIAN_FRONTEND=noninteractive
ARG DEBIAN_FRONTEND=noninteractive
ENV \ ENV \
DEBUG=false \ DEBUG=false \
NODE_VERSION=v14.21.4 \ NODE_VERSION=v14.21.4 \
METEOR_RELEASE=METEOR@2.14 \ METEOR_RELEASE=METEOR@2.13 \
USE_EDGE=false \ USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \ METEOR_EDGE=1.5-beta.17 \
NPM_VERSION=6.14.17 \ NPM_VERSION=latest \
FIBERS_VERSION=4.0.1 \ FIBERS_VERSION=4.0.1 \
ARCHITECTURE=linux-x64 \ ARCHITECTURE=linux-x64 \
SRC_PATH=./ \ SRC_PATH=./ \
WITH_API=true \ WITH_API=true \
RESULTS_PER_PAGE="" \ RESULTS_PER_PAGE="" \
DEFAULT_BOARD_ID="" \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \ ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \ ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \ ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \
@ -32,14 +27,15 @@ ENV \
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 \ ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS=90 \
RICHER_CARD_COMMENT_EDITOR=false \
CARD_OPENED_WEBHOOK_ENABLED=false \
ATTACHMENTS_STORE_PATH="" \
ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM="" \ ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM="" \
ATTACHMENTS_UPLOAD_MIME_TYPES="" \ ATTACHMENTS_UPLOAD_MIME_TYPES="" \
ATTACHMENTS_UPLOAD_MAX_SIZE=0 \ ATTACHMENTS_UPLOAD_MAX_SIZE=0 \
AVATARS_UPLOAD_EXTERNAL_PROGRAM="" \ AVATARS_UPLOAD_EXTERNAL_PROGRAM="" \
AVATARS_UPLOAD_MIME_TYPES="" \ AVATARS_UPLOAD_MIME_TYPES="" \
AVATARS_UPLOAD_MAX_SIZE=0 \ AVATARS_UPLOAD_MAX_SIZE=0 \
RICHER_CARD_COMMENT_EDITOR=false \
CARD_OPENED_WEBHOOK_ENABLED=false \
MAX_IMAGE_PIXEL="" \ MAX_IMAGE_PIXEL="" \
IMAGE_COMPRESS_RATIO="" \ IMAGE_COMPRESS_RATIO="" \
NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="" \ NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="" \
@ -51,15 +47,12 @@ ENV \
MATOMO_SITE_ID="" \ MATOMO_SITE_ID="" \
MATOMO_DO_NOT_TRACK=true \ MATOMO_DO_NOT_TRACK=true \
MATOMO_WITH_USERNAME=false \ MATOMO_WITH_USERNAME=false \
METRICS_ALLOWED_IP_ADDRESSES="" \
BROWSER_POLICY_ENABLED=true \ BROWSER_POLICY_ENABLED=true \
TRUSTED_URL="" \ TRUSTED_URL="" \
WEBHOOKS_ATTRIBUTES="" \ WEBHOOKS_ATTRIBUTES="" \
OAUTH2_ENABLED=false \ OAUTH2_ENABLED=false \
OIDC_REDIRECTION_ENABLED=false \
OAUTH2_CA_CERT="" \ OAUTH2_CA_CERT="" \
OAUTH2_ADFS_ENABLED=false \ OAUTH2_ADFS_ENABLED=false \
OAUTH2_B2C_ENABLED=false \
OAUTH2_LOGIN_STYLE=redirect \ OAUTH2_LOGIN_STYLE=redirect \
OAUTH2_CLIENT_ID="" \ OAUTH2_CLIENT_ID="" \
OAUTH2_SECRET="" \ OAUTH2_SECRET="" \
@ -76,9 +69,6 @@ ENV \
LDAP_ENABLE=false \ LDAP_ENABLE=false \
LDAP_PORT=389 \ LDAP_PORT=389 \
LDAP_HOST="" \ LDAP_HOST="" \
LDAP_AD_SIMPLE_AUTH="" \
LDAP_USER_AUTHENTICATION=false \
LDAP_USER_AUTHENTICATION_FIELD=uid \
LDAP_BASEDN="" \ LDAP_BASEDN="" \
LDAP_LOGIN_FALLBACK=false \ LDAP_LOGIN_FALLBACK=false \
LDAP_RECONNECT=true \ LDAP_RECONNECT=true \
@ -96,6 +86,8 @@ ENV \
LDAP_ENCRYPTION=false \ LDAP_ENCRYPTION=false \
LDAP_CA_CERT="" \ LDAP_CA_CERT="" \
LDAP_REJECT_UNAUTHORIZED=false \ LDAP_REJECT_UNAUTHORIZED=false \
LDAP_USER_AUTHENTICATION=false \
LDAP_USER_AUTHENTICATION_FIELD=uid \
LDAP_USER_SEARCH_FILTER="" \ LDAP_USER_SEARCH_FILTER="" \
LDAP_USER_SEARCH_SCOPE="" \ LDAP_USER_SEARCH_SCOPE="" \
LDAP_USER_SEARCH_FIELD="" \ LDAP_USER_SEARCH_FIELD="" \
@ -150,32 +142,69 @@ ENV \
SAML_IDENTIFIER_FORMAT="" \ SAML_IDENTIFIER_FORMAT="" \
SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE="" \ SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE="" \
SAML_ATTRIBUTES="" \ SAML_ATTRIBUTES="" \
ORACLE_OIM_ENABLED=false \ DEFAULT_WAIT_SPINNER="" \
WAIT_SPINNER="" \
WRITABLE_PATH=/data \
S3="" S3=""
# \
# NODE_OPTIONS="--max_old_space_size=4096"
# NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------
# == at docker-compose.yml: AUTOLOGIN WITH OIDC/OAUTH2 ====
# https://github.com/wekan/wekan/wiki/autologin
#- OIDC_REDIRECTION_ENABLED=true
#--------------------------------------------------------------------- #---------------------------------------------------------------------
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
# Add more Node heap:
# NODE_OPTIONS="--max_old_space_size=4096"
# Add more stack:
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
#---------------------------------------------------------------------
# Install OS
RUN set -o xtrace \
&& useradd --user-group -m --system --home-dir /home/wekan wekan \
&& apt-get update \
&& apt-get install --assume-yes --no-install-recommends apt-utils apt-transport-https ca-certificates 2>&1 \
&& apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS}
# OLD:
# && curl -fsSLO --compressed "https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-$ARCHITECTURE.tar.xz" \
# && curl -fsSLO --compressed "https://nodejs.org/dist/$NODE_VERSION/SHASUMS256.txt.asc" \
# Install NodeJS
RUN set -o xtrace \
&& cd /tmp \
&& curl -fsSLO --compressed "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.xz" \
&& curl -fsSLO --compressed "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt" \
&& grep " node-$NODE_VERSION-$ARCHITECTURE.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-$NODE_VERSION-$ARCHITECTURE.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \
&& rm "node-$NODE_VERSION-$ARCHITECTURE.tar.xz" SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs \
&& mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp /root/.node-gyp/${NODE_VERSION} /home/wekan/.config \
&& npm install -g npm@${NPM_VERSION} \
&& chown wekan:wekan --recursive /home/wekan/.config
ENV DEBIAN_FRONTEND=dialog
USER wekan
# Install Meteor
RUN set -o xtrace \
&& cd /home/wekan \
&& curl https://install.meteor.com/?release=$METEOR_VERSION --output /home/wekan/install-meteor.sh \
# Replace tar with bsdtar in the install script; https://github.com/jshimko/meteor-launchpad/issues/39
&& sed --in-place "s/tar -xzf.*/bsdtar -xf \"\$TARBALL_FILE\" -C \"\$INSTALL_TMPDIR\"/g" /home/wekan/install-meteor.sh \
&& sed --in-place 's/VERBOSITY="--silent"/VERBOSITY="--progress-bar"/' /home/wekan/install-meteor.sh \
&& printf "\n[-] Installing Meteor $METEOR_VERSION...\n\n" \
&& sh /home/wekan/install-meteor.sh
ENV PATH=$PATH:/home/wekan/.meteor/ ENV PATH=$PATH:/home/wekan/.meteor/
RUN <<EOR USER root
echo "export PATH=$PATH" >> /etc/environment
EOR RUN echo "export PATH=$PATH" >> /etc/environment
USER wekan
# Copy source dir # Copy source dir
RUN <<EOR RUN set -o xtrace \
set -o xtrace && mkdir -p /home/wekan/app/.meteor \
&& mkdir -p /home/wekan/app/packages
mkdir -p /home/wekan/app/.meteor
mkdir -p /home/wekan/app/packages
EOR
COPY \ COPY \
.meteor/.finished-upgraders \ .meteor/.finished-upgraders \
@ -200,83 +229,44 @@ COPY \
packages \ packages \
/home/wekan/app/packages/ /home/wekan/app/packages/
# Install OS USER root
RUN <<EOR
set -o xtrace
# Add non-root user wekan RUN set -o xtrace \
useradd --user-group --system --home-dir /home/wekan wekan && chown -R wekan:wekan /home/wekan/app /home/wekan/.meteor
# OS dependencies
apt-get update --assume-yes
apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS} ${DEV_DEPS}
# Meteor installer doesn't work with the default tar binary, so using bsdtar while installing. USER wekan
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
cp $(which tar) $(which tar)~
ln -sf $(which bsdtar) $(which tar)
# Install NodeJS RUN \
cd /tmp set -o xtrace && \
# Build app
cd /home/wekan/app && \
/home/wekan/.meteor/meteor add standard-minifier-js && \
/home/wekan/.meteor/meteor npm install && \
/home/wekan/.meteor/meteor build --directory /home/wekan/app_build
# Download nodejs RUN \
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz" set -o xtrace && \
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt" cd /home/wekan/app_build/bundle/programs/server/ && \
chmod u+w package.json npm-shrinkwrap.json && \
# Verify nodejs authenticity npm install && \
grep "node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz" "SHASUMS256.txt" | shasum -a 256 -c - cd node_modules/fibers && \
rm -f "SHASUMS256.txt" node build.js
# Install Node
tar xzf "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
rm "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" "SHASUMS256.txt"
ln -s "/usr/local/bin/node" "/usr/local/bin/nodejs"
mkdir -p "/opt/nodejs/lib/node_modules/fibers/.node-gyp" "/root/.node-gyp/${NODE_VERSION} /home/wekan/.config"
# Install node dependencies
npm install -g npm@${NPM_VERSION}
chown --recursive wekan:wekan /home/wekan/.config
# Install Meteor
cd /home/wekan
chown --recursive wekan:wekan /home/wekan
echo "Starting meteor ${METEOR_RELEASE} installation... \n"
gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh
mv /root/.meteor /home/wekan/
chown --recursive wekan:wekan /home/wekan/.meteor
# sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js
cd /home/wekan/.meteor
gosu wekan:wekan /home/wekan/.meteor/meteor -- help
# Build app (Development)
cd /home/wekan/app
gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js
gosu wekan:wekan /home/wekan/.meteor/meteor npm install
# Put back the original tar
mv $(which tar)~ $(which tar)
USER root
# Cleanup # Cleanup
apt-get remove --purge --assume-yes ${BUILD_DEPS} RUN \
apt-get install --assume-yes --no-install-recommends build-essential set -o xtrace && \
apt-get autoremove --assume-yes apt-get clean -y && \
apt-get clean --assume-yes apt-get autoremove -y && \
rm -Rf /tmp/* rm -Rf /tmp/* && \
rm -Rf /var/lib/apt/lists/* rm -Rf /home/wekan/app_build && \
rm -Rf /var/cache/apt rm -Rf /var/cache/apt /var/lib/apt/lists && \
rm -Rf /var/lib/apt/lists rm -Rf /var/lib/apt/lists/*
rm -Rf /home/wekan/app_build
mkdir /data
chown wekan --recursive /data
EOR
USER wekan USER wekan
ENV PORT=3000 ENV PORT=3000
EXPOSE $PORT EXPOSE $PORT
STOPSIGNAL SIGKILL
WORKDIR /home/wekan/app WORKDIR /home/wekan/app
#--------------------------------------------------------------------- #---------------------------------------------------------------------
@ -286,6 +276,7 @@ WORKDIR /home/wekan/app
# Add more stack: # Add more stack:
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js" # bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
#--------------------------------------------------------------------- #---------------------------------------------------------------------
# #TODO:
#CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /build/main.js"]
CMD ["/home/wekan/.meteor/meteor", "run", "--verbose", "--settings", "settings.json"] CMD ["/home/wekan/.meteor/meteor", "run", "--verbose", "--settings", "settings.json"]

View file

@ -1,26 +1,18 @@
## Issue ## Issue
<!--
Please report these issues elsewhere: **[PLEASE UPGRADE](https://github.com/wekan/wekan/wiki/Backup)** to the newest WeKan ® before reporting an issue.
- SECURITY ISSUES, PGP EMAIL: https://github.com/wekan/wekan/blob/main/SECURITY.md
- UCS: https://github.com/wekan/univention/issues
If WeKan Snap is slow, try this: https://github.com/wekan/wekan/wiki/Cron
**[PLEASE UPGRADE](https://github.com/wekan/wekan/wiki/Backup)** to the newest
WeKan ® before reporting an issue, if possible.
Please search existing Open and Closed issues, most questions have already been answered. Please search existing Open and Closed issues, most questions have already been answered.
If you can not login for any reason: https://github.com/wekan/wekan/wiki/Forgot-Password If you can not login for any reason: https://github.com/wekan/wekan/wiki/Forgot-Password
Email settings, only SMTP MAIL_URL and MAIL_FROM are in use: 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
The following types of issues should be reported separately:
- SECURITY ISSUES: https://github.com/wekan/wekan/blob/master/SECURITY.md
- UCS: https://github.com/wekan/univention/issues
-->
### Server Setup Information ### Server Setup Information
<!-- Please anonymize info, and do not any of your Wekan board URLs, passwords, API tokens etc to this public issue. -->
Please anonymize info, and do not any of your Wekan board URLs, passwords,
API tokens etc to this public issue.
* Did you test in newest Wekan?: * Did you test in newest Wekan?:
* Did you configure root-url correctly so Wekan cards open correctly (see https://github.com/wekan/wekan/wiki/Settings)? * Did you configure root-url correctly so Wekan cards open correctly (see https://github.com/wekan/wekan/wiki/Settings)?
* Operating System: * Operating System:
@ -31,25 +23,13 @@ API tokens etc to this public issue.
* What webbrowser version are you using (Wekan should work on all modern browsers that support Javascript)? * What webbrowser version are you using (Wekan should work on all modern browsers that support Javascript)?
### Problem description ### Problem description
<!-- Add a recorded animated gif (e.g. with https://github.com/phw/peek) about how it works currently, and screenshot mockups how it should work. -->
Add a recorded animated gif (e.g. with https://github.com/phw/peek) about
how it works currently, and screenshot mockups how it should work.
#### Reproduction Steps #### Reproduction Steps
#### Logs #### Logs
<!-- Check Right Click>Inspect>Console in you browser - generally Chrome shows more detailed info than Firefox. -->
Check Right Click / Inspect / Console in you browser - generally Chromium <!-- Please anonymize logs.
based browsers show more detailed info than Firefox based browsers.
Please anonymize logs.
Snap: sudo snap logs wekan.wekan Snap: sudo snap logs wekan.wekan
Docker: sudo docker logs wekan-app Docker: sudo docker logs wekan-app
If logs are very long, attach them in .zip file -->
If logs are very long, attach them in .zip file

69
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -0,0 +1,69 @@
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 16 * * 3'
permissions:
contents: read
jobs:
analyze:
permissions:
actions: read # for github/codeql-action/init to get workflow details
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/autobuild to send a status report
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript', 'python']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View file

@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@v4 uses: actions/dependency-review-action@v3

View file

@ -9,11 +9,11 @@ on:
schedule: schedule:
- cron: '28 23 * * *' - cron: '28 23 * * *'
push: push:
branches: [ main ] branches: [ master ]
# Publish semver tags as releases. # Publish semver tags as releases.
tags: [ 'v*.*.*' ] tags: [ 'v*.*.*' ]
pull_request: pull_request:
branches: [ main ] branches: [ master ]
env: env:
# Use docker.io for Docker Hub if empty # Use docker.io for Docker Hub if empty
@ -32,13 +32,13 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v3
# Login against a Docker registry except on PR # Login against a Docker registry except on PR
# https://github.com/docker/login-action # https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }} - name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
@ -48,14 +48,14 @@ jobs:
# https://github.com/docker/metadata-action # https://github.com/docker/metadata-action
- name: Extract Docker metadata - name: Extract Docker metadata
id: meta id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR) # Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action # https://github.com/docker/build-push-action
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825
with: with:
context: . context: .
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}

View file

@ -3,7 +3,7 @@ name: Docker Image CI
on: on:
push: push:
branches: branches:
- main - master
permissions: permissions:
contents: read contents: read
@ -15,6 +15,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Build the Docker image - name: Build the Docker image
run: docker build . --file Dockerfile --tag wekan:$(date +%s) run: docker build . --file Dockerfile --tag wekan:$(date +%s)

View file

@ -3,7 +3,7 @@ name: Release Charts
on: on:
push: push:
branches: branches:
- main - master
permissions: permissions:
contents: read contents: read
@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
@ -25,6 +25,6 @@ jobs:
git config user.email "$GITHUB_ACTOR@users.noreply.github.com" git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Run chart-releaser - name: Run chart-releaser
uses: helm/chart-releaser-action@v1.7.0 uses: helm/chart-releaser-action@v1.5.0
env: env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

View file

@ -3,7 +3,7 @@ name: Test suite
on: on:
push: push:
branches: branches:
- main - master
pull_request: pull_request:
permissions: permissions:
@ -18,7 +18,7 @@ jobs:
# runs-on: ubuntu-latest # runs-on: ubuntu-latest
# steps: # steps:
# - name: checkout # - name: checkout
# uses: actions/checkout@v4 # uses: actions/checkout@v3
# #
# - name: setup node # - name: setup node
# uses: actions/setup-node@v1 # uses: actions/setup-node@v1
@ -42,7 +42,7 @@ jobs:
# needs: [lintcode] # needs: [lintcode]
# steps: # steps:
# - name: checkout # - name: checkout
# uses: actions/checkout@v4 # uses: actions/checkout@v3
# #
# - name: setup node # - name: setup node
# uses: actions/setup-node@v1 # uses: actions/setup-node@v1
@ -65,7 +65,7 @@ jobs:
# needs: [lintcode,lintstyle] # needs: [lintcode,lintstyle]
# steps: # steps:
# - name: checkout # - name: checkout
# uses: actions/checkout@v4 # uses: actions/checkout@v3
# #
# - name: setup node # - name: setup node
# uses: actions/setup-node@v1 # uses: actions/setup-node@v1
@ -90,12 +90,12 @@ jobs:
# CHECKOUTS # CHECKOUTS
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
# CACHING # CACHING
- name: Install Meteor - name: Install Meteor
id: cache-meteor-install id: cache-meteor-install
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: ~/.meteor path: ~/.meteor
key: v1-meteor-${{ hashFiles('.meteor/versions') }} key: v1-meteor-${{ hashFiles('.meteor/versions') }}
@ -104,7 +104,7 @@ jobs:
- name: Cache NPM dependencies - name: Cache NPM dependencies
id: cache-meteor-npm id: cache-meteor-npm
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: ~/.npm path: ~/.npm
key: v1-npm-${{ hashFiles('package-lock.json') }} key: v1-npm-${{ hashFiles('package-lock.json') }}
@ -113,7 +113,7 @@ jobs:
- name: Cache Meteor build - name: Cache Meteor build
id: cache-meteor-build id: cache-meteor-build
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: | path: |
.meteor/local/resolver-result-cache.json .meteor/local/resolver-result-cache.json
@ -125,7 +125,7 @@ jobs:
v1-meteor_build_cache- v1-meteor_build_cache-
- name: Setup meteor - name: Setup meteor
uses: meteorengineer/setup-meteor@v2 uses: meteorengineer/setup-meteor@v1
with: with:
meteor-release: '2.2' meteor-release: '2.2'
@ -136,7 +136,7 @@ jobs:
run: sh ./test-wekan.sh -cv run: sh ./test-wekan.sh -cv
- name: Upload coverage - name: Upload coverage
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: coverage-folder name: coverage-folder
path: .coverage/ path: .coverage/
@ -147,17 +147,17 @@ jobs:
needs: [tests] needs: [tests]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Download coverage - name: Download coverage
uses: actions/download-artifact@v4 uses: actions/download-artifact@v3
with: with:
name: coverage-folder name: coverage-folder
path: .coverage/ path: .coverage/
- name: Coverage Report - name: Coverage Report
uses: VeryGoodOpenSource/very_good_coverage@v3.0.0 uses: VeryGoodOpenSource/very_good_coverage@v2.1.0
with: with:
path: ".coverage/lcov.info" path: ".coverage/lcov.info"
min_coverage: 1 # TODO add tests and increase to 95! min_coverage: 1 # TODO add tests and increase to 95!

View file

@ -6,7 +6,7 @@
meteor-base@1.5.1 meteor-base@1.5.1
# Build system # Build system
ecmascript@0.16.8 ecmascript@0.16.7
standard-minifier-js@2.8.1 standard-minifier-js@2.8.1
mquandalle:jade mquandalle:jade
coffeescript@2.4.1! coffeescript@2.4.1!
@ -16,15 +16,16 @@ es5-shim@4.8.0
# Collections # Collections
aldeed:collection2 aldeed:collection2
cfs:standard-packages
cottz:publish-relations cottz:publish-relations
dburles:collection-helpers dburles:collection-helpers
idmontie:migrations idmontie:migrations
easy:search easy:search
mongo@1.16.8 mongo@1.16.7
mquandalle:collection-mutations mquandalle:collection-mutations
# Account system # Account system
accounts-password@2.4.0 accounts-password@2.3.4
useraccounts:core useraccounts:core
useraccounts:flow-routing useraccounts:flow-routing
useraccounts:unstyled useraccounts:unstyled
@ -42,7 +43,7 @@ jquery@3.0.0!
random@1.2.1 random@1.2.1
reactive-dict@1.3.1 reactive-dict@1.3.1
session@1.2.1 session@1.2.1
tracker@1.3.3 tracker@1.3.2
underscore@1.0.13 underscore@1.0.13
arillo:flow-router-helpers arillo:flow-router-helpers
audit-argument-checks@1.0.7 audit-argument-checks@1.0.7
@ -53,11 +54,91 @@ raix:handlebar-helpers
http@2.0.0! # force new http package http@2.0.0! # force new http package
# Datepicker # Datepicker
wekan-bootstrap-datepicker rajit:bootstrap3-datepicker
rajit:bootstrap3-datepicker-de
rajit:bootstrap3-datepicker-tr
rajit:bootstrap3-datepicker-ja
rajit:bootstrap3-datepicker-fr
rajit:bootstrap3-datepicker-it
rajit:bootstrap3-datepicker-th
rajit:bootstrap3-datepicker-nl
rajit:bootstrap3-datepicker-es
rajit:bootstrap3-datepicker-lv
rajit:bootstrap3-datepicker-sv
rajit:bootstrap3-datepicker-gl
rajit:bootstrap3-datepicker-eu
rajit:bootstrap3-datepicker-vi
rajit:bootstrap3-datepicker-no
rajit:bootstrap3-datepicker-en-gb
rajit:bootstrap3-datepicker-ro
rajit:bootstrap3-datepicker-rs
rajit:bootstrap3-datepicker-ru
rajit:bootstrap3-datepicker-bs
rajit:bootstrap3-datepicker-mn
rajit:bootstrap3-datepicker-id
rajit:bootstrap3-datepicker-fa
rajit:bootstrap3-datepicker-ko
rajit:bootstrap3-datepicker-pt
rajit:bootstrap3-datepicker-sl
rajit:bootstrap3-datepicker-sq
rajit:bootstrap3-datepicker-cy
rajit:bootstrap3-datepicker-ka
rajit:bootstrap3-datepicker-sr
rajit:bootstrap3-datepicker-az
rajit:bootstrap3-datepicker-cs
rajit:bootstrap3-datepicker-me
rajit:bootstrap3-datepicker-ta
rajit:bootstrap3-datepicker-eo
rajit:bootstrap3-datepicker-oc
rajit:bootstrap3-datepicker-he
rajit:bootstrap3-datepicker-sw
rajit:bootstrap3-datepicker-rs-latin
rajit:bootstrap3-datepicker-da
rajit:bootstrap3-datepicker-pt-br
rajit:bootstrap3-datepicker-br
rajit:bootstrap3-datepicker-it-ch
rajit:bootstrap3-datepicker-en-za
rajit:bootstrap3-datepicker-en-ie
rajit:bootstrap3-datepicker-en-ca
rajit:bootstrap3-datepicker-uz-latn
rajit:bootstrap3-datepicker-ms
rajit:bootstrap3-datepicker-uk
rajit:bootstrap3-datepicker-hu
rajit:bootstrap3-datepicker-fo
rajit:bootstrap3-datepicker-kr
rajit:bootstrap3-datepicker-hi
rajit:bootstrap3-datepicker-km
rajit:bootstrap3-datepicker-fi
rajit:bootstrap3-datepicker-is
rajit:bootstrap3-datepicker-kh
rajit:bootstrap3-datepicker-bg
rajit:bootstrap3-datepicker-bn
rajit:bootstrap3-datepicker-pl
rajit:bootstrap3-datepicker-et
rajit:bootstrap3-datepicker-ar
rajit:bootstrap3-datepicker-ca
rajit:bootstrap3-datepicker-kk
rajit:bootstrap3-datepicker-sk
rajit:bootstrap3-datepicker-el
rajit:bootstrap3-datepicker-hy
rajit:bootstrap3-datepicker-hr
rajit:bootstrap3-datepicker-tg
rajit:bootstrap3-datepicker-nb
rajit:bootstrap3-datepicker-mk
rajit:bootstrap3-datepicker-nl-be
rajit:bootstrap3-datepicker-zh-cn
rajit:bootstrap3-datepicker-ar-tn
rajit:bootstrap3-datepicker-en-au
rajit:bootstrap3-datepicker-fr-ch
rajit:bootstrap3-datepicker-zh-tw
rajit:bootstrap3-datepicker-uz-cyrl
rajit:bootstrap3-datepicker-sr-latin
rajit:bootstrap3-datepicker-en-nz
# UI components # UI components
ostrio:i18n ostrio:i18n
reactive-var@1.0.12 reactive-var@1.0.12
fortawesome:fontawesome
mousetrap:mousetrap mousetrap:mousetrap
mquandalle:jquery-textcomplete mquandalle:jquery-textcomplete
mquandalle:mousetrap-bindglobal mquandalle:mousetrap-bindglobal
@ -66,6 +147,8 @@ meteor-autosize
shell-server@0.5.0 shell-server@0.5.0
email@2.2.5 email@2.2.5
dynamic-import@0.7.3 dynamic-import@0.7.3
cfs:gridfs
rzymek:fullcalendar
msavin:usercache msavin:usercache
# Keep stylus in 1.1.0, because building v2 takes extra 52 minutes. # Keep stylus in 1.1.0, because building v2 takes extra 52 minutes.
meteorhacks:subs-manager meteorhacks:subs-manager
@ -73,6 +156,7 @@ meteorhacks:aggregate@1.3.0
wekan-markdown wekan-markdown
konecty:mongo-counter konecty:mongo-counter
percolate:synced-cron percolate:synced-cron
cfs:filesystem
ostrio:cookies ostrio:cookies
ostrio:files@2.3.0 ostrio:files@2.3.0
pascoual:pdfkit pascoual:pdfkit
@ -83,14 +167,10 @@ matb33:collection-hooks
simple:json-routes simple:json-routes
kadira:flow-router kadira:flow-router
spacebars spacebars
service-configuration@1.3.2 service-configuration@1.3.1
communitypackages:picker communitypackages:picker
minifier-css@1.6.4 minifier-css@1.6.4
blaze blaze
kadira:blaze-layout kadira:blaze-layout
peerlibrary:blaze-components peerlibrary:blaze-components
ejson@1.1.3 ejson
logging@1.3.3
wekan-fullcalendar
momentjs:moment@2.29.3
wekan-fontawesome

View file

@ -1 +1 @@
METEOR@2.14 METEOR@2.13

View file

@ -1,6 +1,6 @@
accounts-base@2.2.10 accounts-base@2.2.8
accounts-oauth@1.4.3 accounts-oauth@1.4.2
accounts-password@2.4.0 accounts-password@2.3.4
aldeed:collection2@2.10.0 aldeed:collection2@2.10.0
aldeed:collection2-core@1.2.0 aldeed:collection2-core@1.2.0
aldeed:schema-deny@1.1.0 aldeed:schema-deny@1.1.0
@ -10,16 +10,34 @@ allow-deny@1.1.1
arillo:flow-router-helpers@0.5.2 arillo:flow-router-helpers@0.5.2
audit-argument-checks@1.0.7 audit-argument-checks@1.0.7
autoupdate@1.8.0 autoupdate@1.8.0
babel-compiler@7.10.5 babel-compiler@7.10.4
babel-runtime@1.5.1 babel-runtime@1.5.1
base64@1.0.12 base64@1.0.12
binary-heap@1.0.11 binary-heap@1.0.11
blaze@2.7.1 blaze@2.7.1
blaze-tools@1.1.3 blaze-tools@1.1.3
boilerplate-generator@1.7.2 boilerplate-generator@1.7.1
caching-compiler@1.2.2 caching-compiler@1.2.2
caching-html-compiler@1.2.1 caching-html-compiler@1.2.1
callback-hook@1.5.1 callback-hook@1.5.1
cfs:access-point@0.1.49
cfs:base-package@0.0.30
cfs:collection@0.5.5
cfs:collection-filters@0.2.4
cfs:data-man@0.0.6
cfs:file@0.1.17
cfs:filesystem@0.1.2
cfs:gridfs@0.0.34
cfs:http-methods@0.0.32
cfs:http-publish@0.0.13
cfs:power-queue@0.9.11
cfs:reactive-list@0.0.9
cfs:reactive-property@0.0.4
cfs:standard-packages@0.5.10
cfs:storage-adapter@0.2.4
cfs:tempstore@0.1.6
cfs:upload-http@0.0.20
cfs:worker@0.1.5
check@1.3.2 check@1.3.2
coffeescript@2.7.0 coffeescript@2.7.0
coffeescript-compiler@2.4.1 coffeescript-compiler@2.4.1
@ -29,22 +47,23 @@ dburles:collection-helpers@1.1.0
ddp@1.4.1 ddp@1.4.1
ddp-client@2.6.1 ddp-client@2.6.1
ddp-common@1.4.0 ddp-common@1.4.0
ddp-rate-limiter@1.2.1 ddp-rate-limiter@1.2.0
ddp-server@2.7.0 ddp-server@2.6.2
deps@1.0.12 deps@1.0.12
diff-sequence@1.1.2 diff-sequence@1.1.2
dynamic-import@0.7.3 dynamic-import@0.7.3
easy:search@2.2.1 easy:search@2.2.1
easysearch:components@2.2.2 easysearch:components@2.2.2
easysearch:core@2.2.2 easysearch:core@2.2.2
ecmascript@0.16.8 ecmascript@0.16.7
ecmascript-runtime@0.8.1 ecmascript-runtime@0.8.1
ecmascript-runtime-client@0.12.1 ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0 ecmascript-runtime-server@0.11.0
ejson@1.1.3 ejson@1.1.3
email@2.2.5 email@2.2.5
es5-shim@4.8.0 es5-shim@4.8.0
fetch@0.1.4 fetch@0.1.3
fortawesome:fontawesome@4.7.0
geojson-utils@1.0.11 geojson-utils@1.0.11
hot-code-push@1.0.4 hot-code-push@1.0.4
html-tools@1.1.3 html-tools@1.1.3
@ -58,12 +77,13 @@ kadira:blaze-layout@2.3.0
kadira:dochead@1.5.0 kadira:dochead@1.5.0
kadira:flow-router@2.12.1 kadira:flow-router@2.12.1
konecty:mongo-counter@0.0.5_3 konecty:mongo-counter@0.0.5_3
livedata@1.0.18
lmieulet:meteor-coverage@1.1.4 lmieulet:meteor-coverage@1.1.4
localstorage@1.2.0 localstorage@1.2.0
logging@1.3.3 logging@1.3.2
matb33:collection-hooks@1.3.0 matb33:collection-hooks@1.2.2
mdg:validation-error@0.5.1 mdg:validation-error@0.5.1
meteor@1.11.5 meteor@1.11.3
meteor-autosize@5.0.1 meteor-autosize@5.0.1
meteor-base@1.5.1 meteor-base@1.5.1
meteorhacks:aggregate@1.3.0 meteorhacks:aggregate@1.3.0
@ -77,11 +97,11 @@ minifier-css@1.6.4
minifier-js@2.7.5 minifier-js@2.7.5
minifiers@1.1.8-faster-rebuild.0 minifiers@1.1.8-faster-rebuild.0
minimongo@1.9.3 minimongo@1.9.3
modern-browsers@0.1.10 modern-browsers@0.1.9
modules@0.20.0 modules@0.19.0
modules-runtime@0.13.1 modules-runtime@0.13.1
momentjs:moment@2.29.3 momentjs:moment@2.29.3
mongo@1.16.8 mongo@1.16.7
mongo-decimal@0.1.3 mongo-decimal@0.1.3
mongo-dev-server@1.1.0 mongo-dev-server@1.1.0
mongo-id@1.0.8 mongo-id@1.0.8
@ -94,8 +114,8 @@ mquandalle:jade-compiler@0.4.5
mquandalle:jquery-textcomplete@0.8.0_1 mquandalle:jquery-textcomplete@0.8.0_1
mquandalle:mousetrap-bindglobal@0.0.1 mquandalle:mousetrap-bindglobal@0.0.1
msavin:usercache@1.8.0 msavin:usercache@1.8.0
npm-mongo@4.17.2 npm-mongo@4.16.0
oauth@2.2.1 oauth@2.2.0
oauth2@1.3.2 oauth2@1.3.2
observe-sequence@1.0.21 observe-sequence@1.0.21
ongoworks:speakingurl@1.1.0 ongoworks:speakingurl@1.1.0
@ -111,19 +131,100 @@ peerlibrary:blaze-components@0.23.0
peerlibrary:computed-field@0.10.0 peerlibrary:computed-field@0.10.0
peerlibrary:data-lookup@0.3.0 peerlibrary:data-lookup@0.3.0
peerlibrary:reactive-field@0.6.0 peerlibrary:reactive-field@0.6.0
percolate:synced-cron@1.5.2 percolate:synced-cron@1.3.2
promise@0.12.2 promise@0.12.2
raix:eventemitter@0.1.3 raix:eventemitter@0.1.3
raix:handlebar-helpers@0.2.5 raix:handlebar-helpers@0.2.5
rajit:bootstrap3-datepicker@1.7.1_1
rajit:bootstrap3-datepicker-ar@1.7.1
rajit:bootstrap3-datepicker-ar-tn@1.7.1
rajit:bootstrap3-datepicker-az@1.7.1
rajit:bootstrap3-datepicker-bg@1.7.1
rajit:bootstrap3-datepicker-bn@1.7.1
rajit:bootstrap3-datepicker-br@1.7.1
rajit:bootstrap3-datepicker-bs@1.7.1
rajit:bootstrap3-datepicker-ca@1.7.1
rajit:bootstrap3-datepicker-cs@1.7.1
rajit:bootstrap3-datepicker-cy@1.7.1
rajit:bootstrap3-datepicker-da@1.7.1
rajit:bootstrap3-datepicker-de@1.7.1
rajit:bootstrap3-datepicker-el@1.7.1
rajit:bootstrap3-datepicker-en-au@1.7.1
rajit:bootstrap3-datepicker-en-ca@1.7.1
rajit:bootstrap3-datepicker-en-gb@1.7.1
rajit:bootstrap3-datepicker-en-ie@1.7.1
rajit:bootstrap3-datepicker-en-nz@1.7.1
rajit:bootstrap3-datepicker-en-za@1.7.1
rajit:bootstrap3-datepicker-eo@1.7.1
rajit:bootstrap3-datepicker-es@1.7.1
rajit:bootstrap3-datepicker-et@1.7.1
rajit:bootstrap3-datepicker-eu@1.7.1
rajit:bootstrap3-datepicker-fa@1.7.1
rajit:bootstrap3-datepicker-fi@1.7.1
rajit:bootstrap3-datepicker-fo@1.7.1
rajit:bootstrap3-datepicker-fr@1.7.1
rajit:bootstrap3-datepicker-fr-ch@1.7.1
rajit:bootstrap3-datepicker-gl@1.7.1
rajit:bootstrap3-datepicker-he@1.7.1
rajit:bootstrap3-datepicker-hi@1.7.1
rajit:bootstrap3-datepicker-hr@1.7.1
rajit:bootstrap3-datepicker-hu@1.7.1
rajit:bootstrap3-datepicker-hy@1.7.1
rajit:bootstrap3-datepicker-id@1.7.1
rajit:bootstrap3-datepicker-is@1.7.1
rajit:bootstrap3-datepicker-it@1.7.1
rajit:bootstrap3-datepicker-it-ch@1.7.1
rajit:bootstrap3-datepicker-ja@1.7.1
rajit:bootstrap3-datepicker-ka@1.7.1
rajit:bootstrap3-datepicker-kh@1.7.1
rajit:bootstrap3-datepicker-kk@1.7.1
rajit:bootstrap3-datepicker-km@1.7.1
rajit:bootstrap3-datepicker-ko@1.7.1
rajit:bootstrap3-datepicker-kr@1.7.1
rajit:bootstrap3-datepicker-lv@1.7.1
rajit:bootstrap3-datepicker-me@1.7.1
rajit:bootstrap3-datepicker-mk@1.7.1
rajit:bootstrap3-datepicker-mn@1.7.1
rajit:bootstrap3-datepicker-ms@1.7.1
rajit:bootstrap3-datepicker-nb@1.7.1
rajit:bootstrap3-datepicker-nl@1.7.1
rajit:bootstrap3-datepicker-nl-be@1.7.1
rajit:bootstrap3-datepicker-no@1.7.1
rajit:bootstrap3-datepicker-oc@1.7.1
rajit:bootstrap3-datepicker-pl@1.7.1
rajit:bootstrap3-datepicker-pt@1.7.1
rajit:bootstrap3-datepicker-pt-br@1.7.1
rajit:bootstrap3-datepicker-ro@1.7.1
rajit:bootstrap3-datepicker-rs@1.7.1
rajit:bootstrap3-datepicker-rs-latin@1.7.1
rajit:bootstrap3-datepicker-ru@1.7.1
rajit:bootstrap3-datepicker-sk@1.7.1
rajit:bootstrap3-datepicker-sl@1.7.1
rajit:bootstrap3-datepicker-sq@1.7.1
rajit:bootstrap3-datepicker-sr@1.7.1
rajit:bootstrap3-datepicker-sr-latin@1.7.1
rajit:bootstrap3-datepicker-sv@1.7.1
rajit:bootstrap3-datepicker-sw@1.7.1
rajit:bootstrap3-datepicker-ta@1.7.1
rajit:bootstrap3-datepicker-tg@1.7.1
rajit:bootstrap3-datepicker-th@1.7.1
rajit:bootstrap3-datepicker-tr@1.7.1
rajit:bootstrap3-datepicker-uk@1.7.1
rajit:bootstrap3-datepicker-uz-cyrl@1.7.1
rajit:bootstrap3-datepicker-uz-latn@1.7.1
rajit:bootstrap3-datepicker-vi@1.7.1
rajit:bootstrap3-datepicker-zh-cn@1.7.1
rajit:bootstrap3-datepicker-zh-tw@1.7.1
random@1.2.1 random@1.2.1
rate-limit@1.1.1 rate-limit@1.1.1
react-fast-refresh@0.2.8 react-fast-refresh@0.2.7
reactive-dict@1.3.1 reactive-dict@1.3.1
reactive-var@1.0.12 reactive-var@1.0.12
reload@1.3.1 reload@1.3.1
retry@1.1.0 retry@1.1.0
routepolicy@1.1.1 routepolicy@1.1.1
service-configuration@1.3.3 rzymek:fullcalendar@3.8.0
service-configuration@1.3.1
session@1.2.1 session@1.2.1
sha@1.0.9 sha@1.0.9
shell-server@0.5.0 shell-server@0.5.0
@ -132,7 +233,7 @@ simple:json-routes@2.3.1
simple:rest-accounts-password@1.2.2 simple:rest-accounts-password@1.2.2
simple:rest-bearer-token-parser@1.1.1 simple:rest-bearer-token-parser@1.1.1
simple:rest-json-error-handler@1.1.1 simple:rest-json-error-handler@1.1.1
socket-stream-client@0.5.2 socket-stream-client@0.5.1
spacebars@1.4.1 spacebars@1.4.1
spacebars-compiler@1.3.1 spacebars-compiler@1.3.1
standard-minifier-js@2.8.1 standard-minifier-js@2.8.1
@ -141,26 +242,22 @@ templating@1.4.1
templating-compiler@1.4.1 templating-compiler@1.4.1
templating-runtime@1.5.0 templating-runtime@1.5.0
templating-tools@1.2.2 templating-tools@1.2.2
tracker@1.3.3 tracker@1.3.2
typescript@4.9.5
ui@1.0.13 ui@1.0.13
underscore@1.0.13 underscore@1.0.13
url@1.3.2 url@1.3.2
useraccounts:core@1.16.2 useraccounts:core@1.16.2
useraccounts:flow-routing@1.15.0 useraccounts:flow-routing@1.15.0
useraccounts:unstyled@1.14.2 useraccounts:unstyled@1.14.2
webapp@1.13.6 webapp@1.13.5
webapp-hashing@1.1.1 webapp-hashing@1.1.1
wekan-accounts-cas@0.1.0 wekan-accounts-cas@0.1.0
wekan-accounts-lockout@1.0.0 wekan-accounts-lockout@1.0.0
wekan-accounts-oidc@1.0.10 wekan-accounts-oidc@1.0.10
wekan-accounts-sandstorm@0.8.0 wekan-accounts-sandstorm@0.8.0
wekan-bootstrap-datepicker@1.10.0
wekan-fontawesome@6.4.2
wekan-fullcalendar@3.10.5
wekan-ldap@0.0.2 wekan-ldap@0.0.2
wekan-markdown@1.0.9 wekan-markdown@1.0.9
wekan-oidc@1.0.12 wekan-oidc@1.0.12
yasaricli:slugify@0.0.7 yasaricli:slugify@0.0.7
zimme:active-route@2.3.2 zimme:active-route@2.3.2
zodern:types@1.0.10 zodern:types@1.0.9

View file

@ -1,6 +1,6 @@
[main] [main]
host = https://www.transifex.com host = https://www.transifex.com
lang_map = te_IN: te-IN, es_AR: es-AR, es_419: es-LA, es_TX: es-TX, he_IL: he-IL, zh_CN: zh-CN, ar_EG: ar-EG, cs_CZ: cs-CZ, fa_IR: fa-IR, ms_MY: ms-MY, nl_NL: nl-NL, de_CH: de-CH, en_IT: en-IT, uz_UZ: uz-UZ, fr_CH: fr-CH, hi_IN: hi-IN, et_EE: et-EE, es_PE: es-PE, es_MX: es-MX, gl_ES: gl-ES, mn_MN: mn, sl_SI: sl, zh_TW: zh-TW, ast_ES: ast-ES, es_CL: es-CL, ja_JP: ja, lv_LV: lv, ro_RO: ro-RO, az_AZ: az-AZ, cy_GB: cy-GB, gu_IN: gu-IN, pl_PL: pl-PL, vep: ve-PP, en_BR: en-BR, en@ysv: en-YS, hu_HU: hu, ko_KR: ko-KR, pt_BR: pt-BR, zh_HK: zh-HK, zu_ZA: zu-ZA, en_MY: en-MY, ja-Hira: ja-HI, fi_FI: fi, vec: ve-CC, vi_VN: vi-VN, fr_FR: fr-FR, id_ID: id, zh_Hans: zh-Hans, en_DE: en-DE, en_GB: en-GB, el_GR: el-GR, uk_UA: uk-UA, az@latin: az-LA, de_AT: de-AT, uz@Latn: uz-LA, vls: vl-SS, ar_DZ: ar-DZ, bg_BG: bg, es_PY: es-PY, fy_NL: fy-NL, uz@Arab: uz-AR, ru_UA: ru-UA, war: wa-RR, zh_CN.GB2312: zh-GB lang_map = es_AR: es-AR, es_419: es-LA, es_TX: es-TX, he_IL: he-IL, zh_CN: zh-CN, ar_EG: ar-EG, cs_CZ: cs-CZ, fa_IR: fa-IR, ms_MY: ms-MY, nl_NL: nl-NL, de_CH: de-CH, en_IT: en-IT, uz_UZ: uz-UZ, fr_CH: fr-CH, hi_IN: hi-IN, et_EE: et-EE, es_PE: es-PE, es_MX: es-MX, gl_ES: gl-ES, mn_MN: mn, sl_SI: sl, zh_TW: zh-TW, ast_ES: ast-ES, es_CL: es-CL, ja_JP: ja, lv_LV: lv, ro_RO: ro-RO, az_AZ: az-AZ, cy_GB: cy-GB, gu_IN: gu-IN, pl_PL: pl-PL, vep: ve-PP, en_BR: en-BR, en@ysv: en-YS, hu_HU: hu, ko_KR: ko-KR, pt_BR: pt-BR, zh_HK: zh-HK, zu_ZA: zu-ZA, en_MY: en-MY, ja-Hira: ja-HI, fi_FI: fi, vec: ve-CC, vi_VN: vi-VN, fr_FR: fr-FR, id_ID: id, zh_Hans: zh-Hans, en_DE: en-DE, en_GB: en-GB, el_GR: el-GR, uk_UA: uk-UA, az@latin: az-LA, de_AT: de-AT, uz@Latn: uz-LA, vls: vl-SS, ar_DZ: ar-DZ, bg_BG: bg, es_PY: es-PY, fy_NL: fy-NL, uz@Arab: uz-AR, ru_UA: ru-UA, war: wa-RR, zh_CN.GB2312: zh-GB
[o:wekan:p:wekan:r:application] [o:wekan:p:wekan:r:application]
file_filter = imports/i18n/data/<lang>.i18n.json file_filter = imports/i18n/data/<lang>.i18n.json

84
.vscode/launch.json vendored
View file

@ -1,57 +1,45 @@
{ {
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"type": "node", "type": "chrome",
"request": "launch", "request": "launch",
"name": "Meteor: Node", "name": "Meteor: Chrome",
"runtimeExecutable": "meteor", "url": "http://localhost:3000",
"runtimeArgs": [ "webRoot": "${workspaceFolder}"
"--port=4000",
"--exclude-archs=web.browser.legacy,web.cordova",
"--raw-logs"
],
"env": {
"WRITABLE_PATH": "/tmp/uploads",
}, },
"outputCapture": "std", {
"restart": true, "type": "node",
"timeout": 60000 "request": "launch",
}, "name": "Meteor: Node",
{ "runtimeExecutable": "/home/wekan/.meteor/meteor",
"type": "chrome", "runtimeArgs": ["run", "--inspect-brk=9229"],
"request": "launch", "outputCapture": "std",
"name": "Meteor: Chrome", "port": 9229,
"url": "http://localhost:4000", "timeout": 60000
"sourceMapPathOverrides": {
"meteor://💻app/*": "${workspaceFolder}/*"
}, },
"userDataDir": "${env:HOME}/.vscode/chrome" {
}, "type": "node",
{ "request": "launch",
"type": "node", "name": "Test: Node",
"request": "launch", "runtimeExecutable": "meteor",
"name": "Test: Node", "runtimeArgs": [
"runtimeExecutable": "meteor", "test",
"runtimeArgs": [ "--inspect-brk=9229",
"test", "--port=4040",
"--port=4040", "--exclude-archs=web.browser.legacy,web.cordova",
"--exclude-archs=web.browser.legacy,web.cordova", "--driver-package=meteortesting:mocha",
"--driver-package=meteortesting:mocha", "--settings=settings.json"
"--settings=settings.json", ],
"--raw-logs" "outputCapture": "std",
], "port": 9229,
"env": { "timeout": 60000
"TEST_WATCH": "1" }
},
"outputCapture": "std",
"timeout": 60000
}
], ],
"compounds": [ "compounds": [
{ {
"name": "Meteor: All", "name": "Meteor: All",
"configurations": ["Meteor: Node", "Meteor: Chrome"] "configurations": ["Meteor: Node", "Meteor: Chrome"]
} }
] ]
} }

File diff suppressed because it is too large Load diff

View file

@ -1,22 +0,0 @@
# Code of Conduct
For all code at WeKan GitHub Organization https://github.com/wekan
- All code in pull requests need to have permission already to add it to WeKan with MIT license, and will become MIT license.
- All code xet7 add is MIT license.
- For any dependencies, permissive licenses like https://copyfree.org are preferred
- For anything currently that is non-permissive (like GPL, AGPL, SSPL), those will be replaced with permissive-licensed alternatives
# Reporting about violations or something else
## Private reports
- Email support@wekan.team
- Security issues: [SECURITY.md](SECURITY.md)
- License violations
- Anything private, sensitive or negative
## Public
- Feature Requests and Bug Reports https://github.com/wekan/wekan/issues
- Anything happy, positive, encouraging, helping, at friendly WeKan Global FOSS Community

View file

@ -1,35 +1,19 @@
## About money
Not paid:
- Money is not paid for these, everyone uses their own time at their own cost:
- Security reports, see [SECURITY.md](SECURITY.md)
- Pull requests
- xet7 checking pull requests
- Public Community Support
- https://github.com/wekan/wekan/issues
Paid by customers of WeKan Team:
- Commercial Support at https://wekan.team/commercial-support/
- Support
- Private Chat
- Features
- Fixes
- Hosting
## Contributing Security related ## Contributing Security related
For responsible security disclosure, please follow this process: For responsible security disclosure, please follow this process:
https://github.com/wekan/wekan/blob/main/SECURITY.md https://github.com/wekan/wekan/blob/master/SECURITY.md
CVE Hall of Fame is at https://wekan.github.io/hall-of-fame/ CVE Hall of Fame is at https://wekan.github.io/hall-of-fame/
## Contributing to Documentation Wiki ## Contributing to Documentation Wiki
Fork WeKan repo https://github.com/wekan/wekan , Please clone wiki:
edit `docs` directory content at GitHub web interface, ```
and click send PR. git clone https://github.com/wekan/wekan.wiki
```
Edit .md files, and add changed files in .zip attachment
directly to comment of new issue at
https://github.com/wekan/wekan/issues
## Contributing code ## Contributing code
@ -38,7 +22,7 @@ and click send PR.
WeKan code contributors Hall of Fame is at ChangeLog, where WeKan code contributors Hall of Fame is at ChangeLog, where
GitHub usernames are mentioned with changes added: GitHub usernames are mentioned with changes added:
https://github.com/wekan/wekan/blob/main/CHANGELOG.md https://github.com/wekan/wekan/blob/master/CHANGELOG.md
Changes can be like typo fixes, bugfixes, features, or anything else Changes can be like typo fixes, bugfixes, features, or anything else
like for example at open GitHub issues https://github.com/wekan/wekan/issues . like for example at open GitHub issues https://github.com/wekan/wekan/issues .
@ -58,7 +42,7 @@ About 300 persons have contributed to WeKan, stats at:
https://www.openhub.net/p/wekan https://www.openhub.net/p/wekan
WeKan maintainer xet7 reviews PR for typos etc before accepting to WeKan, WeKan maintainer xet7 checks PR for typos etc before accepting to WeKan,
so that WeKan code will still work OK. so that WeKan code will still work OK.
## Contributing translations ## Contributing translations
@ -69,7 +53,7 @@ https://transifex.com/wekan/wekan
When adding new features, in your PR to When adding new features, in your PR to
https://github.com/wekan/wekan/pulls https://github.com/wekan/wekan/pulls
only add new English source language strings only add new English source language strings
to https://github.com/wekan/wekan/blob/main/imports/i18n/data/en.i18n.json to https://github.com/wekan/wekan/blob/master/imports/i18n/data/en.i18n.json
Maintainer of WeKan xet7 downloads all newest Maintainer of WeKan xet7 downloads all newest
translations from Transifex and adds translations from Transifex and adds
@ -78,10 +62,12 @@ new release.
## About WeKan Organization https://github.com/wekan ## About WeKan Organization https://github.com/wekan
Only xet7 has write access to WeKan Organization. xet7 rarely adds any new members to GitHub Organization,
because xet7 prefers to check PRs.
xet7 reviews all PRs before merging. For some repos (other than https://github.com/wekan/wekan ),
some contributors have direct commit access.
There has been over 300 contributors to WeKan, newest stats at: Some contributors are mentioned at this outdated page:
https://www.openhub.net/p/wekan https://github.com/wekan/wekan/wiki/Team

View file

@ -1,8 +1,10 @@
FROM ubuntu:24.04 FROM --platform=linux/amd64 ubuntu:23.04 as wekan
LABEL maintainer="wekan" LABEL maintainer="wekan"
LABEL org.opencontainers.image.ref.name="ubuntu"
LABEL org.opencontainers.image.version="24.04" # 2022-09-04:
LABEL org.opencontainers.image.source="https://github.com/wekan/wekan" # - above "--platform=linux/amd64 ubuntu:22.04 as wekan" is needed to build Dockerfile
# correctly on Mac M1 etc, to not get this error:
# https://stackoverflow.com/questions/71040681/qemu-x86-64-could-not-open-lib64-ld-linux-x86-64-so-2-no-such-file-or-direc
# 2022-04-25: # 2022-04-25:
# - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling, # - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling,
@ -11,23 +13,24 @@ LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
# 2021-09-18: # 2021-09-18:
# - Above Ubuntu base image copied from Docker Hub ubuntu:hirsute-20210825 # - 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)
# DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
# ENV BUILD_DEPS="paxctl"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ENV BUILD_DEPS="apt-utils gnupg gosu wget bzip2 g++ curl libarchive-tools 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" \
ENV \
DEBUG=false \ DEBUG=false \
NODE_VERSION=v14.21.4 \ NODE_VERSION=v14.21.4 \
METEOR_RELEASE=METEOR@2.14 \ METEOR_RELEASE=METEOR@2.13 \
USE_EDGE=false \ USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \ METEOR_EDGE=1.5-beta.17 \
NPM_VERSION=6.14.17 \ NPM_VERSION=latest \
FIBERS_VERSION=4.0.1 \ FIBERS_VERSION=4.0.1 \
ARCHITECTURE=linux-x64 \ ARCHITECTURE=linux-x64 \
SRC_PATH=./ \ SRC_PATH=./ \
WITH_API=true \ WITH_API=true \
RESULTS_PER_PAGE="" \ RESULTS_PER_PAGE="" \
DEFAULT_BOARD_ID="" \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \ ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \ ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \ ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 \
@ -62,7 +65,6 @@ ENV \
OIDC_REDIRECTION_ENABLED=false \ OIDC_REDIRECTION_ENABLED=false \
OAUTH2_CA_CERT="" \ OAUTH2_CA_CERT="" \
OAUTH2_ADFS_ENABLED=false \ OAUTH2_ADFS_ENABLED=false \
OAUTH2_B2C_ENABLED=false \
OAUTH2_LOGIN_STYLE=redirect \ OAUTH2_LOGIN_STYLE=redirect \
OAUTH2_CLIENT_ID="" \ OAUTH2_CLIENT_ID="" \
OAUTH2_SECRET="" \ OAUTH2_SECRET="" \
@ -158,7 +160,7 @@ ENV \
WRITABLE_PATH=/data \ WRITABLE_PATH=/data \
S3="" S3=""
# NODE_OPTIONS="--max_old_space_size=4096" # NODE_OPTIONS="--max_old_space_size=4096" \
#--------------------------------------------- #---------------------------------------------
# == at docker-compose.yml: AUTOLOGIN WITH OIDC/OAUTH2 ==== # == at docker-compose.yml: AUTOLOGIN WITH OIDC/OAUTH2 ====
@ -169,98 +171,99 @@ ENV \
# Copy the app to the image # Copy the app to the image
COPY ${SRC_PATH} /home/wekan/app COPY ${SRC_PATH} /home/wekan/app
# Install OS RUN \
RUN <<EOR set -o xtrace && \
set -o xtrace # Add non-root user wekan
useradd --user-group --system --home-dir /home/wekan wekan && \
# Add non-root user wekan \
useradd --user-group --system --home-dir /home/wekan wekan # OS dependencies
# OS dependencies apt-get update -y && apt-get install -y --no-install-recommends ${BUILD_DEPS} && \
apt-get update --assume-yes \
apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS} # Meteor installer doesn't work with the default tar binary, so using bsdtar while installing.
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
# Meteor installer doesn't work with the default tar binary, so using bsdtar while installing. cp $(which tar) $(which tar)~ && \
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389 ln -sf $(which bsdtar) $(which tar) && \
cp $(which tar) $(which tar)~ \
ln -sf $(which bsdtar) $(which tar) # Download nodejs
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
# Install NodeJS wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt && \
cd /tmp #wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
# Download nodejs #---------------------------------------------------------------------------------------------
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz" \
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt" # Verify nodejs authenticity
grep node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz SHASUMS256.txt | shasum -a 256 -c - && \
# Verify nodejs authenticity rm -f SHASUMS256.txt && \
grep "node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz" "SHASUMS256.txt" | shasum -a 256 -c - #grep ${NODE_VERSION}-${ARCHITECTURE}.tar.gz SHASUMS256.txt.asc | shasum -a 256 -c - && \
rm -f "SHASUMS256.txt" #rm -f SHASUMS256.txt.asc && \
\
# Install Node # Install Node
tar xzf "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" -C /usr/local --strip-components=1 --no-same-owner tar xvzf node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
rm "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" "SHASUMS256.txt" rm node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
ln -s "/usr/local/bin/node" "/usr/local/bin/nodejs" mv node-${NODE_VERSION}-${ARCHITECTURE} /opt/nodejs && \
mkdir -p "/opt/nodejs/lib/node_modules/fibers/.node-gyp" "/root/.node-gyp/${NODE_VERSION} /home/wekan/.config" ln -s /opt/nodejs/bin/node /usr/bin/node && \
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
# Install node dependencies mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/${NODE_VERSION} /home/wekan/.config && \
npm install -g npm@${NPM_VERSION} --production chown wekan --recursive /home/wekan/.config && \
chown --recursive wekan:wekan /home/wekan/.config \
#DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
# Install Meteor #paxctl -mC `which node` && \
cd /home/wekan \
chown --recursive wekan:wekan /home/wekan # Install Node dependencies. Python path for node-gyp.
echo "Starting meteor ${METEOR_RELEASE} installation... \n" npm install -g npm@${NPM_VERSION} && \
gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh \
mv /root/.meteor /home/wekan/ # Change user to wekan and install meteor
chown --recursive wekan:wekan /home/wekan/.meteor cd /home/wekan/ && \
chown wekan --recursive /home/wekan && \
sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js echo "Starting meteor ${METEOR_RELEASE} installation... \n" && \
cd /home/wekan/.meteor gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh && \
gosu wekan:wekan /home/wekan/.meteor/meteor -- help mv /root/.meteor /home/wekan/ && \
chown wekan --recursive /home/wekan/.meteor && \
# Build app (Production) \
cd /home/wekan/app sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js && \
mkdir -p /home/wekan/.npm cd /home/wekan/.meteor && \
chown --recursive wekan:wekan /home/wekan/.npm gosu wekan:wekan /home/wekan/.meteor/meteor -- help; \
chmod u+w *.json \
gosu wekan:wekan meteor npm install --production # Build app
gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build cd /home/wekan/app && \
cd /home/wekan/app_build/bundle/programs/server/ mkdir -p /home/wekan/.npm && \
chmod u+w *.json chown wekan --recursive /home/wekan/.npm /home/wekan/.config /home/wekan/.meteor && \
gosu wekan:wekan meteor npm install --production chmod u+w *.json && \
cd node_modules/fibers gosu wekan:wekan npm install && \
node build.js gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build && \
cd ../.. cd /home/wekan/app_build/bundle/programs/server/ && \
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc. chmod u+w *.json && \
rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy gosu wekan:wekan npm install && \
mv /home/wekan/app_build/bundle /build cd node_modules/fibers && \
node build.js && \
# Put back the original tar cd ../.. && \
mv $(which tar)~ $(which tar) # Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy && \
# Cleanup mv /home/wekan/app_build/bundle /build && \
apt-get remove --purge --assume-yes ${BUILD_DEPS} \
npm uninstall -g api2html # Put back the original tar
apt-get autoremove --assume-yes mv $(which tar)~ $(which tar) && \
apt-get clean --assume-yes \
rm -Rf /tmp/* # Cleanup
rm -Rf /var/lib/apt/lists/* apt-get remove --purge -y ${BUILD_DEPS} && \
rm -Rf /var/cache/apt apt-get autoremove -y && \
rm -Rf /var/lib/apt/lists npm uninstall -g api2html &&\
rm -Rf /home/wekan/app_build rm -R /tmp/* && \
rm -Rf /home/wekan/app rm -R /var/lib/apt/lists/* && \
rm -Rf /home/wekan/.meteor rm -R /home/wekan/.meteor && \
rm -R /home/wekan/app && \
mkdir /data rm -R /home/wekan/app_build && \
chown wekan --recursive /data mkdir /data && \
EOR chown wekan --recursive /data
#cat /home/wekan/python/esprima-python/files.txt | xargs rm -R && \
USER wekan #rm -R /home/wekan/python
#rm /home/wekan/install_meteor.sh
ENV PORT=8080 ENV PORT=8080
EXPOSE $PORT EXPOSE $PORT
USER wekan
STOPSIGNAL SIGKILL STOPSIGNAL SIGKILL
WORKDIR /home/wekan/app
#--------------------------------------------------------------------- #---------------------------------------------------------------------
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132 # https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
@ -271,6 +274,6 @@ WORKDIR /home/wekan/app
#--------------------------------------------------------------------- #---------------------------------------------------------------------
# #
# CMD ["node", "/build/main.js"] # CMD ["node", "/build/main.js"]
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /build/main.js"]
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 --max-old-space-size=8192 /build/main.js"] #CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /build/main.js"]
CMD ["bash", "-c", "ulimit -s 65500; exec node /build/main.js"] CMD ["bash", "-c", "ulimit -s 65500; exec node /build/main.js"]

View file

@ -1,18 +1,24 @@
FROM arm64v8/ubuntu:23.04 AS builder FROM amd64/alpine:3.7 AS builder
#FROM amd64/alpine:latest AS builder
# Set the environment variables for builder # Set the environment variables for builder
ENV QEMU_VERSION=v7.2.0-1 \ ENV QEMU_VERSION=v4.2.0-6 \
QEMU_ARCHITECTURE=aarch64 \ QEMU_ARCHITECTURE=aarch64 \
NODE_ARCHITECTURE=linux-arm64 \ NODE_ARCHITECTURE=linux-arm64 \
NODE_VERSION=v14.21.4 \ NODE_VERSION=v14.21.4 \
WEKAN_VERSION=latest \ WEKAN_VERSION=latest \
WEKAN_ARCHITECTURE=arm64 WEKAN_ARCHITECTURE=arm64 \
NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------------------------------
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
# Add more Node heap:
# NODE_OPTIONS="--max_old_space_size=4096"
# Add more stack:
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
#---------------------------------------------------------------------
# Install dependencies # Install dependencies
#RUN apk update && apk add ca-certificates outils-sha1 && \ RUN apk update && apk add ca-certificates outils-sha1 && \
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
RUN apt update && apt install ca-certificates wget unzip -y && \
\ \
# Download qemu static for our architecture # Download qemu static for our architecture
wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \ wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \
@ -27,27 +33,25 @@ RUN apt update && apt install ca-certificates wget unzip -y && \
unzip wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \ unzip wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
\ \
# Download node and shasums # Download node and shasums
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt && \
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \ #wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \ #wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \ #wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \ #wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
\ \
# Verify nodejs authenticity # Verify nodejs authenticity
grep node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz SHASUMS256.txt | sha256sum -c - && \ grep node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz SHASUMS256.txt.asc | sha256sum -c - && \
\ \
# Extract node and remove tar.gz # Extract node and remove tar.gz
tar xvzf node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz tar xvzf node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz
# Build wekan dockerfile # Build wekan dockerfile
FROM --platform=linux/arm64 arm64v8/ubuntu:23.04 FROM arm64v8/ubuntu:19.10
LABEL maintainer="wekan" 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=v14.21.4 \ NODE_VERSION=v14.21.3 \
NODE_ENV=production \ NODE_ENV=production \
NPM_VERSION=latest \ NPM_VERSION=latest \
WITH_API=true \ WITH_API=true \
@ -73,21 +77,30 @@ RUN \
ln -s /opt/nodejs/bin/node /usr/bin/node && \ ln -s /opt/nodejs/bin/node /usr/bin/node && \
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \ ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \ mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \
chown wekan --recursive /home/wekan/.config chown wekan --recursive /home/wekan/.config && \
\
# Install Node dependencies
npm install -g npm@${NPM_VERSION} && \
\
# Install Health Check dependencies
apk add curl
# \ HEALTHCHECK --start-period=30s --interval=30s --timeout=10s --retries=3 \
# # Install Node dependencies CMD curl --fail "http://localhost:$PORT" || exit 1
# #npm install -g npm@${NPM_VERSION} && \
# \
# # Install Health Check dependencies
# #apk add curl
#
#HEALTHCHECK --start-period=30s --interval=30s --timeout=10s --retries=3 \
# CMD curl --fail "http://localhost:$PORT" || exit 1
EXPOSE $PORT EXPOSE $PORT
USER wekan USER wekan
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 --max-old-space-size=8192 /home/wekan/bundle/main.js"] #---------------------------------------------------------------------
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
# Add more Node heap:
# NODE_OPTIONS="--max_old_space_size=4096"
# Add more stack:
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
#---------------------------------------------------------------------
#
#CMD ["node", "/home/wekan/bundle/main.js"]
#CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /home/wekan/bundle/main.js"]
CMD ["bash", "-c", "ulimit -s 65500; exec node /home/wekan/bundle/main.js"] CMD ["bash", "-c", "ulimit -s 65500; exec node /home/wekan/bundle/main.js"]

View file

@ -1,94 +0,0 @@
FROM arm64v8/ubuntu:23.04 AS builder
#FROM --platform=linux/amd64 amd64/ubuntu:23.04 AS builder
#FROM --platform=linux/amd64 ghcr.io/wekan/wekan:main AS builder
#FROM arm64v8/ubuntu:23.04 AS builder
#FROM amd64/alpine:latest AS builder
# Set the environment variables for builder
ENV QEMU_VERSION=v7.2.0-1 \
QEMU_ARCHITECTURE=s390x \
NODE_ARCHITECTURE=linux-s390x \
NODE_VERSION=v14.21.4 \
WEKAN_VERSION=latest \
WEKAN_ARCHITECTURE=s390x
# Install dependencies
#RUN apk update && apk add ca-certificates outils-sha1 && \
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
RUN apt update && apt install ca-certificates wget unzip -y && \
\
# Download qemu static for our architecture
wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \
\
# Download wekan and shasum
wget https://releases.wekan.team/${WEKAN_ARCHITECTURE}/wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
wget https://releases.wekan.team/${WEKAN_ARCHITECTURE}/SHA256SUMS.txt && \
# Verify wekan
grep wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip SHA256SUMS.txt | sha256sum -c - && \
\
# Unzip wekan
unzip wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
\
# Download node and shasums
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt && \
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
\
# Verify nodejs authenticity
grep node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz SHASUMS256.txt | sha256sum -c - && \
\
# Extract node and remove tar.gz
tar xvzf node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz
# Build wekan dockerfile
FROM --platform=linux/s390x s390x/ubuntu:23.04
LABEL maintainer="wekan"
# Set the environment variables (defaults where required)
ENV QEMU_ARCHITECTURE=s390x \
NODE_ARCHITECTURE=linux-s390x \
NODE_VERSION=v14.21.4 \
NODE_ENV=production \
NPM_VERSION=latest \
WITH_API=true \
PORT=8080 \
ROOT_URL=http://localhost \
MONGO_URL=mongodb://127.0.0.1:27017/wekan
# Copy qemu-static to image
COPY --from=builder qemu-${QEMU_ARCHITECTURE}-static /usr/bin
# Copy the app to the image
COPY --from=builder bundle /home/wekan/bundle
# Copy
COPY --from=builder node-${NODE_VERSION}-${NODE_ARCHITECTURE} /opt/nodejs
RUN \
set -o xtrace && \
# Add non-root user wekan
useradd --user-group --system --home-dir /home/wekan wekan && \
\
# Install Node
ln -s /opt/nodejs/bin/node /usr/bin/node && \
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \
chown wekan --recursive /home/wekan/.config
# \
# # Install Node dependencies
# #npm install -g npm@${NPM_VERSION} && \
# \
# # Install Health Check dependencies
# #apk add curl
#
#HEALTHCHECK --start-period=30s --interval=30s --timeout=10s --retries=3 \
# CMD curl --fail "http://localhost:$PORT" || exit 1
EXPOSE $PORT
USER wekan
CMD ["bash", "-c", "ulimit -s 65500; exec node /home/wekan/bundle/main.js"]

View file

@ -1,40 +0,0 @@
# Future
## Moved Import/Export/Sync issues to Big Picture Roadmap wiki page
This change is limited to only Import/Export/Sync issues, while those are In Progress of being fixed.
2023-11-21 xet7 closed 261 issues that are linked at https://github.com/wekan/wekan/wiki/Sync ,
that is Roadmap of Import/Export/Sync in WeKan. It means, that those issues progress will be
updated at that wiki page, when xet7 and other WeKan contributors fix those.
Many of those issues are In Progress of being fixed and added.
## Platform Updates
Issues related to platforms are being closed, because only list of working platforms is mentioned now
at WeKan website https://wekan.github.io Install section and at [ChangeLog](https://github.com/wekan/wekan/blob/main/CHANGELOG.md)
where is this new text:
> Newest WeKan at amd64 platforms: Linux bundle, Snap Candidate, Docker, Kubernetes. Fixing other platforms In Progress.
Platform support changes often, because:
- There are many dependencies, that update or break or change often
- Node.js segfaults at some CPU/OS
- Some platforms have build errors
Roadmap is to update all existing platforms, and add more platforms.
Upcoming platform upgrades:
- Fix migrations, so that newest WeKan can be released to Snap Stable. (Currently newest is at Snap Candidate).
## WeKan features
Most Meteor WeKan features are listed here:
https://github.com/wekan/wekan/wiki/Deep-Dive-Into-WeKan
Remaining features and all changes are listed here:
https://github.com/wekan/wekan/blob/main/CHANGELOG.md

View file

@ -1,10 +0,0 @@
# Governance
Anyone can send pull request to https://github.com/wekan/wekan/wiki/pulls ,
if there is permission to add code to WeKan with MIT license.
As maintainer, xet7 checks all pull requests and merges them.
Only xet7 has write access to repo https://github.com/wekan/wekan

View file

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014-2024 The Wekan Team Copyright (c) 2014-2019 The Wekan Team
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -2,34 +2,23 @@
# WeKan ® - Open Source kanban # WeKan ® - Open Source kanban
## Downloads
https://wekan.github.io / Install WeKan ® Server
## Docker Containers ## Docker Containers
- [GitHub](https://github.com/wekan/wekan/pkgs/container/wekan)
- [Quay](https://quay.io/repository/wekan/wekan) - [Quay](https://quay.io/repository/wekan/wekan)
- [Docker Hub](https://hub.docker.com/r/wekanteam/wekan) - [Docker Hub](https://hub.docker.com/r/wekanteam/wekan)
docker-compose.yml at https://github.com/wekan/wekan/blob/main/docker-compose.yml Other platforms and compatible software versions at https://wekan.github.io Download section.
## Standards
- [WeKan and Standard for Public Code](https://wekan.github.io/standard-for-public-code/) assessment was made at 2023-11.
Currently Wekan meets 8 out of 16 criteria out of the box.
Some others could be met with small changes.
## Code stats ## Code stats
- [CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4619)
- [Code Climate](https://codeclimate.com/github/wekan/wekan) - [Code Climate](https://codeclimate.com/github/wekan/wekan)
- [Open Hub](https://www.openhub.net/p/wekan) - [Open Hub](https://www.openhub.net/p/wekan)
- [OSS Insight](https://ossinsight.io/analyze/wekan/wekan) - [OSS Insight](https://ossinsight.io/analyze/wekan/wekan)
- [CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4619)
## [Translate WeKan ® at Transifex](https://app.transifex.com/wekan/) ## [Translate WeKan ® at Transifex](https://app.transifex.com/wekan/)
Translations to non-English languages are accepted only at [Transifex](https://app.transifex.com/wekan/wekan) using webbrowser. Translations to non-English languages are accepted only at [Transifex](https://app.transifex.com/wekan/) using webbrowser.
New English strings of new features can be added as PRs to master branch file wekan/imports/i18n/data/en.i18n.json . New English strings of new features can be added as PRs to master branch file wekan/imports/i18n/data/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)
@ -81,14 +70,14 @@ that by providing one-click installation on various platforms.
[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/main/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/main/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.
@ -100,21 +89,13 @@ that by providing one-click installation on various platforms.
[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/main/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.
## Getting Started with Development
The default branch uses [Meteor 2 with Node.js 14](https://wekan.github.io/install/).
To contribute, [create a fork](https://github.com/wekan/wekan/wiki/Emoji#2-create-fork-of-httpsgithubcomwekanwekan-at-github-web-page) and run `./rebuild-wekan.sh` (or `./rebuild-wekan.bat` on Windows) as detailed [here](https://github.com/wekan/wekan/wiki/Emoji#3-select-option-1-to-install-dependencies-and-then-enter). Once you're ready, please test your code and [submit a pull request (PR)](https://github.com/wekan/wekan/wiki/Emoji#7-test).
Please refer to the [developer documentation](https://github.com/wekan/wekan/wiki/Developer-Documentation) for more information.
## 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)
@ -128,7 +109,7 @@ with [Meteor](https://www.meteor.com).
[platforms]: https://github.com/wekan/wekan/wiki/Platforms [platforms]: https://github.com/wekan/wekan/wiki/Platforms
[dev_docs]: https://github.com/wekan/wekan/wiki/Developer-Documentation [dev_docs]: https://github.com/wekan/wekan/wiki/Developer-Documentation
[screenshot_wekan]: https://wekan.github.io/wekan-dark-mode.png [screenshot_wekan]: https://wekan.github.io/wekan-markdown.png
[features]: https://github.com/wekan/wekan/wiki/Features [features]: https://github.com/wekan/wekan/wiki/Features
[roadmap_wekan]: https://boards.wekan.team/b/D2SzJKZDS4Z48yeQH/wekan-open-source-kanban-board-with-mit-license [roadmap_wekan]: https://boards.wekan.team/b/D2SzJKZDS4Z48yeQH/wekan-open-source-kanban-board-with-mit-license
[wekan_issues]: https://github.com/wekan/wekan/issues [wekan_issues]: https://github.com/wekan/wekan/issues

View file

@ -1,113 +0,0 @@
# 🔐 WeKan — Login System Overview
This document provides a detailed overview of WeKans **login and authentication system**, covering client-side UI, server-side logic, external authentication methods, and potential upgrade paths.
---
## 🖥️ Login Web UI
WeKan's login interface is implemented using a combination of:
- `layouts.jade` Login HTML structure
- `layouts.js` Login logic and interactivity
- `layouts.css` Styling and layout
📁 Source: [`client/components/main`](https://github.com/wekan/wekan/tree/main/client/components/main)
---
## ⚙️ Server-Side Authentication
Server-side login functionality is handled in:
- [`server/authentication.js`](https://github.com/wekan/wekan/blob/main/server/authentication.js)
Other related configurations:
- 🔧 Account config: [`config/accounts.js`](https://github.com/wekan/wekan/blob/main/config/accounts.js)
- 📨 Sign-up invitations: [`models/settings.js#L275`](https://github.com/wekan/wekan/blob/main/models/settings.js#L275)
- 👤 User creation logic: [`models/users.js#L1339`](https://github.com/wekan/wekan/blob/main/models/users.js#L1339)
---
## 👥 Meteor User Accounts
WeKan utilizes Meteors `accounts` system. Relevant resources:
- 📚 Meteor 2.x Accounts Docs: [v2-docs.meteor.com/api/accounts](https://v2-docs.meteor.com/api/accounts)
- 🔍 Meteor Packages:
- [`packages`](https://github.com/wekan/wekan/blob/main/.meteor/packages)
- [`versions`](https://github.com/wekan/wekan/blob/main/.meteor/versions)
- 📦 Meteor 2.14 core packages: [Meteor 2.14 packages](https://github.com/meteor/meteor/tree/release/METEOR%402.14/packages)
---
## 🔐 External Authentication (OIDC, LDAP, etc.)
WeKan supports external authentication methods via internal packages.
📁 See [`packages/`](https://github.com/wekan/wekan/tree/main/packages) for:
- OpenID Connect (OIDC)
- LDAP
- OAuth and other integrations
---
## 📦 NPM & AtmosphereJS Dependencies
- 🔗 `package.json`: [Dependencies list](https://github.com/wekan/wekan/blob/main/package.json)
- 🧩 WekanTeam scoped NPM packages: [@wekanteam on npm](https://www.npmjs.com/search?q=%40wekanteam)
- ☁️ AtmosphereJS Meteor packages: [atmospherejs.com](https://atmospherejs.com)
---
## 🚧 Meteor Version & Upgrade Notes
- 📌 Current Version: **Meteor 2.14**
- [`.meteor/release`](https://github.com/wekan/wekan/blob/main/.meteor/release)
- 🔧 Maintained with only **critical fixes** until ~Summer 2025
- 🚀 Migration to **Meteor 3** or a new framework is under consideration
📘 Meteor 3 API: [docs.meteor.com/api/accounts](https://docs.meteor.com/api/accounts)
---
## 🧪 Prototypes & Examples
### 🐘 PHP Prototype Sign-Up
Used in experimental versions:
- Step 1: [`sign-up1.php`](https://github.com/wekan/php/blob/main/page/sign-up1.php)
- Step 2: [`sign-up2.php`](https://github.com/wekan/php/blob/main/page/sign-up2.php)
- Main entry: [`index.php#L72-L83`](https://github.com/wekan/php/blob/main/public/index.php#L72-L83)
---
### 🎨 WeKan Studio Prototype
Sign-up logic in the **WeKan Studio** version:
- [`signUp.fmt`](https://github.com/wekan/wekanstudio/blob/main/srv/templates/login/signUp.fmt)
---
## 📎 Future Considerations
- Upgrading to **Meteor 3.x**
- Refactoring frontend logic to fix translation rendering order
- Exploring **simplified authentication systems** in future prototypes
---
## 🔗 Project Links
- 🔧 Main Repo: [github.com/wekan/wekan](https://github.com/wekan/wekan)
- 🌐 Website: [wekan.github.io](https://wekan.github.io)
- 📚 Documentation: [Wekan Wiki](https://github.com/wekan/wekan/wiki)
---
---

View file

@ -1,7 +1,6 @@
About money, see [CONTRIBUTING.md](CONTRIBUTING.md)
Security is very important to us. If you discover any issue regarding security, please disclose Security is very important to us. If you discover any issue regarding security, please disclose
the information responsibly by sending an email to security@wekan.team and not by the information responsibly by sending an email to support (at) wekan.team using
[this PGP public key](support-at-wekan.team_pgp-publickey.asc) and not by
creating a GitHub issue. We will respond swiftly to fix verifiable security issues. creating a GitHub issue. We will respond swiftly to fix verifiable security issues.
We thank you with a place at our hall of fame page, that is We thank you with a place at our hall of fame page, that is
@ -30,7 +29,7 @@ added to the Wekan Hall of Fame.
## Which domains are in scope? ## Which domains are in scope?
No public domains, because all those are donated to Wekan Open Source project, No public domains, because all those are donated to Wekan Open Source project,
and we don't have any permissions to do security scans on those donated servers. and we don't have any permissions to do security scans on those donated servers
Please don't perform research that could impact other users. Secondly, please keep Please don't perform research that could impact other users. Secondly, please keep
the reports short and succinct. If we fail to understand the logics of your bug, we will tell you. the reports short and succinct. If we fail to understand the logics of your bug, we will tell you.
@ -49,132 +48,31 @@ like Snap and Docker have their own specific sandboxing etc features.
Standalone Wekan by default does not load any files from Internet, like fonts, CSS, etc. Standalone Wekan by default does not load any files from Internet, like fonts, CSS, etc.
This also means all Standalone Wekan functionality works in offline local networks. This also means all Standalone Wekan functionality works in offline local networks.
WeKan is used at most countries of the world https://snapcraft.io/wekan Wekan is used by companies that have [thousands of users](https://github.com/wekan/wekan/wiki/AWS) and at healthcare.
and by by companies that have 30k users.
- Wekan private board attachments are not accessible without logging in. Wekan uses xss package for input fields like cards, as you can see from
- There is feature to set board public, so that board is visible without logging in in readonly mode, with realtime updates. [package.json](https://github.com/wekan/wekan/blob/master/package.json). Other used versions can be seen from
- Admin Panel has feature to disable all public boards, so all boards are private. [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 .
It's possible to add attachments to cards, and markdown/html links to files.
## SSL/TLS Wekan attachments are not accessible without logging in. Import from Trello works by copying
Trello export JSON to Wekan Trello import page, and in Trello JSON file there is direct links to all publicly
accessible Trello attachment files, that Standalone Wekan downloads directly to Wekan MongoDB database in
[CollectionFS](https://github.com/wekan/wekan/pull/875) format. When Wekan board is exported in
Wekan JSON format, all board attachments are included in Wekan JSON file as base64 encoded text.
That Wekan JSON format file can be imported to Sandstorm Wekan with all the attachments, when we get
latest Wekan version working on Sandstorm, only couple of bugs are left before that. In Sandstorm it's not
possible yet to import from Trello with attachments, because Wekan does not implement Sandstorm-compatible
access to outside of Wekan grain.
- SSL/TLS encrypts traffic between webbrowser and webserver. Standalone Wekan only has password auth currently, there is work in progress to add
- If you are thinking about TLS MITM, look at https://github.com/caddyserver/caddy/issues/2530 [oauth2](https://github.com/wekan/wekan/pull/1578), [Openid](https://github.com/wekan/wekan/issues/538),
- Let's Encrypt TLS requires publicly accessible webserver, that Let's Encrypt TLS validation servers check. [LDAP](https://github.com/wekan/wekan/issues/119) etc. If you need more login security for Standalone Wekan now,
- If firewall limits to only allowed IP addresses, you may need non-Let's Encrypt TLS cert. 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)
- For On Premise: like for example [AWS WAF](https://aws.amazon.com/waf/).
- https://caddyserver.com/docs/automatic-https#local-https
- https://github.com/wekan/wekan/wiki/Caddy-Webserver-Config
- https://github.com/wekan/wekan/wiki/Azure
- https://github.com/wekan/wekan/wiki/Traefik-and-self-signed-SSL-certs
## XSS [All Wekan Platforms](https://github.com/wekan/wekan/wiki/Platforms)
- Dompurify https://www.npmjs.com/package/dompurify
- WeKan uses dompurify npm package to filter for XSS at fields like cards, as you can see from
[package.json](https://github.com/wekan/wekan/blob/main/package.json). Other used versions can be seen from
[Meteor versions file](https://github.com/wekan/wekan/blob/main/.meteor/versions).
- Forms can include markdown links, html, image tags etc like you see at https://wekan.github.io .
- It's possible to add attachments to cards, and markdown/html links to files.
- Dompurify cleans up viewed code, so Javascript in input fields does not execute
- https://wekan.github.io/hall-of-fame/fieldbleed/
- Reaction in comment is now checked, that it does not have extra added code
- https://wekan.github.io/hall-of-fame/reactionbleed/
- https://github.com/wekan/wekan/blob/main/packages/markdown/src/template-integration.js#L76
## QA about PubSub
Q:
Hello,
I have just seen the Meteor DevTools Evolved extension and was wondering if anyone had asked themselves the question of security.
Insofar as all data is shown in the minimongo tab in plain text.
How can data be hidden from this extension?
A:
## PubSub
- It is not security issue to show some text or image, that user has permission to see. It is a security issue, if browserside is some text or image that user should not see.
- Meteor has browserside minimongo database, made with Javascript, updated with Publish/Subscribe, PubSub.
- Publish/Subscribe means, that realtime web framework reads database changes stream, and then immediately updates webpage,
like like dashboards, chat, kanban. That is the point in any realtime web framework in any programming language.
- Yes, you should check with Meteor DevTools Evolved Chromium/Firefox extension that at minimongo is only text that user has permission to see.
- Do checking as logged in user, and logged out user.
- Check permissions and sanitize before allowing some change, because someone could modify content of input field,
PubSub/websocket data (for example with Burp Suite Community Edition), etc.
- If you have REST API, also check that only those that have login token, and have permission, can view or edit text
- You should not include any data user is not allowed to see. Not to webpage text, not to websockets/PubSub, etc.
- Minimongo should not have password hashes PubSub https://wekan.github.io/hall-of-fame/userbleed/
- PubSub uses Websockets, so you need those to be enabled at webserver like Caddy/Nginx/Apache etc, examples of settings
at right menu of https://github.com/wekan/wekan/wiki
- Clientside https://github.com/wekan/wekan/tree/main/client/components subscribes to
PubSub https://github.com/wekan/wekan/tree/main/server/publications or calls meteor methods at https://github.com/wekan/wekan/tree/main/models
- For Admin:
- You can have input field for password https://github.com/wekan/wekan/blob/main/client/components/cards/attachments.js#L303-L312
- You can save password to database https://github.com/wekan/wekan/blob/main/client/components/cards/attachments.js#L303-L312
- Check that only current user or Admin can change password https://github.com/wekan/wekan/blob/main/client/components/cards/attachments.js#L303-L312
- Note that currentUser uses code like Meteor.user() in .js file
- Do not have password hashes in PubSub https://github.com/wekan/wekan/blob/main/server/publications/users.js
- Only show Admin Panel to Admin https://github.com/wekan/wekan/blob/main/client/components/settings/settingBody.jade#L3
- If there is a lot of data, use pagination https://github.com/wekan/wekan/blob/main/client/components/settings/peopleBody.js
- Only have limited amount of data published in PubSub. Limit in MongoDB query in publications how much is published. Too much could make browser too slow.
- Use Environment variables for any email etc passwords.
- But what if you would like to remove minimongo? And only use Meteor methods for saving? In that case, you don't have realtime updates,
and you need to write much more code to load and save data yourself, handle any multi user data saving conflicts yourself,
and many Meteor Atmospherejs.com PubSub using packages would not work anymore https://github.com/wekan/we
## PubSub: Fix that user can not change to Admin
- With PubSub, there is checking, that someone modifying Websockets content, like permission isAdmin, can not change to Admin.
- https://github.com/wekan/wekan/commit/cbad4cf5943d47b916f64b4582f8ca76a9dfd743
- https://wekan.github.io/hall-of-fame/adminbleed/
## Permissions and Roles
- For any user permissions, it's best to use Meteor package package https://github.com/Meteor-Community-Packages/meteor-roles .
- Currently WeKan has custom hardcoded permissions, WeKan does not yet use that meteor-roles package.
- Using permissions at WeKan sidebar https://github.com/wekan/wekan/blob/main/client/components/sidebar/sidebar.js#L1854-L1875
- List of roles https://github.com/wekan/wekan/wiki/REST-API-Role . Change at board or Admin Panel. Also Organizations/Teams.
- Worker role: https://github.com/wekan/wekan/issues/2788
- Not implemented yet: Granular Roles https://github.com/wekan/wekan/issues/3022
- Check is user logged in, with `if (Meteor.user()) {`
- Check is code running at server `if (Meteor.isServer()) {` or client `if Meteor.isClient()) {` .
- Here is some authentication code https://github.com/wekan/wekan/blob/main/server/authentication.js
## Environment variables
- For any passwords, use environment variables, those are serverside
- Do not copy environment variable to public variable that is visible browserside https://github.com/wekan/wekan/blob/main/server/max-size.js
```
Meteor.startup(() => {
if (process.env.HEADER_LOGIN_ID) {
Meteor.settings.public.attachmentsUploadMaxSize = process.env.ATTACHMENTS_UPLOAD_MAX_SIZE;
Meteor.settings.public.attachmentsUploadMimeTypes = process.env.ATTACHMENTS_UPLOAD_MIME_TYPES;
Meteor.settings.public.avatarsUploadMaxSize = process.env.AVATARS_UPLOAD_MAX_SIZE;
```
- For serverside, you can set Meteor.settings.variablename, without text public
- For WeKan kanban, there is feature for setting board public, it can be viewed by anyone, there is realtime updates. But
- Some of those permissions are checked at users.js models at https://github.com/wekan/wekan/tree/main/models
- Environment variables are used for email server passwords, etc, at all platforms https://github.com/wekan/wekan/commit/a781c0e7dcfdbe34c1483ee83cec12455b7026f7
## Escape HTML comment tags so that HTML comments are visible
- Someone reported, that it is problem that content of HTML comments in edit mode, are not visible at at view mode, so this makes HTML comments visible.
- https://github.com/wekan/wekan/commit/167863d95711249e69bb3511175d73b34acbbdb3
- https://wekan.github.io/hall-of-fame/invisiblebleed/
## Attachments: XSS in filename is sanitized
- https://github.com/wekan/wekan/blob/main/client/components/cards/attachments.js#L303-L312
- https://wekan.github.io/hall-of-fame/filebleed/
## Brute force login protection
- https://github.com/wekan/wekan/commit/23e5e1e3bd081699ce39ce5887db7e612616014d
- https://github.com/wekan/wekan/tree/main/packages/wekan-accounts-lockout
### Sandstorm Wekan Security ### Sandstorm Wekan Security
@ -207,6 +105,12 @@ 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 guessing. Currently there is
[brute force protection with eluck:accounts-lockout](https://github.com/wekan/wekan/blob/master/CHANGELOG.md#v080-2018-04-04-wekan-release).
- Security issues related to that Wekan uses Meteor 1.6.0.1 related packages, and upgrading to newer
Meteor 1.6.1 is complicated process that requires lots of changes to many dependency packages.
Upgrading [has been tried many times, spending a lot of time](https://github.com/meteor/meteor/issues/9609)
but there still is issues. Helping with package upgrades is very welcome.
- [Wekan API old tokens not replaced correctly](https://github.com/wekan/wekan/issues/1437) - [Wekan API old tokens not replaced correctly](https://github.com/wekan/wekan/issues/1437)
- Missing Cookie flags on non-session cookies or 3rd party cookies - Missing Cookie flags on non-session cookies or 3rd party cookies
- Logout CSRF - Logout CSRF
@ -217,7 +121,7 @@ Typical already known or "no impact" bugs such as:
- Email spoofing, SPF, DMARC & DKIM. Wekan does not include email server. - Email spoofing, SPF, DMARC & DKIM. Wekan does not include email server.
Wekan is Open Source with MIT license, and free to use also for commercial use. Wekan is Open Source with MIT license, and free to use also for commercial use.
We welcome all fixes to improve security by email to security@wekan.team We welcome all fixes to improve security by email to security (at) wekan.team .
## Bonus Points ## Bonus Points

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: "v7.85.0" appVersion: "v7.09.0"
files: files:
userUploads: userUploads:
- README.md - README.md

357
api.py
View file

@ -37,24 +37,9 @@ If *nix: chmod +x api.py => ./api.py users
python3 api.py customfields BOARDID # Custom Fields of BOARDID python3 api.py customfields BOARDID # Custom Fields of BOARDID
python3 api.py customfield BOARDID CUSTOMFIELDID # Info of CUSTOMFIELDID python3 api.py customfield BOARDID CUSTOMFIELDID # Info of CUSTOMFIELDID
python3 api.py addcustomfieldtoboard AUTHORID BOARDID NAME TYPE SETTINGS SHOWONCARD AUTOMATICALLYONCARD SHOWLABELONMINICARD SHOWSUMATTOPOFLIST # Add Custom Field to Board python3 api.py addcustomfieldtoboard AUTHORID BOARDID NAME TYPE SETTINGS SHOWONCARD AUTOMATICALLYONCARD SHOWLABELONMINICARD SHOWSUMATTOPOFLIST # Add Custom Field to Board
python3 api.py editcustomfield BOARDID LISTID CARDID CUSTOMFIELDID NEWCUSTOMFIELDVALUE # Edit Custom Field python3 api.py editcustomfield BOARDID LISTID CARDID CUSTOMFIELDID NEWCUSTOMFIELDVALUE
python3 api.py listattachments BOARDID # List attachments python3 api.py listattachments BOARDID # List attachments
python3 api.py cardsbyswimlane SWIMLANEID LISTID # Retrieve cards list on a swimlane
python3 api.py getcard BOARDID LISTID CARDID # Get card info
python3 api.py addlabel BOARDID LISTID CARDID LABELID # Add label to a card
python3 api.py addcardwithlabel AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION LABELIDS # Add a card and a label
python3 api.py editboardtitle BOARDID NEWBOARDTITLE # Edit board title
python3 api.py copyboard BOARDID NEWBOARDTITLE # Copy a board
python3 api.py createlabel BOARDID LABELCOLOR LABELNAME (Color available: `white`, `green`, `yellow`, `orange`, `red`, `purple`, `blue`, `sky`, `lime`, `pink`, `black`, `silver`, `peachpuff`, `crimson`, `plum`, `darkgreen`, `slateblue`, `magenta`, `gold`, `navy`, `gray`, `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo`) # Create a new label
python3 api.py editcardcolor BOARDID LISTID CARDID COLOR (Color available: `white`, `green`, `yellow`, `orange`, `red`, `purple`, `blue`, `sky`, `lime`, `pink`, `black`, `silver`, `peachpuff`, `crimson`, `plum`, `darkgreen`, `slateblue`, `magenta`, `gold`, `navy`, `gray`, `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo`) # Edit card color
python3 api.py addchecklist BOARDID CARDID TITLE ITEM1 ITEM2 ITEM3 ITEM4 (You can add multiple items or just one, or also without any item, just TITLE works as well. * If items or Title contains spaces, you should add ' between them.) # Add checklist + item on a card
python3 api.py deleteallcards BOARDID SWIMLANEID ( * Be careful will delete ALL CARDS INSIDE the swimlanes automatically in every list * ) # Delete all cards on a swimlane
python3 api.py checklistid BOARDID CARDID # Retrieve Checklist ID attached to a card
python3 api.py checklistinfo BOARDID CARDID CHECKLISTID # Get checklist info
python3 api.py get_list_cards_count BOARDID LISTID # Retrieve how many cards in a list
python3 api.py get_board_cards_count BOARDID # Retrieve how many cards in a board
Admin API: Admin API:
python3 api.py users # All users python3 api.py users # All users
python3 api.py boards # All Public Boards python3 api.py boards # All Public Boards
@ -194,52 +179,6 @@ if arguments == 10:
print(body.text) print(body.text)
# ------- ADD CUSTOM FIELD TO BOARD END ----------- # ------- ADD CUSTOM FIELD TO BOARD END -----------
if arguments == 8:
if sys.argv[1] == 'addcardwithlabel':
# ------- ADD CARD WITH LABEL START -----------
authorid = sys.argv[2]
boardid = sys.argv[3]
swimlaneid = sys.argv[4]
listid = sys.argv[5]
cardtitle = sys.argv[6]
carddescription = sys.argv[7]
labelIds = sys.argv[8] # Aggiunto labelIds
cardtolist = wekanurl + apiboards + boardid + s + l + s + listid + s + cs
# Add card
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
post_data = {
'authorId': '{}'.format(authorid),
'title': '{}'.format(cardtitle),
'description': '{}'.format(carddescription),
'swimlaneId': '{}'.format(swimlaneid),
'labelIds': labelIds
}
body = requests.post(cardtolist, data=post_data, headers=headers)
print(body.text)
# If ok id card
if body.status_code == 200:
card_data = body.json()
new_card_id = card_data.get('_id')
# Updating card
if new_card_id:
edcard = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + new_card_id
put_data = {'labelIds': labelIds}
body = requests.put(edcard, data=put_data, headers=headers)
print("=== EDIT CARD ===\n")
body = requests.get(edcard, headers=headers)
data2 = body.text.replace('}', "}\n")
print(data2)
else:
print("Error obraining ID.")
else:
print("Error adding card.")
# ------- ADD CARD WITH LABEL END -----------
if arguments == 7: if arguments == 7:
if sys.argv[1] == 'addcard': if sys.argv[1] == 'addcard':
@ -298,53 +237,7 @@ if arguments == 6:
print(data2) print(data2)
# ------- EDIT CUSTOMFIELD END ----------- # ------- EDIT CUSTOMFIELD END -----------
if arguments == 5: if arguments == 4:
if sys.argv[1] == 'addlabel':
# ------- EDIT CARD ADD LABEL START -----------
boardid = sys.argv[2]
listid = sys.argv[3]
cardid = sys.argv[4]
labelIds = sys.argv[5]
edcard = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + cardid
print(edcard)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
put_data = {'labelIds': labelIds}
body = requests.put(edcard, data=put_data, headers=headers)
print("=== ADD LABEL ===\n")
body = requests.get(edcard, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- EDIT CARD ADD LABEL END -----------
if sys.argv[1] == 'editcardcolor':
# ------- EDIT CARD COLOR START -----------
boardid = sys.argv[2]
listid = sys.argv[3]
cardid = sys.argv[4]
newcolor = sys.argv[5]
valid_colors = ['white', 'green', 'yellow', 'orange', 'red', 'purple', 'blue', 'sky', 'lime', 'pink', 'black',
'silver', 'peachpuff', 'crimson', 'plum', 'darkgreen', 'slateblue', 'magenta', 'gold', 'navy',
'gray', 'saddlebrown', 'paleturquoise', 'mistyrose', 'indigo']
if newcolor not in valid_colors:
print("Invalid color. Choose a color from the list.")
sys.exit(1)
edcard = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + cardid
print(edcard)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
put_data = {'color': '{}'.format(newcolor)}
body = requests.put(edcard, data=put_data, headers=headers)
print("=== EDIT CARD COLOR ===\n")
body = requests.get(edcard, headers=headers)
data2 = body.text.replace('}', "}\n")
print(data2)
# ------- EDIT CARD COLOR END -----------
if arguments >= 4:
if sys.argv[1] == 'newuser': if sys.argv[1] == 'newuser':
@ -358,155 +251,9 @@ if arguments >= 4:
print("=== CREATE NEW USER ===\n") print("=== CREATE NEW USER ===\n")
print(body.text) print(body.text)
# ------- CREATE NEW USER END ----------- # ------- CREATE NEW USER END -----------
if sys.argv[1] == 'getcard':
# ------- LIST OF CARD START -----------
boardid = sys.argv[2]
listid = sys.argv[3]
cardid = sys.argv[4]
listone = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + cardid
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
print("=== INFO OF ONE LIST ===\n")
print("URL:", listone) # Stampa l'URL per debug
try:
response = requests.get(listone, headers=headers)
print("=== RESPONSE ===\n")
print("Status Code:", response.status_code) # Stampa il codice di stato per debug
if response.status_code == 200:
data2 = response.text.replace('}', "}\n")
print(data2)
else:
print(f"Error: {response.status_code}")
print(f"Response: {response.text}")
except Exception as e:
print(f"Error in the GET request: {e}")
# ------- LISTS OF CARD END -----------
if sys.argv[1] == 'createlabel':
# ------- CREATE LABEL START -----------
boardid = sys.argv[2]
labelcolor = sys.argv[3]
labelname = sys.argv[4]
label_url = wekanurl + apiboards + boardid + s + 'labels'
print(label_url)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
# Object to send
put_data = {'label': {'color': labelcolor, 'name': labelname}}
print("URL:", label_url)
print("Headers:", headers)
print("Data:", put_data)
try:
response = requests.put(label_url, json=put_data, headers=headers)
print("=== CREATE LABELS ===\n")
print("Response Status Code:", response.status_code)
print("Response Text:", response.text)
except Exception as e:
print("Error:", e)
# ------- CREATE LABEL END -----------
if sys.argv[1] == 'addchecklist':
# ------- ADD CHECKLIST START -----------
board_id = sys.argv[2]
card_id = sys.argv[3]
checklist_title = sys.argv[4]
# Aggiungi la checklist
checklist_url = wekanurl + apiboards + board_id + s + cs + s + card_id + '/checklists'
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
data = {'title': checklist_title}
response = requests.post(checklist_url, data=data, headers=headers)
response.raise_for_status()
result = json.loads(response.text)
checklist_id = result.get('_id')
print(f"Checklist '{checklist_title}' created. ID: {checklist_id}")
# Aggiungi gli items alla checklist
items_to_add = sys.argv[5:]
for item_title in items_to_add:
checklist_item_url = wekanurl + apiboards + board_id + s + cs + s + card_id + s + 'checklists' + s + checklist_id + '/items'
item_data = {'title': item_title}
item_response = requests.post(checklist_item_url, data=item_data, headers=headers)
item_response.raise_for_status()
item_result = json.loads(item_response.text)
checklist_item_id = item_result.get('_id')
print(f"Item '{item_title}' added. ID: {checklist_item_id}")
if sys.argv[1] == 'checklistinfo':
# ------- ADD CHECKLIST START -----------
board_id = sys.argv[2]
card_id = sys.argv[3]
checklist_id = sys.argv[4]
checklist_url = wekanurl + apiboards + board_id + s + cs + s + card_id + '/checklists' + s + checklist_id
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
response = requests.get(checklist_url, headers=headers)
response.raise_for_status()
checklist_info = response.json()
print("Checklist Info:")
print(checklist_info)
if arguments == 3: if arguments == 3:
if sys.argv[1] == 'editboardtitle':
# ------- EDIT BOARD TITLE START -----------
boardid = sys.argv[2]
boardtitle = sys.argv[3]
edboardtitle = wekanurl + apiboards + boardid + s + 'title'
print(edboardtitle)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
post_data = {'title': boardtitle}
body = requests.put(edboardtitle, json=post_data, headers=headers)
print("=== EDIT BOARD TITLE ===\n")
#body = requests.get(edboardtitle, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
if body.status_code == 200:
print("Succesfull!")
else:
print(f"Error: {body.status_code}")
print(body.text)
# ------- EDIT BOARD TITLE END -----------
if sys.argv[1] == 'copyboard':
# ------- COPY BOARD START -----------
boardid = sys.argv[2]
boardtitle = sys.argv[3]
edboardcopy = wekanurl + apiboards + boardid + s + 'copy'
print(edboardcopy)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
post_data = {'title': boardtitle}
body = requests.post(edboardcopy, json=post_data, headers=headers)
print("=== COPY BOARD ===\n")
#body = requests.get(edboardcopy, headers=headers)
data2 = body.text.replace('}',"}\n")
print(data2)
if body.status_code == 200:
print("Succesfull!")
else:
print(f"Error: {body.status_code}")
print(body.text)
# ------- COPY BOARD END -----------
if sys.argv[1] == 'createlist': if sys.argv[1] == 'createlist':
# ------- CREATE LIST START ----------- # ------- CREATE LIST START -----------
@ -546,90 +293,6 @@ if arguments == 3:
print(data2) print(data2)
# ------- INFO OF CUSTOM FIELD END ----------- # ------- INFO OF CUSTOM FIELD END -----------
if sys.argv[1] == 'cardsbyswimlane':
# ------- RETRIEVE CARDS BY SWIMLANE ID START -----------
boardid = sys.argv[2]
swimlaneid = sys.argv[3]
cardsbyswimlane = wekanurl + apiboards + boardid + s + sws + s + swimlaneid + s + cs
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
print("=== CARDS BY SWIMLANE ID ===\n")
print("URL:", cardsbyswimlane) # Debug
try:
body = requests.get(cardsbyswimlane, headers=headers)
print("Status Code:", body.status_code) # Debug
data = body.text.replace('}', "}\n")
print("Data:", data)
except Exception as e:
print("Error GET:", e)
# ------- RETRIEVE CARDS BY SWIMLANE ID END -----------
if sys.argv[1] == 'deleteallcards':
boardid = sys.argv[2]
swimlaneid = sys.argv[3]
# ------- GET SWIMLANE CARDS START -----------
get_swimlane_cards_url = wekanurl + apiboards + boardid + s + "swimlanes" + s + swimlaneid + s + "cards"
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
try:
response = requests.get(get_swimlane_cards_url, headers=headers)
response.raise_for_status()
cards_data = response.json()
# Print the details of each card
for card in cards_data:
# ------- DELETE CARD START -----------
delete_card_url = wekanurl + apiboards + boardid + s + "lists" + s + card['listId'] + s + "cards" + s + card['_id']
try:
response = requests.delete(delete_card_url, headers=headers)
if response.status_code == 404:
print(f"Card not found: {card['_id']}")
else:
response.raise_for_status()
deleted_card_data = response.json()
print(f"Card Deleted Successfully. Card ID: {deleted_card_data['_id']}")
except requests.exceptions.RequestException as e:
print(f"Error deleting card: {e}")
# ------- DELETE CARD END -----------
except requests.exceptions.RequestException as e:
print(f"Error getting swimlane cards: {e}")
sys.exit(1)
# ------- GET SWIMLANE CARDS END -----------
if sys.argv[1] == 'get_list_cards_count':
# ------- GET LIST CARDS COUNT START -----------
boardid = sys.argv[2]
listid = sys.argv[3]
get_list_cards_count_url = wekanurl + apiboards + boardid + s + l + s + listid + s + "cards_count"
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
try:
response = requests.get(get_list_cards_count_url, headers=headers)
response.raise_for_status()
data = response.json()
print(f"List Cards Count: {data['list_cards_count']}")
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
# ------- GET LIST CARDS COUNT END -----------
if sys.argv[1] == 'checklistid':
# ------- ADD CHECKLIST START -----------
board_id = sys.argv[2]
card_id = sys.argv[3]
checklist_url = wekanurl + apiboards + board_id + s + cs + s + card_id + '/checklists'
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
response = requests.get(checklist_url, headers=headers)
response.raise_for_status()
checklists = response.json()
print("Checklists:")
for checklist in checklists:
print(checklist)
if arguments == 2: if arguments == 2:
# ------- BOARDS LIST START ----------- # ------- BOARDS LIST START -----------
@ -701,22 +364,6 @@ if arguments == 2:
print(data2) print(data2)
# ------- LISTS OF ATTACHMENTS END ----------- # ------- LISTS OF ATTACHMENTS END -----------
if sys.argv[1] == 'get_board_cards_count':
# ------- GET BOARD CARDS COUNT START -----------
boardid = sys.argv[2]
get_board_cards_count_url = wekanurl + apiboards + boardid + s + "cards_count"
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
try:
response = requests.get(get_board_cards_count_url, headers=headers)
response.raise_for_status()
data = response.json()
print(f"Board Cards Count: {data['board_cards_count']}")
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
# ------- GET BOARD CARDS COUNT END -----------
if arguments == 1: if arguments == 1:
if sys.argv[1] == 'users': if sys.argv[1] == 'users':

View file

@ -49,6 +49,43 @@
margin-top: 5px; margin-top: 5px;
padding: 5px; padding: 5px;
} }
.activities .activity .activity-desc .reactions {
display: flex;
margin-top: 5px;
gap: 5px;
}
.activities .activity .activity-desc .reactions .open-comment-reaction-popup {
display: flex;
align-items: center;
text-decoration: none;
height: 24px;
}
.activities .activity .activity-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o {
font-size: 17px;
font-weight: 500;
margin-left: 2px;
}
.activities .activity .activity-desc .reactions .open-comment-reaction-popup i.fa.fa-plus {
font-size: 8px;
margin-top: -7px;
margin-left: 1px;
}
.activities .activity .activity-desc .reactions .reaction {
cursor: pointer;
border: 1px solid #808080;
border-radius: 15px;
display: flex;
padding: 2px 5px;
}
.activities .activity .activity-desc .reactions .reaction.selected {
background-color: #b0c4de;
}
.activities .activity .activity-desc .reactions .reaction:hover {
background-color: #b0c4de;
}
.activities .activity .activity-desc .reactions .reaction .reaction-count {
font-size: 12px;
}
.activities .activity .activity-desc .activity-checklist { .activities .activity .activity-desc .activity-checklist {
display: block; display: block;
border-radius: 3px; border-radius: 3px;

View file

@ -1,12 +1,11 @@
template(name="activities") template(name="activities")
if showActivities .activities.js-sidebar-activities
.activities.js-sidebar-activities //- We should use Template.dynamic here but there is a bug with
//- We should use Template.dynamic here but there is a bug with //- blaze-components: https://github.com/peerlibrary/meteor-blaze-components/issues/30
//- blaze-components: https://github.com/peerlibrary/meteor-blaze-components/issues/30 if $eq mode "board"
if $eq mode "board" +boardActivities
+boardActivities else
else +cardActivities
+cardActivities
template(name="boardActivities") template(name="boardActivities")
each activityData in currentBoard.activities each activityData in currentBoard.activities
@ -16,6 +15,32 @@ template(name="cardActivities")
each activityData in activities each activityData in activities
+activity(activity=activityData card=card mode=mode) +activity(activity=activityData card=card mode=mode)
template(name="editOrDeleteComment")
= ' - '
a.js-open-inlined-form {{_ "edit"}}
= ' - '
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(data-id=activity._id) .activity(data-id=activity._id)
+userAvatar(userId=activity.user._id) +userAvatar(userId=activity.user._id)
@ -105,17 +130,39 @@ template(name="activity")
| {{{_ 'activity-checklist-item-removed' (sanitize activity.checklist.title) cardLink}}}. | {{{_ 'activity-checklist-item-removed' (sanitize activity.checklist.title) cardLink}}}.
//- comment activity ---------------------------------------------------- //- comment activity ----------------------------------------------------
if($eq activity.activityType 'deleteComment') if($eq mode 'card')
| {{{_ 'activity-deleteComment' activity.commentId}}}. //- if we are in card mode we display the comment in a way that it
//- can be edited by the owner
if($eq activity.activityType 'addComment')
+inlinedForm(classNames='js-edit-comment')
+editor(autofocus=true)
= activity.comment.text
.edit-controls
button.primary(type="submit") {{_ 'edit'}}
.fa.fa-times-thin.js-close-inlined-form
else
.activity-comment
+viewer
= activity.comment.text
+commentReactions(reactions=activity.comment.reactions commentId=activity.comment._id)
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
if($eq currentUser._id activity.comment.userId)
+editOrDeleteComment
else if currentUser.isBoardAdmin
+editOrDeleteComment
if($eq activity.activityType 'editComment') if($eq activity.activityType 'deleteComment')
| {{{_ 'activity-editComment' activity.commentId}}}. | {{{_ 'activity-deleteComment' activity.commentId}}}.
if($eq activity.activityType 'addComment') if($eq activity.activityType 'editComment')
| {{{_ 'activity-on' cardLink}}} | {{{_ 'activity-editComment' activity.commentId}}}.
a.activity-comment(href="{{ activity.card.originRelativeUrl }}") else
+viewer //- if we are not in card mode we only display a summary of the comment
= activity.comment.text if($eq activity.activityType 'addComment')
| {{{_ 'activity-on' cardLink}}}
a.activity-comment(href="{{ activity.card.originRelativeUrl }}")
+viewer
= activity.comment.text
//- date activity ------------------------------------------------ //- date activity ------------------------------------------------
if($eq activity.activityType 'a-receivedAt') if($eq activity.activityType 'a-receivedAt')
@ -161,9 +208,6 @@ template(name="activity")
if($eq activity.activityType 'archivedList') if($eq activity.activityType 'archivedList')
| {{_ 'activity-archived' (sanitize listLabel)}}. | {{_ 'activity-archived' (sanitize listLabel)}}.
if($eq activity.activityType 'changedListTitle')
| {{_ 'activity-changedListTitle' (sanitize listLabel) boardLabelLink}}
//- member activity ---------------------------------------------------- //- member activity ----------------------------------------------------
if($eq activity.activityType 'joinMember') if($eq activity.activityType 'joinMember')
if($eq user._id activity.member._id) if($eq user._id activity.member._id)
@ -199,4 +243,4 @@ template(name="activity")
else if(currentData.timeValue) else if(currentData.timeValue)
| {{_ activity.activityType currentData.timeValue}} | {{_ activity.activityType currentData.timeValue}}
div(title=activity.createdAt).activity-meta {{ moment activity.createdAt }} span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}

View file

@ -13,41 +13,39 @@ BlazeComponent.extendComponent({
const sidebar = Sidebar; const sidebar = Sidebar;
sidebar && sidebar.callFirstWith(null, 'resetNextPeak'); sidebar && sidebar.callFirstWith(null, 'resetNextPeak');
this.autorun(() => { this.autorun(() => {
let mode = this.data()?.mode; let mode = this.data().mode;
if (mode) { const capitalizedMode = Utils.capitalize(mode);
const capitalizedMode = Utils.capitalize(mode); let searchId;
let searchId; if (mode === 'linkedcard' || mode === 'linkedboard') {
const showActivities = this.showActivities(); searchId = Utils.getCurrentCard().linkedId;
if (mode === 'linkedcard' || mode === 'linkedboard') { mode = mode.replace('linked', '');
const currentCard = Utils.getCurrentCard(); } else if (mode === 'card') {
searchId = currentCard.linkedId; searchId = Utils.getCurrentCardId();
mode = mode.replace('linked', ''); } else {
} else if (mode === 'card') { searchId = Session.get(`current${capitalizedMode}`);
searchId = Utils.getCurrentCardId();
} else {
searchId = Session.get(`current${capitalizedMode}`);
}
const limit = this.page.get() * activitiesPerPage;
if (searchId === null) return;
this.subscribe('activities', mode, searchId, limit, showActivities, () => {
this.loadNextPageLocked = false;
// TODO the guard can be removed as soon as the TODO above is resolved
if (!sidebar) return;
// If the sibear peak hasn't increased, that mean that there are no more
// activities, and we can stop calling new subscriptions.
// XXX This is hacky! We need to know excatly and reactively how many
// activities there are, we probably want to denormalize this number
// dirrectly into card and board documents.
const nextPeakBefore = sidebar.callFirstWith(null, 'getNextPeak');
sidebar.calculateNextPeak();
const nextPeakAfter = sidebar.callFirstWith(null, 'getNextPeak');
if (nextPeakBefore === nextPeakAfter) {
sidebar.callFirstWith(null, 'resetNextPeak');
}
});
} }
const limit = this.page.get() * activitiesPerPage;
const user = ReactiveCache.getCurrentUser();
const hideSystem = user ? user.hasHiddenSystemMessages() : false;
if (searchId === null) return;
this.subscribe('activities', mode, searchId, limit, hideSystem, () => {
this.loadNextPageLocked = false;
// TODO the guard can be removed as soon as the TODO above is resolved
if (!sidebar) return;
// If the sibear peak hasn't increased, that mean that there are no more
// activities, and we can stop calling new subscriptions.
// XXX This is hacky! We need to know excatly and reactively how many
// activities there are, we probably want to denormalize this number
// dirrectly into card and board documents.
const nextPeakBefore = sidebar.callFirstWith(null, 'getNextPeak');
sidebar.calculateNextPeak();
const nextPeakAfter = sidebar.callFirstWith(null, 'getNextPeak');
if (nextPeakBefore === nextPeakAfter) {
sidebar.callFirstWith(null, 'resetNextPeak');
}
});
}); });
}, },
loadNextPage() { loadNextPage() {
@ -56,27 +54,15 @@ BlazeComponent.extendComponent({
this.loadNextPageLocked = true; this.loadNextPageLocked = true;
} }
}, },
showActivities() {
let ret = false;
let mode = this.data()?.mode;
if (mode) {
if (mode === 'linkedcard' || mode === 'linkedboard') {
const currentCard = Utils.getCurrentCard();
ret = currentCard.showActivities ?? false;
} else if (mode === 'card') {
ret = this.data()?.card?.showActivities ?? false;
} else {
ret = Utils.getCurrentBoard().showActivities ?? false;
}
}
return ret;
},
activities() {
const ret = this.data().card.activities();
return ret;
},
}).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;
@ -261,6 +247,32 @@ BlazeComponent.extendComponent({
return customField.name; return customField.name;
}, },
events() {
return [
{
// XXX We should use Popup.afterConfirmation here
'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => {
const commentId = this.data().activity.commentId;
CardComments.remove(commentId);
Popup.back();
}),
'submit .js-edit-comment'(evt) {
evt.preventDefault();
const commentText = this.currentComponent()
.getValue()
.trim();
const commentId = Template.parentData().activity.commentId;
if (commentText) {
CardComments.update(commentId, {
$set: {
text: commentText,
},
});
}
},
},
];
},
}).register('activity'); }).register('activity');
Template.activity.helpers({ Template.activity.helpers({

View file

@ -63,78 +63,3 @@
display: block; display: block;
margin: auto; margin: auto;
} }
.comments {
clear: both;
}
.comments .comment {
margin: 0.5px 0;
padding: 6px 0;
display: flex;
}
.comments .comment .member {
width: 32px;
height: 32px;
}
.comments .comment .comment-member {
font-weight: 700;
}
.comments .comment .comment-desc {
word-wrap: break-word;
overflow: hidden;
flex: 1;
align-self: center;
margin: 0;
margin-left: 3px;
overflow: hidden;
word-break: break-word;
}
.comments .comment .comment-desc .comment-text {
display: block;
border-radius: 3px;
background: #fff;
text-decoration: none;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
margin-top: 5px;
padding: 5px;
}
.comments .comment .comment-desc .reactions {
display: flex;
margin-top: 5px;
gap: 5px;
}
.comments .comment .comment-desc .reactions .open-comment-reaction-popup {
display: flex;
align-items: center;
text-decoration: none;
height: 24px;
}
.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o {
font-size: 17px;
font-weight: 500;
margin-left: 2px;
}
.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-plus {
font-size: 8px;
margin-top: -7px;
margin-left: 1px;
}
.comments .comment .comment-desc .reactions .reaction {
cursor: pointer;
border: 1px solid #808080;
border-radius: 15px;
display: flex;
padding: 2px 5px;
}
.comments .comment .comment-desc .reactions .reaction.selected {
background-color: #b0c4de;
}
.comments .comment .comment-desc .reactions .reaction:hover {
background-color: #b0c4de;
}
.comments .comment .comment-desc .reactions .reaction .reaction-count {
font-size: 12px;
}
.comments .comment .comment-desc .comment-meta {
font-size: 0.8em;
color: #999;
}

View file

@ -7,59 +7,3 @@ template(name="commentForm")
| {{getUnsavedValue 'cardComment' currentCard._id}} | {{getUnsavedValue 'cardComment' currentCard._id}}
.add-controls .add-controls
button.primary.confirm.clear.js-add-comment(type="submit") {{_ 'comment'}} button.primary.confirm.clear.js-add-comment(type="submit") {{_ 'comment'}}
template(name="comments")
.comments
each commentData in getComments
+comment(commentData)
template(name="comment")
.comment
+userAvatar(userId=userId)
p.comment-desc
span.comment-member
+memberName(user=user)
+inlinedForm(classNames='js-edit-comment')
+editor(autofocus=true)
= text
.edit-controls
button.primary(type="submit") {{_ 'edit'}}
.fa.fa-times-thin.js-close-inlined-form
else
.comment-text
+viewer
= text
+commentReactions(reactions=reactions commentId=_id)
span(title=createdAt).comment-meta {{ moment createdAt }}
if($eq currentUser._id userId)
+editOrDeleteComment
else if currentUser.isBoardAdmin
+editOrDeleteComment
template(name="editOrDeleteComment")
= ' - '
a.js-open-inlined-form {{_ "edit"}}
= ' - '
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}

View file

@ -55,41 +55,6 @@ BlazeComponent.extendComponent({
}, },
}).register('commentForm'); }).register('commentForm');
BlazeComponent.extendComponent({
getComments() {
const ret = this.data().comments();
return ret;
},
}).register("comments");
BlazeComponent.extendComponent({
events() {
return [
{
'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => {
const commentId = this.data()._id;
CardComments.remove(commentId);
Popup.back();
}),
'submit .js-edit-comment'(evt) {
evt.preventDefault();
const commentText = this.currentComponent()
.getValue()
.trim();
const commentId = this.data()._id;
if (commentText) {
CardComments.update(commentId, {
$set: {
text: commentText,
},
});
}
},
},
];
},
}).register("comment");
// XXX This should be a static method of the `commentForm` component // XXX This should be a static method of the `commentForm` component
function resetCommentInput(input) { function resetCommentInput(input) {
input.val(''); // without manually trigger, input event won't be fired input.val(''); // without manually trigger, input event won't be fired

View file

@ -16,6 +16,10 @@
transition: margin 0.1s; transition: margin 0.1s;
overflow-y: auto; overflow-y: auto;
} }
.board-wrapper .board-canvas.is-sibling-sidebar-open {
margin-right: 248px;
}
.board-wrapper .board-canvas.overlayed {overflow:hidden}
.board-wrapper .board-canvas .board-overlay { .board-wrapper .board-canvas .board-overlay {
position: fixed; position: fixed;
left: 0; left: 0;

View file

@ -17,32 +17,26 @@ template(name="boardBody")
| {{_ 'tableVisibilityMode-allowPrivateOnly'}} | {{_ 'tableVisibilityMode-allowPrivateOnly'}}
else else
.board-wrapper(class=currentBoard.colorClass) .board-wrapper(class=currentBoard.colorClass)
+sidebar
.board-canvas.js-swimlanes( .board-canvas.js-swimlanes(
class="{{#if hasSwimlanes}}dragscroll{{/if}}"
class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}" class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}"
class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}" class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}"
class="{{#if draggingActive.get}}is-dragging-active{{/if}}" class="{{#if draggingActive.get}}is-dragging-active{{/if}}"
class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}") class="{{#if showOverlay.get}}overlayed{{/if}}")
if showOverlay.get if showOverlay.get
.board-overlay .board-overlay
if currentBoard.isTemplatesBoard if currentBoard.isTemplatesBoard
each currentBoard.swimlanes each currentBoard.swimlanes
+swimlane(this) +swimlane(this)
else if isViewSwimlanes else if isViewSwimlanes
if hasSwimlanes each currentBoard.swimlanes
each currentBoard.swimlanes +swimlane(this)
+swimlane(this)
else
a.js-empty-board-add-swimlane(title="{{_ 'add-swimlane'}}")
h1.big-message.quiet
| {{_ 'add-swimlane'}} +
else if isViewLists else if isViewLists
+listsGroup(currentBoard) +listsGroup(currentBoard)
else if isViewCalendar else if isViewCalendar
+calendarView +calendarView
else else
+listsGroup(currentBoard) +listsGroup(currentBoard)
+sidebar
template(name="calendarView") template(name="calendarView")
if isViewCalendar if isViewCalendar

View file

@ -1,6 +1,5 @@
import { ReactiveCache } from '/imports/reactiveCache'; import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n'; import { TAPi18n } from '/imports/i18n';
import dragscroll from '@wekanteam/dragscroll';
const subManager = new SubsManager(); const subManager = new SubsManager();
const { calculateIndex } = Utils; const { calculateIndex } = Utils;
@ -195,9 +194,6 @@ BlazeComponent.extendComponent({
}); });
this.autorun(() => { this.autorun(() => {
// Always reset dragscroll on view switch
dragscroll.reset();
if (Utils.isTouchScreenOrShowDesktopDragHandles()) { if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$swimlanesDom.sortable({ $swimlanesDom.sortable({
handle: '.js-swimlane-header-handle', handle: '.js-swimlane-header-handle',
@ -223,7 +219,6 @@ BlazeComponent.extendComponent({
boardComponent.openNewListForm(); boardComponent.openNewListForm();
} }
dragscroll.reset();
Utils.setBackgroundImage(); Utils.setBackgroundImage();
}, },
@ -248,10 +243,6 @@ BlazeComponent.extendComponent({
} }
}, },
hasSwimlanes() {
return Utils.getCurrentBoard().swimlanes().length > 0;
},
isViewLists() { isViewLists() {
const currentUser = ReactiveCache.getCurrentUser(); const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) { if (currentUser) {
@ -270,11 +261,6 @@ BlazeComponent.extendComponent({
} }
}, },
isVerticalScrollbars() {
const user = ReactiveCache.getCurrentUser();
return user && user.isVerticalScrollbars();
},
openNewListForm() { openNewListForm() {
if (this.isViewSwimlanes()) { if (this.isViewSwimlanes()) {
// The form had been removed in 416b17062e57f215206e93a85b02ef9eb1ab4902 // The form had been removed in 416b17062e57f215206e93a85b02ef9eb1ab4902
@ -297,7 +283,6 @@ BlazeComponent.extendComponent({
this._isDragging = false; this._isDragging = false;
} }
}, },
'click .js-empty-board-add-swimlane': Popup.open('swimlaneAdd'),
}, },
]; ];
}, },
@ -335,7 +320,7 @@ BlazeComponent.extendComponent({
calendarOptions() { calendarOptions() {
return { return {
id: 'calendar-view', id: 'calendar-view',
defaultView: 'month', defaultView: 'agendaDay',
editable: true, editable: true,
selectable: true, selectable: true,
timezone: 'local', timezone: 'local',

File diff suppressed because it is too large Load diff

View file

@ -12,9 +12,8 @@ template(name="boardHeaderBar")
if currentBoard if currentBoard
if currentUser if currentUser
with currentBoard with currentBoard
if currentUser.isBoardAdmin a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title)
a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title) i.fa.fa-pencil-square-o
i.fa.fa-pencil-square-o
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}" a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}") title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")

View file

@ -1,12 +1,42 @@
import { ReactiveCache } from '/imports/reactiveCache'; import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n'; import { TAPi18n } from '/imports/i18n';
import dragscroll from '@wekanteam/dragscroll';
/* /*
const DOWNCLS = 'fa-sort-down'; const DOWNCLS = 'fa-sort-down';
const UPCLS = 'fa-sort-up'; const UPCLS = 'fa-sort-up';
*/ */
const sortCardsBy = new ReactiveVar(''); const sortCardsBy = new ReactiveVar('');
Template.boardMenuPopup.events({
'click .js-rename-board': Popup.open('boardChangeTitle'),
'click .js-custom-fields'() {
Sidebar.setView('customFields');
Popup.back();
},
'click .js-open-archives'() {
Sidebar.setView('archives');
Popup.back();
},
'click .js-change-board-color': Popup.open('boardChangeColor'),
'click .js-change-language': Popup.open('changeLanguage'),
'click .js-archive-board ': Popup.afterConfirm('archiveBoard', function() {
const currentBoard = Utils.getCurrentBoard();
currentBoard.archive();
// XXX We should have some kind of notification on top of the page to
// confirm that the board was successfully archived.
FlowRouter.go('home');
}),
'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() {
const currentBoard = Utils.getCurrentBoard();
Popup.back();
Boards.remove(currentBoard._id);
FlowRouter.go('home');
}),
'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
'click .js-card-settings': Popup.open('boardCardSettings'),
'click .js-minicard-settings': Popup.open('boardMinicardSettings'),
});
Template.boardChangeTitlePopup.events({ Template.boardChangeTitlePopup.events({
submit(event, templateInstance) { submit(event, templateInstance) {

View file

@ -151,8 +151,8 @@ BlazeComponent.extendComponent({
} }
const currUser = ReactiveCache.getCurrentUser(); const currUser = ReactiveCache.getCurrentUser();
let orgIdsUserBelongs = currUser?.orgIdsUserBelongs() || ''; let orgIdsUserBelongs = currUser !== undefined && currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : '';
if (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++){
// query.$and[2].$or.push({'orgs.orgId': orgsIds[i]}); // query.$and[2].$or.push({'orgs.orgId': orgsIds[i]});
@ -162,8 +162,8 @@ BlazeComponent.extendComponent({
query.$and[2].$or.push({ 'orgs.orgId': { $in: orgsIds } }); query.$and[2].$or.push({ 'orgs.orgId': { $in: orgsIds } });
} }
let teamIdsUserBelongs = currUser?.teamIdsUserBelongs() || ''; let teamIdsUserBelongs = currUser !== undefined && currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : '';
if (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++){
// query.$or[2].$or.push({'teams.teamId': teamsIds[i]}); // query.$or[2].$or.push({'teams.teamId': teamsIds[i]});
@ -199,12 +199,15 @@ BlazeComponent.extendComponent({
}, },
boardMembers(boardId) { boardMembers(boardId) {
let boardMembers = [];
/* Bug Board icons random dance https://github.com/wekan/wekan/issues/4214 /* Bug Board icons random dance https://github.com/wekan/wekan/issues/4214
const lists = ReactiveCache.getBoard(boardId) const lists = ReactiveCache.getBoard(boardId)
const boardMembers = lists?.members.map(member => member.userId); let members = lists.members
return boardMembers; members.forEach(member => {
boardMembers.push(member.userId);
});
*/ */
return []; return boardMembers;
}, },
isStarred() { isStarred() {

View file

@ -24,6 +24,7 @@
min-width: 150px; min-width: 150px;
max-height: 150px; max-height: 150px;
padding-right: 16px; padding-right: 16px;
} }
.attachment-thumbnail { .attachment-thumbnail {
max-width: 150px; max-width: 150px;
@ -77,10 +78,10 @@
width: 100%; width: 100%;
height: 100vh; height: 100vh;
position: fixed; position: fixed;
top: 0; top: 48px; /* height of the navbar */
left: 0; left: 0;
z-index: 9999 !important; z-index: 9999 !important;
background: rgba(13, 13, 13, 0.95); background: rgba(13,13,13,0.95);
} }
#viewer-container { #viewer-container {
display: flex; display: flex;
@ -98,12 +99,10 @@
#attachment-name { #attachment-name {
color: white; color: white;
font-size: 1.5em; font-size: 1.5em;
max-width: calc( max-width: calc(100% - 50px); /* Make sure the name does not overlap the close button */
100% - 50px
); /* Make sure the name does not overlap the close button */
} }
#viewer-close { #viewer-close {
color: white; color:white;
cursor: pointer; cursor: pointer;
font-size: 4em; font-size: 4em;
top: 0; top: 0;
@ -112,32 +111,26 @@
} }
.attachment-arrow { .attachment-arrow {
font-size: 4em; font-size: 4em;
color: white; color:white;
cursor: pointer; cursor: pointer;
align-self: center; align-self: center;
margin: 0 20px; margin: 0 20px;
} }
#viewer-content {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: calc(100% - 50px);
}
#image-viewer { #image-viewer {
background: repeating-conic-gradient(#808080 0% 25%, transparent 0% 50%) 50% / background:
20px 20px; /* Checkerboard background for transparent images */ repeating-conic-gradient(#808080 0% 25%, transparent 0% 50%)
50% / 20px 20px; /* Checkerboard background for transparent images */
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
} }
#pdf-viewer { #pdf-viewer {
width: 40vw; width: 40vw;
height: 100%; height: 100vh;
} }
#txt-viewer { #txt-viewer{
background-color: white; background-color: white;
width: 40vw; width: 40vw;
height: 100%; height: 100vh;
} }
.pdf-preview-error { .pdf-preview-error {
margin-top: 20vh; margin-top: 20vh;
@ -156,29 +149,25 @@
#viewer-container { #viewer-container {
display: block; display: block;
} }
.attachment-arrow { .attachment-arrow{
position: absolute; position: absolute;
bottom: 2.2em; bottom: 2.2em;
font-size: 1.6em; font-size: 1.6em;
padding: 16px; padding: 16px;
} }
#prev-attachment { #prev-attachment{
left: 0; left: 0;
} }
#next-attachment { #next-attachment{
right: 0; right: 0;
} }
#pdf-viewer { #pdf-viewer {
width: 100%; width: 100%;
height: calc( height: calc(100vh - 155px); /* Full height - height of top and bottom bars */
100vh - 155px
); /* Full height - height of top and bottom bars */
} }
#txt-viewer { #txt-viewer {
width: 100%; width: 100%;
height: calc( height: calc(100vh - 155px); /* Full height - height of top and bottom bars */
100vh - 155px
); /* Full height - height of top and bottom bars */
} }
#audio-viewer { #audio-viewer {
margin-top: 20%; margin-top: 20%;

View file

@ -51,9 +51,8 @@ template(name="attachmentGallery")
.attachment-gallery .attachment-gallery
if canModifyCard a.attachment-item.add-attachment.js-add-attachment
a.attachment-item.add-attachment.js-add-attachment i.fa.fa-plus.icon
i.fa.fa-plus.icon
each attachments each attachments
@ -117,6 +116,8 @@ template(name="attachmentActionsPopup")
| {{_ 'remove-background-image'}} | {{_ 'remove-background-image'}}
else else
| {{_ 'add-background-image'}} | {{_ 'add-background-image'}}
p.attachment-storage
| {{versions.original.storage}}
if $neq versions.original.storage "fs" if $neq versions.original.storage "fs"
a.js-move-storage-fs a.js-move-storage-fs

View file

@ -39,7 +39,7 @@ Template.attachmentGallery.events({
'click .js-rename': Popup.open('attachmentRename'), 'click .js-rename': Popup.open('attachmentRename'),
'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', function() { 'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', function() {
Attachments.remove(this._id); Attachments.remove(this._id);
Popup.back(); Popup.back(2);
}), }),
}); });
@ -501,7 +501,7 @@ BlazeComponent.extendComponent({
if (name === DOMPurify.sanitize(name)) { if (name === DOMPurify.sanitize(name)) {
Meteor.call('renameAttachment', this.data()._id, name); Meteor.call('renameAttachment', this.data()._id, name);
} }
Popup.back(); Popup.back(2);
}, },
} }
] ]

View file

@ -79,14 +79,13 @@ template(name="cardCustomField-currency")
template(name="cardCustomField-date") template(name="cardCustomField-date")
if canModifyCard if canModifyCard
a.js-edit-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") a.js-edit-date(title="{{showTitle}} {{_ 'predicate-week'}} {{showWeek}}" class="{{classes}}")
if value if value
div.card-date div.card-date
time(datetime="{{showISODate}}") time(datetime="{{showISODate}}")
| {{showDate}} | {{showDate}}
if showWeekOfYear b
b | {{showWeek}}
| {{showWeek}}
else else
| {{_ 'edit'}} | {{_ 'edit'}}
else else
@ -94,9 +93,8 @@ template(name="cardCustomField-date")
div.card-date div.card-date
time(datetime="{{showISODate}}") time(datetime="{{showISODate}}")
| {{showDate}} | {{showDate}}
if showWeekOfYear b
b | {{showWeek}}
| {{showWeek}}
template(name="cardCustomField-dropdown") template(name="cardCustomField-dropdown")
if canModifyCard if canModifyCard

View file

@ -148,10 +148,6 @@ CardCustomField.register('cardCustomField');
return this.date.get().week().toString(); return this.date.get().week().toString();
} }
showWeekOfYear() {
return ReactiveCache.getCurrentUser().isShowWeekOfYear();
}
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

View file

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

View file

@ -11,7 +11,7 @@ import { DatePicker } from '/client/lib/datepicker';
} }
_storeDate(date) { _storeDate(date) {
this.card.setReceived(moment(date).format('YYYY-MM-DD HH:mm')); this.card.setReceived(date);
} }
_deleteDate() { _deleteDate() {
@ -37,7 +37,7 @@ import { DatePicker } from '/client/lib/datepicker';
} }
_storeDate(date) { _storeDate(date) {
this.card.setStart(moment(date).format('YYYY-MM-DD HH:mm')); this.card.setStart(date);
} }
_deleteDate() { _deleteDate() {
@ -60,7 +60,7 @@ import { DatePicker } from '/client/lib/datepicker';
} }
_storeDate(date) { _storeDate(date) {
this.card.setDue(moment(date).format('YYYY-MM-DD HH:mm')); this.card.setDue(date);
} }
_deleteDate() { _deleteDate() {
@ -83,7 +83,7 @@ import { DatePicker } from '/client/lib/datepicker';
} }
_storeDate(date) { _storeDate(date) {
this.card.setEnd(moment(date).format('YYYY-MM-DD HH:mm')); this.card.setEnd(date);
} }
_deleteDate() { _deleteDate() {
@ -110,10 +110,6 @@ const CardDate = BlazeComponent.extendComponent({
return this.date.get().week().toString(); return this.date.get().week().toString();
}, },
showWeekOfYear() {
return ReactiveCache.getCurrentUser().isShowWeekOfYear();
},
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
@ -287,10 +283,6 @@ class CardCustomFieldDate extends CardDate {
return this.date.get().week().toString(); return this.date.get().week().toString();
} }
showWeekOfYear() {
return ReactiveCache.getCurrentUser().isShowWeekOfYear();
}
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
@ -322,19 +314,19 @@ CardCustomFieldDate.register('cardCustomFieldDate');
(class extends CardStartDate { (class extends CardStartDate {
showDate() { showDate() {
return this.date.get().format('YYYY-MM-DD HH:mm'); return this.date.get().format('L');
} }
}.register('minicardStartDate')); }.register('minicardStartDate'));
(class extends CardDueDate { (class extends CardDueDate {
showDate() { showDate() {
return this.date.get().format('YYYY-MM-DD HH:mm'); return this.date.get().format('L');
} }
}.register('minicardDueDate')); }.register('minicardDueDate'));
(class extends CardEndDate { (class extends CardEndDate {
showDate() { showDate() {
return this.date.get().format('YYYY-MM-DD HH:mm'); return this.date.get().format('L');
} }
}.register('minicardEndDate')); }.register('minicardEndDate'));

View file

@ -5,7 +5,7 @@
float: left; float: left;
height: 30px; height: 30px;
width: 30px; width: 30px;
margin: .3vh; margin: 0 4px 4px 0;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
z-index: 1; z-index: 1;
@ -196,9 +196,6 @@
margin-right: 10px; margin-right: 10px;
padding: 10px; padding: 10px;
} }
.card-details .card-description i.fa.fa-pencil-square-o {
float: right;
}
.card-details .card-description textarea { .card-details .card-description textarea {
min-height: 100px; min-height: 100px;
} }
@ -248,10 +245,10 @@
padding-top: 10px; padding-top: 10px;
} }
@media screen and (min-width: 801px) { @media screen and (min-width: 801px) {
.card-details { .card-details-maximized {
top: 97px; top: 97px;
left: calc(50% - (600px / 2)); left: 0;
width: 600px; right:0;
bottom: 0; bottom: 0;
position: fixed; position: fixed;
resize: both; resize: both;
@ -270,10 +267,6 @@
box-shadow: 0 0 7px 0 #b3b3b3; box-shadow: 0 0 7px 0 #b3b3b3;
transition: flex-basis 0.1s; transition: flex-basis 0.1s;
box-sizing: border-box; box-sizing: border-box;
top: 97px;
left: 0px;
height: calc(100% - 100px);
width: calc(100% - 20px);
float: left; float: left;
} }
.card-details-maximized .card-details-left { .card-details-maximized .card-details-left {

View file

@ -5,7 +5,7 @@ template(name="cardDetails")
+attachmentViewer +attachmentViewer
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}'): .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
@ -13,12 +13,11 @@ template(name="cardDetails")
unless isMiniScreen unless isMiniScreen
unless isPopup unless isPopup
a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}") a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
if canModifyCard 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
else a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}") if currentUser.isBoardMember
if canModifyCard
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'}}")
a.fa.fa-link.card-copy-button.js-copy-link( a.fa.fa-link.card-copy-button.js-copy-link(
id="cardURL_copy" id="cardURL_copy"
@ -30,7 +29,7 @@ template(name="cardDetails")
else else
unless isPopup unless isPopup
a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}") a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
if canModifyCard 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.js-copy-link( a.fa.fa-link.card-copy-mobile-button.js-copy-link(
id="cardURL_copy" id="cardURL_copy"
@ -525,12 +524,12 @@ template(name="cardDetails")
a.fa.fa-times-thin.js-close-inlined-form a.fa.fa-times-thin.js-close-inlined-form
else else
if currentBoard.allowsDescriptionText if currentBoard.allowsDescriptionText
a.js-open-inlined-form(title="{{_ 'edit'}}" value=title) a.js-open-inlined-form
i.fa.fa-pencil-square-o
a.js-open-inlined-form(title="{{_ 'edit'}}" value=title)
if getDescription if getDescription
+viewer +viewer
= getDescription = getDescription
else
| {{_ 'edit'}}
if (hasUnsavedValue 'cardDescription' _id) if (hasUnsavedValue 'cardDescription' _id)
p.quiet p.quiet
| {{_ 'unsaved-description'}} | {{_ 'unsaved-description'}}
@ -549,7 +548,7 @@ template(name="cardDetails")
.card-checklist-attachmentGallery.card-checklists .card-checklist-attachmentGallery.card-checklists
if currentBoard.allowsChecklists if currentBoard.allowsChecklists
hr hr
+checklists(cardId = _id card = this) +checklists(cardId = _id)
if currentBoard.allowsSubtasks if currentBoard.allowsSubtasks
hr hr
+subtasks(cardId = _id) +subtasks(cardId = _id)
@ -569,34 +568,25 @@ template(name="cardDetails")
+attachmentGallery +attachmentGallery
hr hr
unless currentUser.isNoComments
.comment-title
h3.card-details-item-title
i.fa.fa-comment-o
| {{_ 'comments'}}
if currentBoard.allowsComments
if currentUser.isBoardMember
unless currentUser.isNoComments
+commentForm
+comments
hr
.card-details-right .card-details-right
unless currentUser.isNoComments unless currentUser.isNoComments
.activity-title .activity-title
h3.card-details-item-title h3.card-details-item-title
i.fa.fa-history i.fa.fa-history
| {{ _ 'activities'}} | {{ _ 'activity'}}
if currentUser.isBoardMember if currentUser.isBoardMember
.material-toggle-switch(title="{{_ 'show-activities'}}") .material-toggle-switch(title="{{_ 'hide-system-messages'}}")
if showActivities //span.toggle-switch-title
input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard" checked="checked") if hiddenSystemMessages
input.toggle-switch(type="checkbox" id="toggleButton" checked="checked")
else else
input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard") input.toggle-switch(type="checkbox" id="toggleButton")
label.toggle-label(for="toggleShowActivitiesCard") label.toggle-label(for="toggleButton")
if currentBoard.allowsComments
if currentUser.isBoardMember
unless currentUser.isNoComments
+commentForm
unless currentUser.isNoComments unless currentUser.isNoComments
if isLoaded.get if isLoaded.get
if isLinkedCard if isLinkedCard

View file

@ -63,6 +63,10 @@ BlazeComponent.extendComponent({
return card.findWatcher(Meteor.userId()); return card.findWatcher(Meteor.userId());
}, },
hiddenSystemMessages() {
return ReactiveCache.getCurrentUser().hasHiddenSystemMessages();
},
customFieldsGrid() { customFieldsGrid() {
return ReactiveCache.getCurrentUser().hasCustomFieldsGrid(); return ReactiveCache.getCurrentUser().hasCustomFieldsGrid();
}, },
@ -72,6 +76,66 @@ BlazeComponent.extendComponent({
return !Utils.getPopupCardId() && ReactiveCache.getCurrentUser().hasCardMaximized(); return !Utils.getPopupCardId() && ReactiveCache.getCurrentUser().hasCardMaximized();
}, },
scrollParentContainer() {
const cardPanelWidth = 600;
const parentComponent = this.parentComponent();
/*
// Incomplete fix about bug where opening card scrolls to wrong place
// https://github.com/wekan/wekan/issues/4572#issuecomment-1184149395
// TODO sometimes parentComponent is not available, maybe because it's not
// yet created?!
//
// uncommented again by chrisi51
// only with that, the autoscroll feature is working properly
// after my fixes, all scrollings where correct
*/
if (!parentComponent) return;
const bodyBoardComponent = parentComponent.parentComponent();
//On Mobile View Parent is Board, Not Board Body. I cant see how this funciton should work then.
if (bodyBoardComponent === null) return;
const $cardView = this.$(this.firstNode());
const $cardContainer = bodyBoardComponent.$('.js-swimlanes');
/*
// Incomplete fix about bug where opening card scrolls to wrong place
// https://github.com/wekan/wekan/issues/4572#issuecomment-1184149395
// TODO sometimes cardContainer is not available, maybe because it's not yet
// created?!
if (!$cardContainer) return;
*/
const cardContainerScroll = $cardContainer.scrollLeft();
const cardContainerWidth = $cardContainer.width();
const cardViewStart = $cardView.offset().left;
const cardViewEnd = cardViewStart + cardPanelWidth;
let offset = false;
if (cardViewStart < 0) {
offset = cardViewStart;
} else if (cardViewEnd > cardContainerWidth) {
offset = cardViewEnd - cardContainerWidth;
}
if (offset) {
bodyBoardComponent.scrollLeft(cardContainerScroll + offset);
}
//Scroll top
const cardViewStartTop = $cardView.offset().top;
const cardContainerScrollTop = $cardContainer.scrollTop();
let topOffset = false;
if (cardViewStartTop !== 100) {
topOffset = cardViewStartTop - 100;
}
if (topOffset !== false) {
bodyBoardComponent.scrollTop(cardContainerScrollTop + topOffset);
}
},
presentParentTask() { presentParentTask() {
let result = this.currentBoard.presentParentTask; let result = this.currentBoard.presentParentTask;
if (result === null || result === undefined) { if (result === null || result === undefined) {
@ -114,11 +178,6 @@ BlazeComponent.extendComponent({
); );
}, },
isVerticalScrollbars() {
const user = ReactiveCache.getCurrentUser();
return user && user.isVerticalScrollbars();
},
/** returns if the list id is the current list id /** returns if the list id is the current list id
* @param listId list id to check * @param listId list id to check
* @return is the list id the current list id ? * @return is the list id the current list id ?
@ -162,6 +221,12 @@ BlazeComponent.extendComponent({
//------------- //-------------
} }
if (!Utils.isMiniScreen()) {
Meteor.setTimeout(() => {
this.scrollParentContainer();
}, 500);
}
const $checklistsDom = this.$('.card-checklist-items'); const $checklistsDom = this.$('.card-checklist-items');
$checklistsDom.sortable({ $checklistsDom.sortable({
@ -378,11 +443,8 @@ BlazeComponent.extendComponent({
Session.set('cardDetailsIsDragging', false); Session.set('cardDetailsIsDragging', false);
Session.set('cardDetailsIsMouseDown', false); Session.set('cardDetailsIsMouseDown', false);
}, },
'click #toggleShowActivitiesCard'() { 'click #toggleButton'() {
this.data().toggleShowActivities(); Meteor.call('toggleSystemMessages');
},
'click #toggleHideCheckedChecklistItems'() {
this.data().toggleHideCheckedChecklistItems();
}, },
'click #toggleCustomFieldsGridButton'() { 'click #toggleCustomFieldsGridButton'() {
Meteor.call('toggleCustomFieldsGrid'); Meteor.call('toggleCustomFieldsGrid');
@ -390,10 +452,20 @@ BlazeComponent.extendComponent({
'click .js-maximize-card-details'() { 'click .js-maximize-card-details'() {
Meteor.call('toggleCardMaximized'); Meteor.call('toggleCardMaximized');
autosize($('.card-details')); autosize($('.card-details'));
// if (!Utils.isMiniScreen()) {
// Meteor.setTimeout(() => {
// this.scrollParentContainer();
// }, 500);
// }
}, },
'click .js-minimize-card-details'() { 'click .js-minimize-card-details'() {
Meteor.call('toggleCardMaximized'); Meteor.call('toggleCardMaximized');
autosize($('.card-details')); autosize($('.card-details'));
if (!Utils.isMiniScreen()) {
Meteor.setTimeout(() => {
this.scrollParentContainer();
}, 500);
}
}, },
'click .js-vote'(e) { 'click .js-vote'(e) {
const forIt = $(e.target).hasClass('js-vote-positive'); const forIt = $(e.target).hasClass('js-vote-positive');
@ -850,15 +922,13 @@ BlazeComponent.extendComponent({
'click .js-palette-color'() { 'click .js-palette-color'() {
this.currentColor.set(this.currentData().color); this.currentColor.set(this.currentData().color);
}, },
'click .js-submit'(event) { 'click .js-submit'() {
event.preventDefault();
this.currentCard.setColor(this.currentColor.get()); this.currentCard.setColor(this.currentColor.get());
Popup.back(); Popup.close();
}, },
'click .js-remove-color'(event) { 'click .js-remove-color'() {
event.preventDefault();
this.currentCard.setColor(null); this.currentCard.setColor(null);
Popup.back(); Popup.close();
}, },
}, },
]; ];

View file

@ -45,9 +45,6 @@ textarea.js-edit-checklist-item {
border-radius: 16px; border-radius: 16px;
height: 100%; height: 100%;
} }
.checklist-title {
padding: 10px;
}
.checklist-title .checkbox { .checklist-title .checkbox {
float: left; float: left;
width: 30px; width: 30px;

View file

@ -10,18 +10,17 @@ template(name="checklists")
a.add-checklist-top.js-open-inlined-form(title="{{_ 'add-checklist'}}") a.add-checklist-top.js-open-inlined-form(title="{{_ 'add-checklist'}}")
i.fa.fa-plus i.fa.fa-plus
if currentUser.isBoardMember if currentUser.isBoardMember
.material-toggle-switch(title="{{_ 'hide-finished-checklist'}}") .material-toggle-switch(title="{{_ 'hide-checked-items'}}")
//span.toggle-switch-title //span.toggle-switch-title
if card.hideFinishedChecklistIfItemsAreHidden if hideCheckedItems
input.toggle-switch(type="checkbox" id="toggleHideFinishedChecklist" checked="checked") input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton" checked="checked")
else else
input.toggle-switch(type="checkbox" id="toggleHideFinishedChecklist") input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton")
label.toggle-label(for="toggleHideFinishedChecklist") label.toggle-label(for="toggleHideCheckedItemsButton")
.card-checklist-items .card-checklist-items
each checklist in checklists each checklist in checklists
if checklist.showChecklist card.hideFinishedChecklistIfItemsAreHidden +checklistDetail(checklist = checklist)
+checklistDetail(checklist = checklist card = card)
if canModifyCard if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId) +inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId)
@ -31,7 +30,7 @@ template(name="checklists")
i.fa.fa-plus i.fa.fa-plus
template(name="checklistDetail") template(name="checklistDetail")
.js-checklist.checklist.nodragscroll .js-checklist.checklist
+inlinedForm(classNames="js-edit-checklist-title" checklist = checklist) +inlinedForm(classNames="js-edit-checklist-title" checklist = checklist)
+editChecklistItemForm(checklist = checklist) +editChecklistItemForm(checklist = checklist)
else else
@ -56,7 +55,7 @@ template(name="checklistDetail")
.checklist-progress-text {{finishedPercent}}% .checklist-progress-text {{finishedPercent}}%
.checklist-progress-bar .checklist-progress-bar
.checklist-progress(style="width:{{finishedPercent}}%") .checklist-progress(style="width:{{finishedPercent}}%")
+checklistItems(checklist = checklist card = card) +checklistItems(checklist = checklist)
template(name="checklistDeletePopup") template(name="checklistDeletePopup")
p {{_ 'confirm-checklist-delete-popup'}} p {{_ 'confirm-checklist-delete-popup'}}
@ -73,12 +72,6 @@ template(name="addChecklistItemForm")
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItem'}}") .material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItem'}}")
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItem") input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItem")
label.toggle-label(for="toggleNewlineBecomesNewChecklistItem") label.toggle-label(for="toggleNewlineBecomesNewChecklistItem")
| {{_ 'newLineNewItem'}}
if $eq position 'top'
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItemOriginOrder'}}")
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItemOriginOrder")
label.toggle-label(for="toggleNewlineBecomesNewChecklistItemOriginOrder")
| {{_ 'originOrder'}}
template(name="editChecklistItemForm") template(name="editChecklistItemForm")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}") a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
@ -102,7 +95,7 @@ template(name="checklistItems")
if checklist.items.length if checklist.items.length
if canModifyCard if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist position="top") +inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist position="top")
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true position="top") +addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true)
else else
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}") a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
i.fa.fa-plus i.fa.fa-plus
@ -111,7 +104,7 @@ template(name="checklistItems")
+inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist) +inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist)
+editChecklistItemForm(type = 'item' item = item checklist = checklist) +editChecklistItemForm(type = 'item' item = item checklist = checklist)
else else
+checklistItemDetail(item = item checklist = checklist card = card) +checklistItemDetail(item = item checklist = checklist)
if canModifyCard if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist) +inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist)
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true) +addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true)
@ -120,7 +113,7 @@ template(name="checklistItems")
i.fa.fa-plus i.fa.fa-plus
template(name='checklistItemDetail') template(name='checklistItemDetail')
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}" .js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if hideCheckedItems}} invisible{{/if}}{{/if}}"
role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0") role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
if canModifyCard if canModifyCard
.check-box-container .check-box-container
@ -148,24 +141,6 @@ template(name="checklistActionsPopup")
a.js-copy-checklist.copy-checklist a.js-copy-checklist.copy-checklist
i.fa.fa-copy i.fa.fa-copy
| {{_ "copyChecklist"}} ... | {{_ "copyChecklist"}} ...
a.js-hide-checked-checklist-items
i.fa.fa-eye-slash
| {{_ "hideCheckedChecklistItems"}} ...
.material-toggle-switch(title="{{_ 'hide-checked-items'}}")
if checklist.hideCheckedChecklistItems
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}" checked="checked")
else
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}")
label.toggle-label(for="toggleHideCheckedChecklistItems_{{checklist._id}}")
a.js-hide-all-checklist-items
i.fa.fa-ban
| {{_ "hideAllChecklistItems"}} ...
.material-toggle-switch(title="{{_ 'hideAllChecklistItems'}}")
if checklist.hideAllChecklistItems
input.toggle-switch(type="checkbox" id="toggleHideAllChecklistItems_{{checklist._id}}" checked="checked")
else
input.toggle-switch(type="checkbox" id="toggleHideAllChecklistItems_{{checklist._id}}")
label.toggle-label(for="toggleHideAllChecklistItems_{{checklist._id}}")
template(name="copyChecklistPopup") template(name="copyChecklistPopup")
+copyAndMoveChecklist +copyAndMoveChecklist

View file

@ -119,7 +119,6 @@ BlazeComponent.extendComponent({
event.preventDefault(); event.preventDefault();
const textarea = this.find('textarea.js-add-checklist-item'); const textarea = this.find('textarea.js-add-checklist-item');
const newlineBecomesNewChecklistItem = this.find('input#toggleNewlineBecomesNewChecklistItem'); const newlineBecomesNewChecklistItem = this.find('input#toggleNewlineBecomesNewChecklistItem');
const newlineBecomesNewChecklistItemOriginOrder = this.find('input#toggleNewlineBecomesNewChecklistItemOriginOrder');
const title = textarea.value.trim(); const title = textarea.value.trim();
const checklist = this.currentData().checklist; const checklist = this.currentData().checklist;
@ -128,28 +127,22 @@ BlazeComponent.extendComponent({
if (newlineBecomesNewChecklistItem.checked) { if (newlineBecomesNewChecklistItem.checked) {
checklistItems = title.split('\n').map(_value => _value.trim()); checklistItems = title.split('\n').map(_value => _value.trim());
if (this.currentData().position === 'top') { if (this.currentData().position === 'top') {
if (newlineBecomesNewChecklistItemOriginOrder.checked === false) { checklistItems = checklistItems.reverse();
checklistItems = checklistItems.reverse();
}
} }
} }
let addIndex;
let sortIndex;
if (this.currentData().position === 'top') {
sortIndex = Utils.calculateIndexData(null, checklist.firstItem()).base;
addIndex = -1;
} else {
sortIndex = Utils.calculateIndexData(checklist.lastItem(), null).base;
addIndex = 1;
}
for (let checklistItem of checklistItems) { for (let checklistItem of checklistItems) {
let sortIndex;
if (this.currentData().position === 'top') {
sortIndex = Utils.calculateIndexData(null, checklist.firstItem()).base;
} else {
sortIndex = Utils.calculateIndexData(checklist.lastItem(), null).base;
}
ChecklistItems.insert({ ChecklistItems.insert({
title: checklistItem, title: checklistItem,
checklistId: checklist._id, checklistId: checklist._id,
cardId: checklist.cardId, cardId: checklist.cardId,
sort: sortIndex, sort: sortIndex,
}); });
sortIndex += addIndex;
} }
} }
// We keep the form opened, empty it. // We keep the form opened, empty it.
@ -208,8 +201,15 @@ BlazeComponent.extendComponent({
}, },
events() { events() {
const events = {
'click #toggleHideCheckedItemsButton'() {
Meteor.call('toggleHideCheckedItems');
},
};
return [ return [
{ {
...events,
'click .js-open-checklist-details-menu': Popup.open('checklistActions'), 'click .js-open-checklist-details-menu': Popup.open('checklistActions'),
'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,
@ -220,10 +220,6 @@ BlazeComponent.extendComponent({
'focus .js-add-checklist-item': this.focusChecklistItem, 'focus .js-add-checklist-item': this.focusChecklistItem,
// add and delete checklist / checklist-item // add and delete checklist / checklist-item
'click .js-open-inlined-form': this.closeAllInlinedForms, 'click .js-open-inlined-form': this.closeAllInlinedForms,
'click #toggleHideFinishedChecklist'(event) {
event.preventDefault();
this.data().card.toggleHideFinishedChecklist();
},
keydown: this.pressKey, keydown: this.pressKey,
}, },
]; ];
@ -278,6 +274,11 @@ Template.checklists.helpers({
const ret = card.checklists(); const ret = card.checklists();
return ret; return ret;
}, },
hideCheckedItems() {
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) return currentUser.hasHideCheckedItems();
return false;
},
}); });
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
@ -312,16 +313,6 @@ BlazeComponent.extendComponent({
}), }),
'click .js-move-checklist': Popup.open('moveChecklist'), 'click .js-move-checklist': Popup.open('moveChecklist'),
'click .js-copy-checklist': Popup.open('copyChecklist'), 'click .js-copy-checklist': Popup.open('copyChecklist'),
'click .js-hide-checked-checklist-items'(event) {
event.preventDefault();
this.data().checklist.toggleHideCheckedChecklistItems();
Popup.back();
},
'click .js-hide-all-checklist-items'(event) {
event.preventDefault();
this.data().checklist.toggleHideAllChecklistItems();
Popup.back();
},
} }
] ]
} }
@ -347,6 +338,11 @@ BlazeComponent.extendComponent({
}).register('editChecklistItemForm'); }).register('editChecklistItemForm');
Template.checklistItemDetail.helpers({ Template.checklistItemDetail.helpers({
hideCheckedItems() {
const user = ReactiveCache.getCurrentUser();
if (user) return user.hasHideCheckedItems();
return false;
},
}); });
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({

View file

@ -37,4 +37,5 @@ template(name="cardLabelsPopup")
= name = name
if(isLabelSelected ../_id) if(isLabelSelected ../_id)
i.card-label-selectable-icon.fa.fa-check i.card-label-selectable-icon.fa.fa-check
a.quiet-button.full.js-add-label {{_ 'label-create'}} if currentUser.isBoardAdmin
a.quiet-button.full.js-add-label {{_ 'label-create'}}

View file

@ -47,12 +47,10 @@
float: right; float: right;
font-size: 18px; font-size: 18px;
padding-right: 30px; padding-right: 30px;
padding-left: 5px;
} }
.minicard-details-menu { .minicard-details-menu {
float: right; float: right;
font-size: 18px; font-size: 18px;
padding-left: 5px;
} }
@media print { @media print {
.minicard-details-menu, .minicard-details-menu,
@ -92,7 +90,7 @@
background-size: contain; background-size: contain;
height: 145px; height: 145px;
user-select: none; user-select: none;
margin: 6px -8px 6px -8px; margin: -6px -8px 6px -8px;
border-radius: top 2px; border-radius: top 2px;
} }
.minicard .minicard-labels { .minicard .minicard-labels {
@ -158,6 +156,7 @@
.minicard .minicard-title .viewer { .minicard .minicard-title .viewer {
display: block; display: block;
word-wrap: break-word; word-wrap: break-word;
max-width: 230px;
} }
} }
.minicard .dates { .minicard .dates {
@ -249,7 +248,6 @@
} }
.minicard .minicard-description { .minicard .minicard-description {
padding: 6px 0 0 8px; padding: 6px 0 0 8px;
color: #000;
background-color: #eee; background-color: #eee;
width: 100%; width: 100%;
margin-bottom: 2px; margin-bottom: 2px;

View file

@ -1,15 +1,14 @@
template(name="minicard") template(name="minicard")
.minicard.nodragscroll( .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="{{#if colorClass}}minicard-{{colorClass}}{{/if}}") class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
if canModifyCard if isTouchScreenOrShowDesktopDragHandles
if isTouchScreenOrShowDesktopDragHandles a.fa.fa-navicon.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
a.fa.fa-navicon.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") .handle
.handle .fa.fa-arrows
.fa.fa-arrows else
else a.fa.fa-navicon.minicard-details-menu.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
a.fa.fa-navicon.minicard-details-menu.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
.dates .dates
if getReceived if getReceived
unless getStart unless getStart
@ -124,15 +123,15 @@ template(name="minicard")
each getMembers each getMembers
+userAvatar(userId=this) +userAvatar(userId=this)
if showCreatorOnMinicard if showCreator
.minicard-creator .minicard-creator
+userAvatar(userId=this.userId noRemove=true) +userAvatar(userId=this.userId noRemove=true)
.badges .badges
if canModifyCard unless currentUser.isNoComments
if comments.length if comments.length
.badge(title="{{_ 'card-comments-title' comments.length }}") .badge(title="{{_ 'card-comments-title' comments.length }}")
span.badge-icon.fa.fa-comment-o.badge-comment.badge-text span.badge-icon.fa.fa-comment-o.badge-comment
= ' ' = ' '
= comments.length = comments.length
//span.badge-comment.badge-text //span.badge-comment.badge-text
@ -184,11 +183,12 @@ template(name="editCardSortOrderPopup")
template(name="minicardDetailsActionsPopup") template(name="minicardDetailsActionsPopup")
ul.pop-over-list ul.pop-over-list
if canModifyCard if currentUser.isBoardAdmin
li li
a.js-move-card a.js-move-card
i.fa.fa-arrow-right i.fa.fa-arrow-right
| {{_ 'moveCardPopup-title'}} | {{_ 'moveCardPopup-title'}}
unless currentUser.isWorker
li li
a.js-copy-card a.js-copy-card
i.fa.fa-copy i.fa.fa-copy

View file

@ -37,12 +37,16 @@ BlazeComponent.extendComponent({
return ret; return ret;
}, },
showCreatorOnMinicard() { showCreator() {
// cache "board" to reduce the mini-mongodb access // cache "board" to reduce the mini-mongodb access
const board = this.data().board(); const board = this.data().board();
let ret = false; let ret = false;
if (board) { if (board) {
ret = board.allowsCreatorOnMinicard ?? false; ret =
board.allowsCreator === null ||
board.allowsCreator === undefined ||
board.allowsCreator
;
} }
return ret; return ret;
}, },

View file

@ -26,7 +26,8 @@ template(name="subtaskDetail")
.subtask-title .subtask-title
span span
if canModifyCard if canModifyCard
a.fa.fa-navicon.subtask-details-menu.js-open-subtask-details-menu(title="{{_ 'subtaskActionsPopup-title'}}") if currentUser.isBoardAdmin
a.fa.fa-navicon.subtask-details-menu.js-open-subtask-details-menu(title="{{_ 'subtaskActionsPopup-title'}}")
if canModifyCard if canModifyCard
h2.title.js-open-inlined-form.is-editable h2.title.js-open-inlined-form.is-editable
+viewer +viewer
@ -94,8 +95,7 @@ template(name="subtaskActionsPopup")
a.js-view-subtask(title="{{ subtask.title }}") a.js-view-subtask(title="{{ subtask.title }}")
i.fa.fa-eye i.fa.fa-eye
| {{_ "view-it"}} | {{_ "view-it"}}
if currentUser.isBoardAdmin a.js-delete-subtask.delete-subtask
a.js-delete-subtask.delete-subtask i.fa.fa-trash
i.fa.fa-trash | {{_ "delete"}} ...
| {{_ "delete"}} ...

View file

@ -68,10 +68,6 @@ BlazeComponent.extendComponent({
} }
}, },
isBoardAdmin() {
return ReactiveCache.getCurrentUser().isBoardAdmin();
},
editSubtask(event) { editSubtask(event) {
event.preventDefault(); event.preventDefault();
const textarea = this.find('textarea.js-edit-subtask-item'); const textarea = this.find('textarea.js-edit-subtask-item');
@ -108,9 +104,6 @@ BlazeComponent.extendComponent({
}).register('subtaskItemDetail'); }).register('subtaskItemDetail');
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
isBoardAdmin() {
return ReactiveCache.getCurrentUser().isBoardAdmin();
},
events() { events() {
return [ return [
{ {
@ -136,14 +129,3 @@ BlazeComponent.extendComponent({
] ]
} }
}).register('subtaskActionsPopup'); }).register('subtaskActionsPopup');
Template.editSubtaskItemForm.helpers({
user() {
return ReactiveCache.getUser(this.userId);
},
isBoardAdmin() {
return ReactiveCache.getCurrentUser().isBoardAdmin();
},
});

View file

@ -7,16 +7,18 @@
border-left: 1px solid #ccc; border-left: 1px solid #ccc;
padding: 0; padding: 0;
float: left; float: left;
} min-width: 100px; /* TODO(mark-i-m): hardcoded? */
[id^="swimlane-"] .list:first-child { /*max-width: 270px;*/
min-width: 20px; /* Reverted incomplete change list width: */
} /* https://github.com/wekan/wekan/issues/4558 */
.list.list-auto-width { /* Orinal width: 270px. Changes not saved yet: */
flex: 1; /*resize: both; - List width and height resizeable */
/* overflow: auto; - List width and height resizeable */
} }
.list:first-child { .list:first-child {
min-width: 20px;
margin-left: 5px;
border-left: none; border-left: none;
flex: none;
} }
.card-details + .list { .card-details + .list {
border-left: none; border-left: none;
@ -35,9 +37,6 @@
box-shadow: none; box-shadow: none;
height: 100px; height: 100px;
} }
.list.list-collapsed {
flex: none;
}
.list.list-composer .open-list-composer, .list.list-composer .open-list-composer,
.list .list-composer .open-list-composer { .list .list-composer .open-list-composer {
color: #8c8c8c; color: #8c8c8c;
@ -49,7 +48,7 @@
} }
.list-header-add { .list-header-add {
flex: 0 0 auto; flex: 0 0 auto;
padding: 12px; padding: 20px 12px 4px;
position: relative; position: relative;
min-height: 20px; min-height: 20px;
} }
@ -82,20 +81,6 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
word-wrap: break-word; word-wrap: break-word;
}
.list-rotated {
width: 10px;
height: 250px;
margin-top: -90px;
margin-left: -110px;
margin-right: 0;
transform: rotate(90deg);
position: relative;
text-overflow: ellipsis;
white-space: nowrap;
}
.list-header .list-rotated {
} }
.list-header .list-header-watch-icon { .list-header .list-header-watch-icon {
padding-left: 10px; padding-left: 10px;
@ -114,23 +99,6 @@
color: #a6a6a6; color: #a6a6a6;
margin-right: 15px; margin-right: 15px;
} }
.list-header .list-header-collapse-right {
color: #a6a6a6;
}
.list-header .list-header-collapse-left {
color: #a6a6a6;
margin-right: 15px;
}
.list-header .list-header-uncollapse-left {
color: #a6a6a6;
}
.list-header .list-header-uncollapse-right {
color: #a6a6a6;
}
.list-header .list-header-collapse {
color: #a6a6a6;
margin-right: 15px;
}
.list-header .highlight { .list-header .highlight {
color: #ce1414; color: #ce1414;
} }
@ -252,11 +220,11 @@
padding: 15px 19px; padding: 15px 19px;
} }
.list-header { .list-header {
/*Updated padding values for mobile devices, this should fix text grouping issue*/ padding: 0 12px 0px;
padding: 20px 0px 20px 0px;
border-bottom: 0px solid #e4e4e4; border-bottom: 0px solid #e4e4e4;
min-height: 30px; height: 60px;
margin-top: 10px; margin-top: 10px;
display: flex;
align-items: center; align-items: center;
} }
.list-header .list-header-left-icon { .list-header .list-header-left-icon {
@ -329,6 +297,7 @@
} }
.list-header-white { .list-header-white {
border-bottom: 6px solid #fff; border-bottom: 6px solid #fff;
border: 1px solid #eee;
} }
.list-header-green { .list-header-green {
border-bottom: 6px solid #3cb500; border-bottom: 6px solid #3cb500;
@ -361,7 +330,7 @@
border-bottom: 6px solid #51e898; border-bottom: 6px solid #51e898;
} }
.list-header-silver { .list-header-silver {
border-bottom: 6px solid #e4e4e4; border-bottom: 6px solid unset;
} }
.list-header-peachpuff { .list-header-peachpuff {
border-bottom: 6px solid #ffdab9; border-bottom: 6px solid #ffdab9;

View file

@ -1,7 +1,6 @@
template(name='list') template(name='list')
.list.js-list(id="js-list-{{_id}}" .list.js-list(id="js-list-{{_id}}"
style="{{#unless collapsed}}min-width:{{listWidth}}px;max-width:{{listConstraint}}px;{{/unless}}" style="width:{{listWidth}}px;")
class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}}")
+listHeader +listHeader
+listBody +listBody

View file

@ -196,22 +196,10 @@ BlazeComponent.extendComponent({
}, },
listWidth() { listWidth() {
const user = ReactiveCache.getCurrentUser(); const user = Meteor.user();
const list = Template.currentData(); const list = Template.currentData();
return user.getListWidth(list.boardId, list._id); return user.getListWidth(list.boardId, list._id);
}, },
listConstraint() {
const user = ReactiveCache.getCurrentUser();
const list = Template.currentData();
return user.getListConstraint(list.boardId, list._id);
},
autoWidth() {
const user = ReactiveCache.getCurrentUser();
const list = Template.currentData();
return user.isAutoWidth(list.boardId);
},
}).register('list'); }).register('list');
Template.miniList.events({ Template.miniList.events({

View file

@ -1,38 +1,37 @@
template(name="listBody") template(name="listBody")
unless collapsed .list-body
.list-body(class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}") .minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}")
.minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}") if cards.length
if cards.length +inlinedForm(autoclose=false position="top")
+inlinedForm(autoclose=false position="top") +addCardForm(listId=_id position="top")
+addCardForm(listId=_id position="top") ul.sidebar-list
ul.sidebar-list each customFieldsSum
each customFieldsSum li
li +viewer
= name
if $eq customFieldsSum.type "number"
+viewer +viewer
= name = value
if $eq customFieldsSum.type "number" if $eq customFieldsSum.type "currency"
+viewer +viewer
= value = formattedCurrencyCustomFieldValue(value)
if $eq customFieldsSum.type "currency" each (cardsWithLimit (idOrNull ../../_id))
+viewer a.minicard-wrapper.js-minicard(href=originRelativeUrl
= formattedCurrencyCustomFieldValue(value) class="{{#if cardIsSelected}}is-selected{{/if}}"
each (cardsWithLimit (idOrNull ../../_id)) class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
a.minicard-wrapper.js-minicard(href=originRelativeUrl if MultiSelection.isActive
class="{{#if cardIsSelected}}is-selected{{/if}}" .materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection(
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
if MultiSelection.isActive +minicard(this)
.materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection( if (showSpinner (idOrNull ../../_id))
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") +spinnerList
+minicard(this)
if (showSpinner (idOrNull ../../_id))
+spinnerList
if canSeeAddCard if canSeeAddCard
+inlinedForm(autoclose=false position="bottom") +inlinedForm(autoclose=false position="bottom")
+addCardForm(listId=_id position="bottom") +addCardForm(listId=_id position="bottom")
else else
a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}") a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}")
i.fa.fa-plus i.fa.fa-plus
template(name="spinnerList") template(name="spinnerList")
.sk-spinner.sk-spinner-list( .sk-spinner.sk-spinner-list(
@ -80,18 +79,18 @@ template(name="linkCardPopup")
select.js-select-boards select.js-select-boards
option(value="") option(value="")
each boards each boards
option(value="{{_id}}") {{isTitleDefault title}} option(value="{{_id}}") {{title}}
input.primary.confirm.js-link-board(type="button" value="{{_ 'link'}}") input.primary.confirm.js-link-board(type="button" value="{{_ 'link'}}")
label {{_ 'swimlanes'}}: label {{_ 'swimlanes'}}:
select.js-select-swimlanes select.js-select-swimlanes
each swimlanes each swimlanes
option(value="{{_id}}") {{isTitleDefault title}} option(value="{{_id}}") {{title}}
label {{_ 'lists'}}: label {{_ 'lists'}}:
select.js-select-lists select.js-select-lists
each lists each lists
option(value="{{_id}}") {{isTitleDefault title}} option(value="{{_id}}") {{title}}
label {{_ 'cards'}}: label {{_ 'cards'}}:
select.js-select-cards select.js-select-cards

View file

@ -231,11 +231,6 @@ BlazeComponent.extendComponent({
); );
}, },
isVerticalScrollbars() {
const user = ReactiveCache.getCurrentUser();
return user && user.isVerticalScrollbars();
},
cardDetailsPopup(event) { cardDetailsPopup(event) {
if (!Popup.isOpen()) { if (!Popup.isOpen()) {
Popup.open("cardDetails")(event); Popup.open("cardDetails")(event);
@ -598,31 +593,6 @@ BlazeComponent.extendComponent({
}, },
}).register('linkCardPopup'); }).register('linkCardPopup');
Template.linkCardPopup.helpers({
isTitleDefault(title) {
// https://github.com/wekan/wekan/issues/4763
// https://github.com/wekan/wekan/issues/4742
// Translation text for "default" does not work, it returns an object.
// When that happens, try use translation "defaultdefault" that has same content of default, or return text "Default".
// This can happen, if swimlane does not have name.
// Yes, this is fixing the symptom (Swimlane title does not have title)
// instead of fixing the problem (Add Swimlane title when creating swimlane)
// because there could be thousands of swimlanes, adding name Default to all of them
// would be very slow.
if (title.startsWith("key 'default") && title.endsWith('returned an object instead of string.')) {
if (`${TAPi18n.__('defaultdefault')}`.startsWith("key 'default") && `${TAPi18n.__('defaultdefault')}`.endsWith('returned an object instead of string.')) {
return 'Default';
} else {
return `${TAPi18n.__('defaultdefault')}`;
}
} else if (title === 'Default') {
return `${TAPi18n.__('defaultdefault')}`;
} else {
return title;
}
},
});
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
mixins() { mixins() {
return []; return [];
@ -651,7 +621,6 @@ BlazeComponent.extendComponent({
if (this.isTemplateSearch) { if (this.isTemplateSearch) {
const boardId = (ReactiveCache.getCurrentUser().profile || {}).templatesBoardId; const boardId = (ReactiveCache.getCurrentUser().profile || {}).templatesBoardId;
if (boardId) { if (boardId) {
subManager.subscribe('board', boardId, false);
this.board = ReactiveCache.getBoard(boardId); this.board = ReactiveCache.getBoard(boardId);
} }
} else { } else {

View file

@ -1,5 +1,5 @@
template(name="listHeader") template(name="listHeader")
.list-header.js-list-header.nodragscroll( .list-header.js-list-header(
class="{{#if limitToShowCardsCount}}list-header-card-count{{/if}}" class="{{#if limitToShowCardsCount}}list-header-card-count{{/if}}"
class=colorClass) class=colorClass)
+inlinedForm +inlinedForm
@ -8,40 +8,19 @@ template(name="listHeader")
if isMiniScreen if isMiniScreen
if currentList if currentList
a.list-header-left-icon.fa.fa-angle-left.js-unselect-list a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
else h2.list-header-name(
if collapsed title="{{ moment modifiedAt 'LLL' }}"
a.js-collapse(title="{{_ 'uncollapse'}}") class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
i.fa.fa-arrow-left.list-header-uncollapse-left +viewer
i.fa.fa-arrow-right.list-header-uncollapse-right = title
if showCardsCountForList cards.length if wipLimit.enabled
br |&nbsp;(
span.cardCount {{cardsCount}} span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
if isMiniScreen |/#{wipLimit.value})
h2.list-header-name(
title="{{ moment modifiedAt 'LLL' }}" if showCardsCountForList cards.length
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}") span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
+viewer
= title
if wipLimit.enabled
|&nbsp;(
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|/#{wipLimit.value})
if showCardsCountForList cards.length
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
else
div(class="{{#if collapsed}}list-rotated{{/if}}")
h2.list-header-name(
title="{{ moment modifiedAt 'LLL' }}"
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
+viewer
= title
if wipLimit.enabled
|&nbsp;(
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|/#{wipLimit.value})
unless collapsed
if showCardsCountForList cards.length
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
if isMiniScreen if isMiniScreen
if currentList if currentList
if isWatching if isWatching
@ -57,20 +36,16 @@ template(name="listHeader")
else if currentUser.isBoardMember else if currentUser.isBoardMember
if isWatching if isWatching
i.list-header-watch-icon.fa.fa-eye i.list-header-watch-icon.fa.fa-eye
unless collapsed 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-top(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-top(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.js-collapse(title="{{_ 'collapse'}}") if currentUser.isBoardAdmin
i.fa.fa-arrow-right.list-header-collapse-right if isTouchScreenOrShowDesktopDragHandles
i.fa.fa-arrow-left.list-header-collapse-left a.list-header-handle.handle.fa.fa-arrows.js-list-handle
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
if currentUser.isBoardAdmin
if isTouchScreenOrShowDesktopDragHandles
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
template(name="editListTitleForm") template(name="editListTitleForm")
.list-composer .list-composer
@ -191,14 +166,8 @@ template(name="setListWidthPopup")
label {{_ 'set-list-width-value'}} label {{_ 'set-list-width-value'}}
p p
input.list-width-value(type="number" value="{{ listWidthValue }}" min="100") input.list-width-value(type="number" value="{{ listWidthValue }}" min="100")
input.list-constraint-value(type="number" value="{{ listConstraintValue }}" min="100")
input.list-width-apply(type="submit" value="{{_ 'apply'}}") input.list-width-apply(type="submit" value="{{_ 'apply'}}")
input.list-width-error input.list-width-error
br
a.js-auto-width-board(
title="{{#if isAutoWidth}}{{_ 'click-to-disable-auto-width'}}{{else}}{{_ 'click-to-enable-auto-width'}}{{/if}}")
i.fa(class="fa-solid fa-{{#if isAutoWidth}}compress{{else}}expand{{/if}}")
span {{_ 'auto-list-width'}}
template(name="listWidthErrorPopup") template(name="listWidthErrorPopup")
.list-width-invalid .list-width-invalid

View file

@ -1,6 +1,5 @@
import { ReactiveCache } from '/imports/reactiveCache'; import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n'; import { TAPi18n } from '/imports/i18n';
import dragscroll from '@wekanteam/dragscroll';
let listsColors; let listsColors;
Meteor.startup(() => { Meteor.startup(() => {
@ -32,17 +31,6 @@ BlazeComponent.extendComponent({
return !status; return !status;
} }
}, },
collapsed(check = undefined) {
const list = Template.currentData();
const status = list.isCollapsed();
if (check === undefined) {
// just check
return status;
} else {
list.collapse(!status);
return !status;
}
},
editTitle(event) { editTitle(event) {
event.preventDefault(); event.preventDefault();
const newTitle = this.childComponents('inlinedForm')[0] const newTitle = this.childComponents('inlinedForm')[0]
@ -116,10 +104,6 @@ BlazeComponent.extendComponent({
event.preventDefault(); event.preventDefault();
this.starred(!this.starred()); this.starred(!this.starred());
}, },
'click .js-collapse'(event) {
event.preventDefault();
this.collapsed(!this.collapsed());
},
'click .js-open-list-menu': Popup.open('listAction'), 'click .js-open-list-menu': Popup.open('listAction'),
'click .js-add-card.list-header-plus-top'(event) { 'click .js-add-card.list-header-plus-top'(event) {
const listDom = $(event.target).parents( const listDom = $(event.target).parents(
@ -156,7 +140,7 @@ Template.listActionPopup.helpers({
isWatching() { isWatching() {
return this.findWatcher(Meteor.userId()); return this.findWatcher(Meteor.userId());
} },
}); });
Template.listActionPopup.events({ Template.listActionPopup.events({
@ -348,20 +332,14 @@ BlazeComponent.extendComponent({
.val(), .val(),
10, 10,
); );
const constraint = parseInt(
Template.instance()
.$('.list-constraint-value')
.val(),
10,
);
// FIXME(mark-i-m): where do we put constants? // FIXME(mark-i-m): where do we put constants?
if (width < 100 || !width || constraint < 100 || !constraint) { if (width < 100 || !width) {
Template.instance() Template.instance()
.$('.list-width-error') .$('.list-width-error')
.click(); .click();
} else { } else {
Meteor.call('applyListWidth', board, list._id, width, constraint); Meteor.call('applyListWidth', board, list._id, width);
Popup.back(); Popup.back();
} }
}, },
@ -369,28 +347,12 @@ BlazeComponent.extendComponent({
listWidthValue() { listWidthValue() {
const list = Template.currentData(); const list = Template.currentData();
const board = list.boardId; const board = list.boardId;
return ReactiveCache.getCurrentUser().getListWidth(board, list._id); return Meteor.user().getListWidth(board, list._id);
},
listConstraintValue() {
const list = Template.currentData();
const board = list.boardId;
return ReactiveCache.getCurrentUser().getListConstraint(board, list._id);
},
isAutoWidth() {
const boardId = Utils.getCurrentBoardId();
const user = ReactiveCache.getCurrentUser();
return user && user.isAutoWidth(boardId);
}, },
events() { events() {
return [ return [
{ {
'click .js-auto-width-board'() {
dragscroll.reset();
ReactiveCache.getCurrentUser().toggleAutoWidth(Utils.getCurrentBoardId());
},
'click .list-width-apply': this.applyListWidth, 'click .list-width-apply': this.applyListWidth,
'click .list-width-error': Popup.open('listWidthError'), 'click .list-width-error': Popup.open('listWidthError'),
}, },

View file

@ -1,74 +0,0 @@
.my-cards-board-wrapper {
border-radius: 0 0 4px 4px;
min-width: 400px;
margin-bottom: 2rem;
margin-right: auto;
margin-left: auto;
border-width: 2px;
border-style: solid;
border-color: #a2a2a2;
}
.my-cards-board-title {
font-size: 1.4rem;
font-weight: bold;
padding: 0.5rem;
background-color: #808080;
color: #fff;
}
.my-cards-swimlane-title {
font-size: 1.1rem;
font-weight: bold;
padding: 0.5rem;
padding-bottom: 0.4rem;
margin-top: 0;
margin-bottom: 0.5rem;
text-align: center;
}
.swimlane-default-color {
background-color: #d3d3d3;
}
.my-cards-list-title {
font-weight: bold;
font-size: 1.1rem;
text-align: center;
margin-bottom: 0.7rem;
}
.my-cards-list-wrapper {
margin: 1rem;
border-radius: 5px;
display: inline-grid;
min-width: 250px;
max-width: 350px;
}
.my-cards-card-wrapper {
margin-top: 0;
margin-bottom: 10px;
}
.my-cards-dueat-list-wrapper {
max-width: 500px;
margin-right: auto;
margin-left: auto;
}
.my-cards-board-table thead {
border-bottom: 3px solid #4d4d4d;
background-color: transparent;
}
.my-cards-board-table th,
.my-cards-board-table td {
border: 0;
}
.my-cards-board-table tr {
border-bottom: 2px solid #a2a2a2;
}
.my-cards-card-title-table {
font-weight: bold;
padding-left: 2px;
max-width: 243px;
}
.my-cards-board-badge {
width: 36px;
height: 24px;
float: left;
border-radius: 5px;
margin-right: 5px;
}

View file

@ -1,8 +0,0 @@
template(name="accessibilityHeaderBar")
if currentUser
h1
| {{_ 'accessibility-title'}}
template(name="accessibility")
if currentUser
| {{_ 'accessibility-content'}}

View file

@ -1,11 +0,0 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
BlazeComponent.extendComponent({
onCreated() {
this.error = new ReactiveVar('');
this.loading = new ReactiveVar(false);
Meteor.subscribe('setting');
},
}).register('accessibility');

View file

@ -1,19 +1,7 @@
.new-comment a.fa.fa-brands.fa-markdown,
.inlined-form a.fa.fa-brands.fa-markdown {
float: right;
position: absolute;
top: -10px;
right: 60px;
}
.new-comment a.fa.fa-copy, .new-comment a.fa.fa-copy,
.inlined-form a.fa.fa-copy { .inlined-form a.fa.fa-copy {
float: right; float: right;
position: relative; position: relative;
top: -10px;
right: 5px;
}
.js-inlined-form.viewer.btn-sm {
position: absolute;
top: 20px; top: 20px;
right: 6px; right: 6px;
} }

View file

@ -1,5 +1,4 @@
template(name="editor") template(name="editor")
a.fa.fa-brands.fa-markdown(title="{{_ 'convert-to-markdown'}}")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}") a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}} span.copied-tooltip {{_ 'copied'}}
textarea.editor( textarea.editor(

View file

@ -1,6 +1,4 @@
import { ReactiveCache } from '/imports/reactiveCache'; import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
var converter = require('@wekanteam/html-to-markdown');
const specialHandles = [ const specialHandles = [
{userId: 'board_members', username: 'board_members'}, {userId: 'board_members', username: 'board_members'},
@ -11,33 +9,6 @@ const specialHandleNames = specialHandles.map(m => m.username);
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
onRendered() { onRendered() {
// Start: Copy <pre> code https://github.com/wekan/wekan/issues/5149
// TODO: Try to make copyPre visible at Card Details after editing or closing editor or Card Details.
// - Also this same TODO below at event, if someone gets it working.
var copy = function(target) {
var textArea = document.createElement('textarea');
textArea.setAttribute('style','width:1px;border:0;opacity:0;');
document.body.appendChild(textArea);
textArea.value = target.innerHTML;
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
}
var pres = document.querySelectorAll(".viewer > pre");
pres.forEach(function(pre){
var button = document.createElement("a");
button.className = "fa fa-copy btn btn-sm right";
// TODO: Translate text 'Copy text to clipboard'
button.setAttribute('title','Copy text to clipboard');
button.innerHTML = '';
pre.parentNode.insertBefore(button, pre);
button.addEventListener('click', function(e){
e.preventDefault();
copy(pre.childNodes[0]);
})
})
// End: Copy <pre> code
const textareaSelector = 'textarea'; const textareaSelector = 'textarea';
const mentions = [ const mentions = [
// User mentions // User mentions
@ -73,14 +44,12 @@ BlazeComponent.extendComponent({
index: 1, index: 1,
}, },
]; ];
const enableTextarea = function() { const enableTextarea = function() {
const $textarea = this.$(textareaSelector); const $textarea = this.$(textareaSelector);
autosize($textarea); autosize($textarea);
$textarea.escapeableTextComplete(mentions); $textarea.escapeableTextComplete(mentions);
}; };
/* if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false) {
if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR === true || Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR === 'true') {
const isSmall = Utils.isMiniScreen(); const isSmall = Utils.isMiniScreen();
const toolbar = isSmall const toolbar = isSmall
? [ ? [
@ -300,8 +269,6 @@ BlazeComponent.extendComponent({
} else { } else {
enableTextarea(); enableTextarea();
} }
*/
enableTextarea();
}, },
events() { events() {
return [ return [
@ -313,14 +280,6 @@ BlazeComponent.extendComponent({
const $tooltip = this.$('.copied-tooltip'); const $tooltip = this.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip); Utils.showCopied(promise, $tooltip);
}, },
'click a.fa.fa-brands.fa-markdown'(event) {
const $editor = this.$('textarea.editor');
$editor[0].value = converter.convert($editor[0].value);
},
// TODO: Try to make copyPre visible at Card Details after editing or closing editor or Card Details.
//'click .js-close-inlined-form'(event) {
// Utils.copyPre();
//},
} }
] ]
} }

View file

@ -446,12 +446,6 @@ a:not(.disabled).is-active i.fa {
padding: 0; padding: 0;
padding-top: 15px; padding-top: 15px;
} }
.no-scrollbars {
scrollbar-width: none;
}
.no-scrollbars::-webkit-scrollbar {
display: none !important;
}
@media screen and (max-width: 800px) { @media screen and (max-width: 800px) {
#content { #content {
margin: 1px 0px 0px 0px; margin: 1px 0px 0px 0px;
@ -476,7 +470,6 @@ a:not(.disabled).is-active i.fa {
.select-authentication { .select-authentication {
width: 100%; width: 100%;
} }
.textBelowCustomLoginLogo,
.auth-layout { .auth-layout {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -37,13 +37,10 @@ template(name="userFormsLayout")
else else
img(src="{{pathFor '/wekan-logo.svg'}}" alt="" width="300" height="auto") img(src="{{pathFor '/wekan-logo.svg'}}" alt="" width="300" height="auto")
br br
if currentSetting.textBelowCustomLoginLogo if currentSetting.textBelowCustomLoginLogo
hr +viewer
section.textBelowCustomLoginLogo | {{currentSetting.textBelowCustomLoginLogo}}
+viewer br
| {{currentSetting.textBelowCustomLoginLogo}}
hr
section.auth-layout
section.auth-dialog section.auth-dialog
if isLoading if isLoading
+loader +loader

View file

@ -70,14 +70,13 @@ template(name="myCards")
unless isMiniScreen unless isMiniScreen
.my-cards-board-badge(class=board.colorClass, id="header") .my-cards-board-badge(class=board.colorClass, id="header")
.my-cards-card-title-table .my-cards-card-title-table
| {{card.title}} a.minicard-wrapper(href=card.originRelativeUrl)
//a.minicard-wrapper(href=card.originRelativeUrl) | {{card.title}}
// | {{card.title}}
td td
| {{list.title}} | {{list.title}}
td td
a(href=board.originRelativeUrl)
| {{board.title}} | {{board.title}}
//a(href=board.originRelativeUrl)
td td
| {{swimlane.title}} | {{swimlane.title}}

View file

@ -11,7 +11,7 @@ template(name='notificationsDrawer')
a.fa.fa-times-thin.close a.fa.fa-times-thin.close
ul.notifications ul.notifications
each transformedProfile.notifications each transformedProfile.notifications
+notification(activityData=activityObj index=dbIndex read=read) +notification(activityData=activity index=dbIndex read=read)
if($gt unreadNotifications 0) if($gt unreadNotifications 0)
a.all-read {{_ 'mark-all-as-read'}} a.all-read {{_ 'mark-all-as-read'}}
if ($and ($.Session.get 'showReadNotifications') ($gt readNotifications 0)) if ($and ($.Session.get 'showReadNotifications') ($gt readNotifications 0))

View file

@ -58,20 +58,18 @@ template(name="rulesReport")
h1 {{_ 'rulesReportTitle'}} h1 {{_ 'rulesReportTitle'}}
if resultsCount if resultsCount
table table
thead tr
tr th Rule Title
th Rule Title th Board Title
th Board Title th actionType
th actionType th activityType
th activityType
each rule in results each rule in results
tbody tr
tr td {{ rule.title }}
td {{ rule.title }} td {{ rule.boardTitle }}
td {{ rule.boardTitle }} td {{ rule.action.actionType }}
td {{ rule.action.actionType }} td {{ rule.trigger.activityType }}
td {{ rule.trigger.activityType }}
else else
div {{_ 'no-results' }} div {{_ 'no-results' }}
@ -79,24 +77,22 @@ template(name="filesReport")
h1 {{_ 'filesReportTitle'}} h1 {{_ 'filesReportTitle'}}
if resultsCount if resultsCount
table table
thead tr
tr th Filename
th Filename th.right Size (kB)
th.right Size (kB) th MIME Type
th MIME Type th Attachment ID
th Attachment ID th Board ID
th Board ID th Card ID
th Card ID
each att in results each att in results
tbody tr
tr td {{ att.name }}
td {{ att.name }} td.right {{ fileSize att.size }}
td.right {{ fileSize att.size }} td {{ att.type }}
td {{ att.type }} td {{ att._id }}
td {{ att._id }} td {{ att.meta.boardId }}
td {{ att.meta.boardId }} td {{ att.meta.cardId }}
td {{ att.meta.cardId }}
else else
div {{_ 'no-results' }} div {{_ 'no-results' }}
@ -104,24 +100,22 @@ template(name="cardsReport")
h1 {{_ 'cardsReportTitle'}} h1 {{_ 'cardsReportTitle'}}
if resultsCount if resultsCount
table.table table.table
thead tr
tr th Card Title
th Card Title th Board
th Board th Swimlane
th Swimlane th List
th List th Members
th Members th Assignees
th Assignees
each card in results each card in results
tbody tr
tr td {{abbreviate card.title }}
td {{abbreviate card.title }} td {{abbreviate card.board.title }}
td {{abbreviate card.board.title }} td {{abbreviate card.swimlane.title }}
td {{abbreviate card.swimlane.title }} td {{abbreviate card.list.title }}
td {{abbreviate card.list.title }} td {{userNames card.members }}
td {{userNames card.members }} td {{userNames card.assignees }}
td {{userNames card.assignees }}
else else
div {{_ 'no-results' }} div {{_ 'no-results' }}
@ -129,25 +123,22 @@ template(name="boardsReport")
h1 {{_ 'boardsReportTitle'}} h1 {{_ 'boardsReportTitle'}}
if resultsCount if resultsCount
table.table table.table
thead tr
tr th Title
th Title th Id
th Id th Permission
th Permission th Archived?
th Archived? th Members
th Members th Organizations
th Organizations th Teams
th Teams
each board in results each board in results
tbody tr
tr td {{abbreviate board.title }}
td {{abbreviate board.title }} td {{abbreviate board._id }}
td {{abbreviate board._id }} td {{ board.permission }}
td {{ board.permission }} td
td = yesOrNo(board.archived)
= yesOrNo(board.archived) td {{userNames board.members }}
td {{userNames board.members }}
td {{orgs board.orgs }}
td {{teams board.teams }}
else else
div {{_ 'no-results' }} div {{_ 'no-results' }}

View file

@ -170,27 +170,8 @@ class AdminReport extends BlazeComponent {
.join(", "); .join(", ");
return ret; return ret;
} }
teams(memberTeams) {
const ret = (memberTeams || [])
.map(_memberTeam => {
const _ret = ReactiveCache.getTeam(_memberTeam.teamId)?.teamDisplayName || _memberTeam.teamId;
return _ret;
})
.join(", ");
return ret;
}
orgs(orgs) {
const ret = (orgs || [])
.map(_orgs => {
const _ret = ReactiveCache.getOrg(_orgs.orgId)?.orgDisplayName || _orgs.orgId;
return _ret;
})
.join(", ");
return ret;
}
}.register('boardsReport')); }.register('boardsReport'));
(class extends AdminReport { (class extends AdminReport {
collection = Cards; collection = Cards;

View file

@ -73,25 +73,22 @@ template(name="people")
template(name="orgGeneral") template(name="orgGeneral")
table table
thead tbody
tr tr
th {{_ 'displayName'}} th {{_ 'displayName'}}
th {{_ 'description'}} th {{_ 'description'}}
th {{_ 'shortName'}} th {{_ 'shortName'}}
th {{_ 'autoAddUsersWithDomainName'}}
th {{_ 'website'}} th {{_ 'website'}}
th {{_ 'createdAt'}} th {{_ 'createdAt'}}
th {{_ 'active'}} th {{_ 'active'}}
th th
+newOrgRow +newOrgRow
tbody
tr
each org in orgList each org in orgList
+orgRow(orgId=org._id) +orgRow(orgId=org._id)
template(name="teamGeneral") template(name="teamGeneral")
table table
thead tbody
tr tr
th {{_ 'displayName'}} th {{_ 'displayName'}}
th {{_ 'description'}} th {{_ 'description'}}
@ -101,8 +98,6 @@ template(name="teamGeneral")
th {{_ 'active'}} th {{_ 'active'}}
th th
+newTeamRow +newTeamRow
tbody
tr
each team in teamList each team in teamList
+teamRow(teamId=team._id) +teamRow(teamId=team._id)
@ -110,7 +105,7 @@ template(name="peopleGeneral")
#divAddOrRemoveTeamContainer #divAddOrRemoveTeamContainer
+modifyTeamsUsers +modifyTeamsUsers
table table
thead tbody
tr tr
th th
+selectAllUser +selectAllUser
@ -128,8 +123,6 @@ template(name="peopleGeneral")
th {{_ 'teams'}} th {{_ 'teams'}}
th th
+newUserRow +newUserRow
tbody
tr
each user in peopleList each user in peopleList
+peopleRow(userId=user._id) +peopleRow(userId=user._id)
@ -166,10 +159,6 @@ template(name="orgRow")
td {{ orgData.orgShortName }} td {{ orgData.orgShortName }}
else else
td <s>{{ orgData.orgShortName }}</s> td <s>{{ orgData.orgShortName }}</s>
if orgData.orgIsActive
td {{ orgData.orgAutoAddUsersWithDomainName }}
else
td <s>{{ orgData.orgAutoAddUsersWithDomainName }}</s>
if orgData.orgIsActive if orgData.orgIsActive
td {{ orgData.orgWebsite }} td {{ orgData.orgWebsite }}
else else
@ -318,12 +307,9 @@ template(name="editOrgPopup")
label label
| {{_ 'shortName'}} | {{_ 'shortName'}}
input.js-orgShortName(type="text" value=org.orgShortName required) input.js-orgShortName(type="text" value=org.orgShortName required)
label
| {{_ 'autoAddUsersWithDomainName'}}
input.js-orgAutoAddUsersWithDomainName(type="text" value=org.orgAutoAddUsersWithDomainName)
label label
| {{_ 'website'}} | {{_ 'website'}}
input.js-orgWebsite(type="text" value=org.orgWebsite) input.js-orgWebsite(type="text" value=org.orgWebsite required)
label label
| {{_ 'active'}} | {{_ 'active'}}
select.select-active.js-org-isactive select.select-active.js-org-isactive
@ -349,7 +335,7 @@ template(name="editTeamPopup")
input.js-teamShortName(type="text" value=team.teamShortName required) input.js-teamShortName(type="text" value=team.teamShortName required)
label label
| {{_ 'website'}} | {{_ 'website'}}
input.js-teamWebsite(type="text" value=team.teamWebsite) input.js-teamWebsite(type="text" value=team.teamWebsite required)
label label
| {{_ 'active'}} | {{_ 'active'}}
select.select-active.js-team-isactive select.select-active.js-team-isactive
@ -419,7 +405,7 @@ template(name="editUserPopup")
each value in orgsDatas each value in orgsDatas
option(value="{{value._id}}") {{value.orgDisplayName}} option(value="{{value._id}}") {{value.orgDisplayName}}
input#jsUserOrgsInPut.js-userOrgs(type="text" value=user.orgsUserBelongs, disabled) input#jsUserOrgsInPut.js-userOrgs(type="text" value=user.orgsUserBelongs, disabled)
input#jsUserOrgIdsInPut.js-userOrgIds.hide(type="hidden" value=user.orgIdsUserBelongs) input#jsUserOrgIdsInPut.js-userOrgIds.hide(type="text" value=user.orgIdsUserBelongs)
label label
| {{_ 'teams'}} | {{_ 'teams'}}
i.fa.fa-plus-square#addUserTeam i.fa.fa-plus-square#addUserTeam
@ -429,7 +415,7 @@ template(name="editUserPopup")
each value in teamsDatas each value in teamsDatas
option(value="{{value._id}}") {{_ value.teamDisplayName}} option(value="{{value._id}}") {{_ value.teamDisplayName}}
input#jsUserTeamsInPut.js-userteams(type="text" value=user.teamsUserBelongs, disabled) input#jsUserTeamsInPut.js-userteams(type="text" value=user.teamsUserBelongs, disabled)
input#jsUserTeamIdsInPut.js-userteamIds.hide(type="hidden" value=user.teamIdsUserBelongs) input#jsUserTeamIdsInPut.js-userteamIds.hide(type="text" value=user.teamIdsUserBelongs)
hr hr
label label
@ -450,9 +436,6 @@ template(name="newOrgPopup")
label label
| {{_ 'shortName'}} | {{_ 'shortName'}}
input.js-orgShortName(type="text" value="" required) input.js-orgShortName(type="text" value="" required)
label
| {{_ 'autoAddUsersWithDomainName'}}
input.js-orgAutoAddUsersWithDomainName(type="text" value="")
label label
| {{_ 'website'}} | {{_ 'website'}}
input.js-orgWebsite(type="text" value="" required) input.js-orgWebsite(type="text" value="" required)
@ -479,7 +462,7 @@ template(name="newTeamPopup")
input.js-teamShortName(type="text" value="" required) input.js-teamShortName(type="text" value="" required)
label label
| {{_ 'website'}} | {{_ 'website'}}
input.js-teamWebsite(type="text" value="") input.js-teamWebsite(type="text" value="" required)
label label
| {{_ 'active'}} | {{_ 'active'}}
select.select-active.js-team-isactive select.select-active.js-team-isactive
@ -500,9 +483,9 @@ template(name="modifyTeamsUsers")
| {{_ 'r-action'}} | {{_ 'r-action'}}
.form-group.flex .form-group.flex
input.wekan-form-control#addAction(type="radio" name="action" value="true" checked="checked") input.wekan-form-control#addAction(type="radio" name="action" value="true" checked="checked")
label(for=addAction) {{_ 'add'}} span {{_ 'add'}}
input.wekan-form-control#deleteAction(type="radio" name="action" value="false") input.wekan-form-control#deleteAction(type="radio" name="action" value="false")
label(for=deleteAction) {{_ 'delete'}} span {{_ 'delete'}}
div.buttonsContainer div.buttonsContainer
input.primary.wide#addTeamBtn(type="submit" value="{{_ 'save'}}") input.primary.wide#addTeamBtn(type="submit" value="{{_ 'save'}}")
input.primary.wide#cancelBtn(type="submit" value="{{_ 'cancel'}}") input.primary.wide#cancelBtn(type="submit" value="{{_ 'cancel'}}")

View file

@ -576,14 +576,12 @@ Template.editOrgPopup.events({
.value.trim(); .value.trim();
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 orgAutoAddUsersWithDomainName = templateInstance.find('.js-orgAutoAddUsersWithDomainName').value.trim();
const orgWebsite = templateInstance.find('.js-orgWebsite').value.trim(); const orgWebsite = templateInstance.find('.js-orgWebsite').value.trim();
const orgIsActive = templateInstance.find('.js-org-isactive').value.trim() == 'true'; const orgIsActive = 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;
const isChangeOrgShortName = orgShortName !== org.orgShortName; const isChangeOrgShortName = orgShortName !== org.orgShortName;
const isChangeOrgAutoAddUsersWithDomainName = orgAutoAddUsersWithDomainName !== org.orgAutoAddUsersWithDomainName;
const isChangeOrgWebsite = orgWebsite !== org.orgWebsite; const isChangeOrgWebsite = orgWebsite !== org.orgWebsite;
const isChangeOrgIsActive = orgIsActive !== org.orgIsActive; const isChangeOrgIsActive = orgIsActive !== org.orgIsActive;
@ -591,7 +589,6 @@ Template.editOrgPopup.events({
isChangeOrgDisplayName || isChangeOrgDisplayName ||
isChangeOrgDesc || isChangeOrgDesc ||
isChangeOrgShortName || isChangeOrgShortName ||
isChangeOrgAutoAddUsersWithDomainName ||
isChangeOrgWebsite || isChangeOrgWebsite ||
isChangeOrgIsActive isChangeOrgIsActive
) { ) {
@ -601,7 +598,6 @@ Template.editOrgPopup.events({
orgDisplayName, orgDisplayName,
orgDesc, orgDesc,
orgShortName, orgShortName,
orgAutoAddUsersWithDomainName,
orgWebsite, orgWebsite,
orgIsActive, orgIsActive,
); );
@ -924,7 +920,6 @@ Template.newOrgPopup.events({
.value.trim(); .value.trim();
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 orgAutoAddUsersWithDomainName = templateInstance.find('.js-orgAutoAddUsersWithDomainName').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';
@ -934,7 +929,6 @@ Template.newOrgPopup.events({
orgDisplayName, orgDisplayName,
orgDesc, orgDesc,
orgShortName, orgShortName,
orgAutoAddUsersWithDomainName,
orgWebsite, orgWebsite,
orgIsActive, orgIsActive,
); );

View file

@ -7,13 +7,11 @@
display: -moz-flex; display: -moz-flex;
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
height: 100%;
} }
.setting-content { .setting-content {
color: #727479; color: #727479;
background: #dedede; background: #dedede;
width: 100%; width: 100%;
height: 100%;
position: absolute; position: absolute;
} }
.setting-content .content-title { .setting-content .content-title {
@ -58,8 +56,6 @@
-moz-user-select: text; -moz-user-select: text;
-ms-user-select: text; -ms-user-select: text;
user-select: text; user-select: text;
max-height: 100%;
overflow: auto;
} }
.setting-content .content-body .main-body ul li { .setting-content .content-body .main-body ul li {
padding: 0.5rem 0.5rem; padding: 0.5rem 0.5rem;
@ -72,31 +68,26 @@
padding: 0 0.5rem; padding: 0 0.5rem;
} }
.setting-content .content-body .main-body ul li .admin-announcement, .setting-content .content-body .main-body ul li .admin-announcement,
.setting-content .content-body .main-body ul li .admin-accessibility,
.setting-content .content-body .main-body ul li .invite-people, .setting-content .content-body .main-body ul li .invite-people,
.setting-content .content-body .main-body ul li .layout { .setting-content .content-body .main-body ul li .layout {
padding-left: 20px; padding-left: 20px;
} }
.setting-content .content-body .main-body ul li .admin-announcement li, .setting-content .content-body .main-body ul li .admin-announcement li,
.setting-content .content-body .main-body ul li .admin-accessibility li,
.setting-content .content-body .main-body ul li .invite-people li, .setting-content .content-body .main-body ul li .invite-people li,
.setting-content .content-body .main-body ul li .layout li { .setting-content .content-body .main-body ul li .layout li {
min-width: 500px; min-width: 500px;
} }
.setting-content .content-body .main-body ul li .admin-announcement li ul.no-margin-bottom, .setting-content .content-body .main-body ul li .admin-announcement li ul.no-margin-bottom,
.setting-content .content-body .main-body ul li .admin-accessibility li ul.no-margin-bottom,
.setting-content .content-body .main-body ul li .invite-people li ul.no-margin-bottom, .setting-content .content-body .main-body ul li .invite-people li ul.no-margin-bottom,
.setting-content .content-body .main-body ul li .layout li ul.no-margin-bottom { .setting-content .content-body .main-body ul li .layout li ul.no-margin-bottom {
margin-bottom: 0; margin-bottom: 0;
} }
.setting-content .content-body .main-body ul li .admin-announcement li .bg-white a, .setting-content .content-body .main-body ul li .admin-announcement li .bg-white a,
.setting-content .content-body .main-body ul li .admin-accessibility li .bg-white a,
.setting-content .content-body .main-body ul li .invite-people li .bg-white a, .setting-content .content-body .main-body ul li .invite-people li .bg-white a,
.setting-content .content-body .main-body ul li .layout li .bg-white a { .setting-content .content-body .main-body ul li .layout li .bg-white a {
background: #f7f7f7; background: #f7f7f7;
} }
.setting-content .content-body .main-body ul li .admin-announcement li .bg-white a.is-checked, .setting-content .content-body .main-body ul li .admin-announcement li .bg-white a.is-checked,
.setting-content .content-body .main-body ul li .admin-accessibility li .bg-white a.is-checked,
.setting-content .content-body .main-body ul li .invite-people li .bg-white a.is-checked, .setting-content .content-body .main-body ul li .invite-people li .bg-white a.is-checked,
.setting-content .content-body .main-body ul li .layout li .bg-white a.is-checked { .setting-content .content-body .main-body ul li .layout li .bg-white a.is-checked {
background: #fff; background: #fff;

View file

@ -30,10 +30,6 @@ template(name="setting")
a.js-setting-menu(data-id="announcement-setting") a.js-setting-menu(data-id="announcement-setting")
i.fa.fa-bullhorn i.fa.fa-bullhorn
| {{_ 'admin-announcement'}} | {{_ 'admin-announcement'}}
//li
// a.js-setting-menu(data-id="accessibility-setting")
// i.fa.fa-universal-access
// | {{_ 'accessibility'}}
li li
a.js-setting-menu(data-id="layout-setting") a.js-setting-menu(data-id="layout-setting")
i.fa.fa-object-group i.fa.fa-object-group
@ -56,8 +52,6 @@ template(name="setting")
+tableVisibilityModeSettings +tableVisibilityModeSettings
else if announcementSetting.get else if announcementSetting.get
+announcementSettings +announcementSettings
else if accessibilitySetting.get
+accessibilitySettings
else if layoutSetting.get else if layoutSetting.get
+layoutSettings +layoutSettings
else if webhookSetting.get else if webhookSetting.get
@ -143,32 +137,34 @@ template(name='tableVisibilityModeSettings')
.title {{_ 'tableVisibilityMode-allowPrivateOnly'}} .title {{_ 'tableVisibilityMode-allowPrivateOnly'}}
.form-group.flex .form-group.flex
input.wekan-form-control#accounts-allowPrivateOnly(type="radio" name="allowPrivateOnly" value="true" checked="{{#if allowPrivateOnly}}checked{{/if}}") input.wekan-form-control#accounts-allowPrivateOnly(type="radio" name="allowPrivateOnly" value="true" checked="{{#if allowPrivateOnly}}checked{{/if}}")
label {{_ 'yes'}} span {{_ 'yes'}}
input.wekan-form-control#accounts-allowPrivateOnly(type="radio" name="allowPrivateOnly" value="false" checked="{{#unless allowPrivateOnly}}checked{{/unless}}") input.wekan-form-control#accounts-allowPrivateOnly(type="radio" name="allowPrivateOnly" value="false" checked="{{#unless allowPrivateOnly}}checked{{/unless}}")
label {{_ 'no'}} span {{_ 'no'}}
button.js-tableVisibilityMode-save.primary {{_ 'save'}} button.js-tableVisibilityMode-save.primary {{_ 'save'}}
template(name='accountSettings') template(name='accountSettings')
ul#account-setting.setting-detail ul#account-setting.setting-detail
li
button.js-all-hide-system-messages.primary {{_ 'hide-system-messages-of-all-users'}}
li.accounts-form li.accounts-form
.title {{_ 'accounts-allowEmailChange'}} .title {{_ 'accounts-allowEmailChange'}}
.form-group.flex .form-group.flex
input.wekan-form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="true" checked="{{#if allowEmailChange}}checked{{/if}}") input.wekan-form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="true" checked="{{#if allowEmailChange}}checked{{/if}}")
label {{_ 'yes'}} span {{_ 'yes'}}
input.wekan-form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="false" checked="{{#unless allowEmailChange}}checked{{/unless}}") input.wekan-form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="false" checked="{{#unless allowEmailChange}}checked{{/unless}}")
label {{_ 'no'}} span {{_ 'no'}}
.title {{_ 'accounts-allowUserNameChange'}} .title {{_ 'accounts-allowUserNameChange'}}
.form-group.flex .form-group.flex
input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="true" checked="{{#if allowUserNameChange}}checked{{/if}}") input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="true" checked="{{#if allowUserNameChange}}checked{{/if}}")
label {{_ 'yes'}} span {{_ 'yes'}}
input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="false" checked="{{#unless allowUserNameChange}}checked{{/unless}}") input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="false" checked="{{#unless allowUserNameChange}}checked{{/unless}}")
label {{_ 'no'}} span {{_ 'no'}}
.title {{_ 'accounts-allowUserDelete'}} .title {{_ 'accounts-allowUserDelete'}}
.form-group.flex .form-group.flex
input.wekan-form-control#accounts-allowUserDelete(type="radio" name="allowUserDelete" value="true" checked="{{#if allowUserDelete}}checked{{/if}}") input.wekan-form-control#accounts-allowUserDelete(type="radio" name="allowUserDelete" value="true" checked="{{#if allowUserDelete}}checked{{/if}}")
label {{_ 'yes'}} span {{_ 'yes'}}
input.wekan-form-control#accounts-allowUserDelete(type="radio" name="allowUserDelete" value="false" checked="{{#unless allowUserDelete}}checked{{/unless}}") input.wekan-form-control#accounts-allowUserDelete(type="radio" name="allowUserDelete" value="false" checked="{{#unless allowUserDelete}}checked{{/unless}}")
label {{_ 'no'}} span {{_ 'no'}}
button.js-accounts-save.primary {{_ 'save'}} button.js-accounts-save.primary {{_ 'save'}}
template(name='announcementSettings') template(name='announcementSettings')
@ -187,33 +183,8 @@ template(name='announcementSettings')
li li
button.js-announcement-save.primary {{_ 'save'}} button.js-announcement-save.primary {{_ 'save'}}
template(name='accessibilitySettings')
ul#accessibility-setting.setting-detail
li
a.flex.js-toggle-accessibility
.materialCheckBox(class="{{#if currentAccessibility.enabled}}is-checked{{/if}}")
span {{_ 'admin-accessibility-active'}}
li
.title {{_ 'accessibility-title'}}
.form-group
input.wekan-form-control#accessibility-title(type="text", placeholder="" value="{{currentSetting.accessibilityTitle}}")
li
.accessibility-content(class="{{#if currentAccessibility.enabled}}{{else}}hide{{/if}}")
ul
li
.title {{_ 'admin-accessibility-title'}}
textarea#admin-accessibility.wekan-form-control= currentAccessibility.accessibilityTitle
li
.title {{_ 'admin-accessibility-content'}}
textarea#admin-accessibility.wekan-form-control= currentAccessibility.accessibilityContent
li
button.js-accessibility-save.primary {{_ 'save'}}
template(name='layoutSettings') template(name='layoutSettings')
ul#layout-setting.setting-detail ul#layout-setting.setting-detail
li
button.js-all-boards-hide-activities.primary {{_ 'hide-activities-of-all-boards'}}
li.layout-form li.layout-form
.title {{_ 'oidc-button-text'}} .title {{_ 'oidc-button-text'}}
.form-group .form-group
@ -230,9 +201,9 @@ template(name='layoutSettings')
.title {{_ 'display-authentication-method'}} .title {{_ 'display-authentication-method'}}
.form-group.flex .form-group.flex
input.wekan-form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="true" checked="{{#if currentSetting.displayAuthenticationMethod}}checked{{/if}}") input.wekan-form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="true" checked="{{#if currentSetting.displayAuthenticationMethod}}checked{{/if}}")
label {{_ 'yes'}} span {{_ 'yes'}}
input.wekan-form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="false" checked="{{#unless currentSetting.displayAuthenticationMethod}}checked{{/unless}}") input.wekan-form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="false" checked="{{#unless currentSetting.displayAuthenticationMethod}}checked{{/unless}}")
label {{_ 'no'}} span {{_ 'no'}}
li.layout-form li.layout-form
.title {{_ 'default-authentication-method'}} .title {{_ 'default-authentication-method'}}
+selectAuthenticationMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod) +selectAuthenticationMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)
@ -247,9 +218,9 @@ template(name='layoutSettings')
.title {{_ 'hide-logo'}} .title {{_ 'hide-logo'}}
.form-group.flex .form-group.flex
input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="true" checked="{{#if currentSetting.hideLogo}}checked{{/if}}") input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="true" checked="{{#if currentSetting.hideLogo}}checked{{/if}}")
label {{_ 'yes'}} span {{_ 'yes'}}
input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="false" checked="{{#unless currentSetting.hideLogo}}checked{{/unless}}") input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="false" checked="{{#unless currentSetting.hideLogo}}checked{{/unless}}")
label {{_ 'no'}} span {{_ 'no'}}
li.layout-form li.layout-form
.title {{_ 'custom-login-logo-image-url'}} .title {{_ 'custom-login-logo-image-url'}}
.form-group .form-group
@ -286,16 +257,16 @@ template(name='layoutSettings')
.title {{_ 'hide-card-counter-list'}} .title {{_ 'hide-card-counter-list'}}
.form-group.flex .form-group.flex
input.wekan-form-control#hide-card-counter-list(type="radio" name="hideCardCounterList" value="true" checked="{{#if currentSetting.hideCardCounterList}}checked{{/if}}") input.wekan-form-control#hide-card-counter-list(type="radio" name="hideCardCounterList" value="true" checked="{{#if currentSetting.hideCardCounterList}}checked{{/if}}")
label {{_ 'yes'}} span {{_ 'yes'}}
input.wekan-form-control#hide-card-counter-list(type="radio" name="hideCardCounterList" value="false" checked="{{#unless currentSetting.hideCardCounterList}}checked{{/unless}}") input.wekan-form-control#hide-card-counter-list(type="radio" name="hideCardCounterList" value="false" checked="{{#unless currentSetting.hideCardCounterList}}checked{{/unless}}")
label {{_ 'no'}} span {{_ 'no'}}
li.layout-form li.layout-form
.title {{_ 'hide-board-member-list'}} .title {{_ 'hide-board-member-list'}}
.form-group.flex .form-group.flex
input.wekan-form-control#hide-board-member-list(type="radio" name="hideBoardMemberList" value="true" checked="{{#if currentSetting.hideBoardMemberList}}checked{{/if}}") input.wekan-form-control#hide-board-member-list(type="radio" name="hideBoardMemberList" value="true" checked="{{#if currentSetting.hideBoardMemberList}}checked{{/if}}")
label {{_ 'yes'}} span {{_ 'yes'}}
input.wekan-form-control#hide-board-member-list(type="radio" name="hideBoardMemberList" value="false" checked="{{#unless currentSetting.hideBoardMemberList}}checked{{/unless}}") input.wekan-form-control#hide-board-member-list(type="radio" name="hideBoardMemberList" value="false" checked="{{#unless currentSetting.hideBoardMemberList}}checked{{/unless}}")
label {{_ 'no'}} span {{_ 'no'}}
li li
button.js-save-layout.primary {{_ 'save'}} button.js-save-layout.primary {{_ 'save'}}

View file

@ -89,9 +89,6 @@ BlazeComponent.extendComponent({
toggleHideBoardMemberList() { toggleHideBoardMemberList() {
$('#hide-board-member-list').toggleClass('is-checked'); $('#hide-board-member-list').toggleClass('is-checked');
}, },
toggleAccessibilityPageEnabled() {
$('#accessibility-page-enabled').toggleClass('is-checked');
},
toggleDisplayAuthenticationMethod() { toggleDisplayAuthenticationMethod() {
$('#display-authentication-method').toggleClass('is-checked'); $('#display-authentication-method').toggleClass('is-checked');
}, },
@ -242,15 +239,7 @@ BlazeComponent.extendComponent({
const displayAuthenticationMethod = const displayAuthenticationMethod =
$('input[name=displayAuthenticationMethod]:checked').val() === 'true'; $('input[name=displayAuthenticationMethod]:checked').val() === 'true';
const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val(); const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
/*
const accessibilityPageEnabled = $('input[name=accessibilityPageEnabled]:checked').val() === 'true';
const accessibilityTitle = $('#accessibility-title')
.val()
.trim();
const accessibilityContent = $('#accessibility-content')
.val()
.trim();
*/
const spinnerName = $('#spinnerName').val(); const spinnerName = $('#spinnerName').val();
try { try {
@ -276,11 +265,6 @@ BlazeComponent.extendComponent({
legalNotice, legalNotice,
}, },
}); });
/*
accessibilityPageEnabled,
accessibilityTitle,
accessibilityContent,
*/
} catch (e) { } catch (e) {
return; return;
} finally { } finally {
@ -317,7 +301,6 @@ BlazeComponent.extendComponent({
'click a.js-toggle-hide-logo': this.toggleHideLogo, 'click a.js-toggle-hide-logo': this.toggleHideLogo,
'click a.js-toggle-hide-card-counter-list': this.toggleHideCardCounterList, 'click a.js-toggle-hide-card-counter-list': this.toggleHideCardCounterList,
'click a.js-toggle-hide-board-member-list': this.toggleHideBoardMemberList, 'click a.js-toggle-hide-board-member-list': this.toggleHideBoardMemberList,
'click a.js-toggle-accessibility-page-enabled': this.toggleAccessibilityPageEnabled,
'click button.js-save-layout': this.saveLayout, 'click button.js-save-layout': this.saveLayout,
'click a.js-toggle-display-authentication-method': this 'click a.js-toggle-display-authentication-method': this
.toggleDisplayAuthenticationMethod, .toggleDisplayAuthenticationMethod,
@ -353,12 +336,12 @@ BlazeComponent.extendComponent({
allowUserDelete() { allowUserDelete() {
return AccountSettings.findOne('accounts-allowUserDelete').booleanValue; return AccountSettings.findOne('accounts-allowUserDelete').booleanValue;
}, },
allBoardsHideActivities() { allHideSystemMessages() {
Meteor.call('setAllBoardsHideActivities', (err, ret) => { Meteor.call('setAllUsersHideSystemMessages', (err, ret) => {
if (!err && ret) { if (!err && ret) {
if (ret === true) { if (ret === true) {
const message = `${TAPi18n.__( const message = `${TAPi18n.__(
'now-activities-of-all-boards-are-hidden', 'now-system-messages-of-all-users-are-hidden',
)}`; )}`;
alert(message); alert(message);
} }
@ -376,7 +359,7 @@ BlazeComponent.extendComponent({
'click button.js-accounts-save': this.saveAccountsChange, 'click button.js-accounts-save': this.saveAccountsChange,
}, },
{ {
'click button.js-all-boards-hide-activities': this.allBoardsHideActivities, 'click button.js-all-hide-system-messages': this.allHideSystemMessages,
}, },
]; ];
}, },
@ -393,12 +376,12 @@ BlazeComponent.extendComponent({
allowPrivateOnly() { allowPrivateOnly() {
return TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue; return TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
}, },
allBoardsHideActivities() { allHideSystemMessages() {
Meteor.call('setAllBoardsHideActivities', (err, ret) => { Meteor.call('setAllUsersHideSystemMessages', (err, ret) => {
if (!err && ret) { if (!err && ret) {
if (ret === true) { if (ret === true) {
const message = `${TAPi18n.__( const message = `${TAPi18n.__(
'now-activities-of-all-boards-are-hidden', 'now-system-messages-of-all-users-are-hidden',
)}`; )}`;
alert(message); alert(message);
} }
@ -416,7 +399,7 @@ BlazeComponent.extendComponent({
'click button.js-tableVisibilityMode-save': this.saveTableVisibilityChange, 'click button.js-tableVisibilityMode-save': this.saveTableVisibilityChange,
}, },
{ {
'click button.js-all-boards-hide-activities': this.allBoardsHideActivities, 'click button.js-all-hide-system-messages': this.allHideSystemMessages,
}, },
]; ];
}, },

View file

@ -34,14 +34,13 @@ template(name="translation")
template(name="translationGeneral") template(name="translationGeneral")
table table
thead tbody
tr tr
th {{_ 'language'}} th {{_ 'language'}}
th {{_ 'text'}} th {{_ 'text'}}
th {{_ 'translation-text'}} th {{_ 'translation-text'}}
th th
+newTranslationRow +newTranslationRow
tbody
each translation in translationList each translation in translationList
+translationRow(translationId=translation._id) +translationRow(translationId=translation._id)

View file

@ -3,31 +3,34 @@
top: 0; top: 0;
bottom: 0; bottom: 0;
right: 0; right: 0;
overflow-y: scroll;
} }
.sidebar { .sidebar .sidebar-shadow {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
background: #f7f7f7; background: #f7f7f7;
box-shadow: -10px 0px 5px -10px #b3b3b3; box-shadow: -10px 0px 5px -10px #b3b3b3;
z-index: 10;
} }
.sidebar-xmark { .sidebar-xmark {
position: absolute; position: absolute;
right: 0px; right: 10px;
top: 0px; top: 5px;
font-size: 25px; font-size: 25px;
padding: 10px;
}
.sidebar-xmark:hover {
background: rgba(0,0,0,0.15);
}
.sidebar-actions {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 10px 10px 0px 10px;
} }
.sidebar .sidebar-content { .sidebar .sidebar-content {
padding: 0 12px; padding: 12px;
margin-bottom: 1.6em;
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
overflow-x: hidden;
overflow-y: auto;
width: 90%;
} }
.sidebar .sidebar-content .hide-btn { .sidebar .sidebar-content .hide-btn {
display: none; display: none;
@ -103,23 +106,21 @@
margin-right: 10px; margin-right: 10px;
} }
.sidebar .sidebar-shortcuts { .sidebar .sidebar-shortcuts {
position: absolute;
margin-left: 40%;
padding: 0;
top: 7px; top: 7px;
font-size: 1em; font-size: 0.8em;
line-height: 1.6em; line-height: 1.6em;
color: #999; color: #999;
} }
.sidebar .sidebar-shortcuts .sidebar-btn {
margin-left: 3px;
margin-right: 3px;
}
.board-sidebar { .board-sidebar {
display: none; width: 548px;
width: 30vw; right: -548px;
z-index: 100;
transition: top 0.1s, right 0.1s, width 0.1s; transition: top 0.1s, right 0.1s, width 0.1s;
} }
.board-sidebar.is-open { .board-sidebar.is-open {
display: block; right: 0;
} }
.board-widget h4 { .board-widget h4 {
margin: 5px 0; margin: 5px 0;
@ -177,7 +178,7 @@
@media screen and (max-width: 800px) { @media screen and (max-width: 800px) {
.board-sidebar { .board-sidebar {
width: 100%; width: 100%;
left: 0; right: -100%;
} }
.board-sidebar .sidebar-content .hide-btn { .board-sidebar .sidebar-content .hide-btn {
width: 40px; width: 40px;

View file

@ -1,61 +1,36 @@
template(name="sidebar") template(name="sidebar")
.board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}} {{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}") .board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}}")
//a.sidebar-tongue.js-toggle-sidebar( //a.sidebar-tongue.js-toggle-sidebar(
// class="{{#if isTongueHidden}}is-hidden{{/if}}", // class="{{#if isTongueHidden}}is-hidden{{/if}}",
// title="{{showTongueTitle}}") // title="{{showTongueTitle}}")
// i.fa.fa-navicon // i.fa.fa-navicon
.sidebar-actions .sidebar-shadow
.sidebar-shortcuts
a.sidebar-btn.js-shortcuts(title="{{_ 'keyboard-shortcuts' }}")
i.fa.fa-keyboard-o
span {{_ 'keyboard-shortcuts' }}
a.sidebar-btn.js-keyboard-shortcuts-toggle(
title="{{#if isKeyboardShortcuts}}{{_ 'keyboard-shortcuts-enabled'}}{{else}}{{_ 'keyboard-shortcuts-disabled'}}{{/if}}")
i.fa(class="fa-solid fa-{{#if isKeyboardShortcuts}}check-square-o{{else}}ban{{/if}}")
a.sidebar-xmark.js-close-sidebar &#10005; a.sidebar-xmark.js-close-sidebar &#10005;
.sidebar-content.js-board-sidebar-content .sidebar-content.js-board-sidebar-content
//a.hide-btn.js-hide-sidebar //a.hide-btn.js-hide-sidebar
// i.fa.fa-navicon // i.fa.fa-navicon
unless isDefaultView unless isDefaultView
h2 h2
a.fa.fa-chevron-left.js-back-home a.fa.fa-chevron-left.js-back-home
= getViewTitle = getViewTitle
if isOpen if isOpen
+Template.dynamic(template=getViewTemplate) +Template.dynamic(template=getViewTemplate)
template(name='homeSidebar') template(name='homeSidebar')
hr hr
+membersWidget +membersWidget
hr hr
+labelsWidget +labelsWidget
hr
ul#cards.label-text-hidden ul#cards.label-text-hidden
a.flex.js-toggle-minicard-label-text(title="{{_ 'hide-minicard-label-text'}}") a.flex.js-toggle-minicard-label-text(title="{{_ 'hide-minicard-label-text'}}")
span {{_ 'hide-minicard-label-text'}} span {{_ 'hide-minicard-label-text'}}
b &nbsp; b &nbsp;
.materialCheckBox(class="{{#if hiddenMinicardLabelText}}is-checked{{/if}}") .materialCheckBox(class="{{#if hiddenMinicardLabelText}}is-checked{{/if}}")
ul#cards.vertical-scrollbars-toggle
a.flex.js-vertical-scrollbars-toggle(title="{{_ 'enable-vertical-scrollbars'}}")
span {{_ 'enable-vertical-scrollbars'}}
b &nbsp;
.materialCheckBox(class="{{#if isVerticalScrollbars}}is-checked{{/if}}")
ul#cards.show-week-of-year-toggle
a.flex.js-show-week-of-year-toggle(title="{{_ 'show-week-of-year'}}")
span {{_ 'show-week-of-year'}}
b &nbsp;
.materialCheckBox(class="{{#if isShowWeekOfYear}}is-checked{{/if}}")
hr hr
unless currentUser.isNoComments unless currentUser.isNoComments
h3.activity-title h3
i.fa.fa-comments-o i.fa.fa-comments-o
| {{_ 'activities'}} | {{_ 'activities'}}
.material-toggle-switch(title="{{_ 'show-activities'}}")
if showActivities
input.toggle-switch(type="checkbox" id="toggleShowActivitiesBoard" checked="checked")
else
input.toggle-switch(type="checkbox" id="toggleShowActivitiesBoard")
label.toggle-label(for="toggleShowActivitiesBoard")
+activities(mode="board") +activities(mode="board")
template(name="membersWidget") template(name="membersWidget")
@ -65,6 +40,11 @@ template(name="membersWidget")
a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}") a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}")
i.board-header-btn-icon.fa.fa-cog i.board-header-btn-icon.fa.fa-cog
| {{_ 'boardMenuPopup-title'}} | {{_ 'boardMenuPopup-title'}}
.board-widget.board-widget-members
.sidebar-shortcuts
a.board-header-btn.js-shortcuts(title="{{_ 'keyboard-shortcuts' }}")
i.fa.fa-keyboard-o
span {{_ 'keyboard-shortcuts' }}
hr hr
h3 h3
i.fa.fa-users i.fa.fa-users
@ -195,7 +175,7 @@ template(name="boardInfoOnMyBoardsPopup")
template(name="boardCardSettingsPopup") template(name="boardCardSettingsPopup")
form.board-card-settings form.board-card-settings
h3 {{_ 'show-on-card'}}, {{_ 'show-on-minicard'}} h3 {{_ 'show-on-card'}}
div.check-div div.check-div
a.flex.js-field-has-receiveddate(class="{{#if allowsReceivedDate}}is-checked{{/if}}") a.flex.js-field-has-receiveddate(class="{{#if allowsReceivedDate}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsReceivedDate}}is-checked{{/if}}") .materialCheckBox(class="{{#if allowsReceivedDate}}is-checked{{/if}}")
@ -226,18 +206,14 @@ template(name="boardCardSettingsPopup")
span span
i.fa.fa-users i.fa.fa-users
| {{_ 'members'}} | {{_ 'members'}}
div.check-div div.check-div
a.flex.js-field-has-creator(class="{{#if allowsCreator}}is-checked{{/if}}") a.flex.js-field-has-creator(class="{{#if allowsCreator}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsCreator}}is-checked{{/if}}") .materialCheckBox(class="{{#if allowsCreator}}is-checked{{/if}}")
span span
i.fa.fa-user i.fa.fa-user
| {{_ 'creator'}} | {{_ 'creator'}}
div.check-div
a.flex.js-field-has-creator-on-minicard(class="{{#if allowsCreatorOnMinicard}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsCreatorOnMinicard}}is-checked{{/if}}")
span
i.fa.fa-user
| {{_ 'creator-on-minicard'}}
div.check-div div.check-div
a.flex.js-field-has-assignee(class="{{#if allowsAssignee}}is-checked{{/if}}") a.flex.js-field-has-assignee(class="{{#if allowsAssignee}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsAssignee}}is-checked{{/if}}") .materialCheckBox(class="{{#if allowsAssignee}}is-checked{{/if}}")
@ -262,12 +238,6 @@ template(name="boardCardSettingsPopup")
span span
i.fa.fa-sort i.fa.fa-sort
| {{_ 'card-sorting-by-number'}} | {{_ 'card-sorting-by-number'}}
div.check-div
a.flex.js-field-has-card-sorting-by-number-on-minicard(class="{{#if allowsCardSortingByNumberOnMinicard}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsCardSortingByNumberOnMinicard}}is-checked{{/if}}")
span
i.fa.fa-sort
| {{_ 'card-sorting-by-number-on-minicard'}}
div.check-div div.check-div
a.flex.js-field-has-card-show-lists(class="{{#if allowsShowLists}}is-checked{{/if}}") a.flex.js-field-has-card-show-lists(class="{{#if allowsShowLists}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsShowLists}}is-checked{{/if}}") .materialCheckBox(class="{{#if allowsShowLists}}is-checked{{/if}}")
@ -301,12 +271,6 @@ template(name="boardCardSettingsPopup")
i.fa.fa-align-left i.fa.fa-align-left
| {{_ 'description'}} | {{_ 'description'}}
| {{_ 'custom-field-text'}} | {{_ 'custom-field-text'}}
div.check-div
a.flex.js-field-has-description-text-on-minicard(class="{{#if allowsDescriptionTextOnMinicard}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsDescriptionTextOnMinicard}}is-checked{{/if}}")
span
i.fa.fa-align-left
| {{_ 'description-on-minicard'}}
div.check-div div.check-div
a.flex.js-field-has-checklists(class="{{#if allowsChecklists}}is-checked{{/if}}") a.flex.js-field-has-checklists(class="{{#if allowsChecklists}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsChecklists}}is-checked{{/if}}") .materialCheckBox(class="{{#if allowsChecklists}}is-checked{{/if}}")
@ -325,19 +289,6 @@ template(name="boardCardSettingsPopup")
span span
i.fa.fa-paperclip i.fa.fa-paperclip
| {{_ 'attachments'}} | {{_ 'attachments'}}
div.check-div
a.flex.js-field-has-badge-attachment-on-minicard(class="{{#if allowsBadgeAttachmentOnMinicard}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsBadgeAttachmentOnMinicard}}is-checked{{/if}}")
span
i.fa.fa-paperclip
| {{_ 'badge-attachment-on-minicard'}}
div.check-div
a.flex.js-field-has-cover-attachment-on-minicard(class="{{#if allowsCoverAttachmentOnMinicard}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsCoverAttachmentOnMinicard}}is-checked{{/if}}")
span
i.fa.fa-book
i.fa.fa-picture-o
| {{_ 'cover-attachment-on-minicard'}}
//div.check-div //div.check-div
// a.flex.js-field-has-comments(class="{{#if allowsComments}}is-checked{{/if}}") // a.flex.js-field-has-comments(class="{{#if allowsComments}}is-checked{{/if}}")
// .materialCheckBox(class="{{#if allowsComments}}is-checked{{/if}}") // .materialCheckBox(class="{{#if allowsComments}}is-checked{{/if}}")
@ -351,6 +302,35 @@ template(name="boardCardSettingsPopup")
// i.fa.fa-history // i.fa.fa-history
// | {{_ 'activities'}} // | {{_ 'activities'}}
template(name="boardMinicardSettingsPopup")
form.board-minicard-settings
h3 {{_ 'show-on-minicard'}}
div.check-div
a.flex.js-field-has-description-text-on-minicard(class="{{#if allowsDescriptionTextOnMinicard}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsDescriptionTextOnMinicard}}is-checked{{/if}}")
span
i.fa.fa-align-left
| {{_ 'description-on-minicard'}}
div.check-div
a.flex.js-field-has-cover-attachment-on-minicard(class="{{#if allowsCoverAttachmentOnMinicard}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsCoverAttachmentOnMinicard}}is-checked{{/if}}")
span
i.fa.fa-book
i.fa.fa-picture-o
| {{_ 'cover-attachment-on-minicard'}}
div.check-div
a.flex.js-field-has-badge-attachment-on-minicard(class="{{#if allowsBadgeAttachmentOnMinicard}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsBadgeAttachmentOnMinicard}}is-checked{{/if}}")
span
i.fa.fa-paperclip
| {{_ 'badge-attachment-on-minicard'}}
div.check-div
a.flex.js-field-has-card-sorting-by-number-on-minicard(class="{{#if allowsCardSortingByNumberOnMinicard}}is-checked{{/if}}")
.materialCheckBox(class="{{#if allowsCardSortingByNumberOnMinicard}}is-checked{{/if}}")
span
i.fa.fa-sort
| {{_ 'card-sorting-by-number-on-minicard'}}
template(name="boardSubtaskSettingsPopup") template(name="boardSubtaskSettingsPopup")
form.board-subtask-settings form.board-subtask-settings
h3 {{_ 'show-parent-in-minicard'}} h3 {{_ 'show-parent-in-minicard'}}
@ -493,6 +473,10 @@ template(name="boardMenuPopup")
a.js-card-settings a.js-card-settings
i.fa.fa-id-card-o i.fa.fa-id-card-o
| {{_ 'card-settings'}} | {{_ 'card-settings'}}
li
a.js-minicard-settings
i.fa.fa-id-card-o
| {{_ 'minicard-settings'}}
li li
a.js-subtask-settings a.js-subtask-settings
i.fa.fa-sitemap i.fa.fa-sitemap

View file

@ -105,16 +105,6 @@ BlazeComponent.extendComponent({
else return `${TAPi18n.__('sidebar-open')}`; else return `${TAPi18n.__('sidebar-open')}`;
}, },
isKeyboardShortcuts() {
const user = ReactiveCache.getCurrentUser();
return user && user.isKeyboardShortcuts();
},
isVerticalScrollbars() {
const user = ReactiveCache.getCurrentUser();
return user && user.isVerticalScrollbars();
},
events() { events() {
return [ return [
{ {
@ -136,15 +126,6 @@ BlazeComponent.extendComponent({
'click .js-shortcuts'() { 'click .js-shortcuts'() {
FlowRouter.go('shortcuts'); FlowRouter.go('shortcuts');
}, },
'click .js-keyboard-shortcuts-toggle'() {
ReactiveCache.getCurrentUser().toggleKeyboardShortcuts();
},
'click .js-vertical-scrollbars-toggle'() {
ReactiveCache.getCurrentUser().toggleVerticalScrollbars();
},
'click .js-show-week-of-year-toggle'() {
ReactiveCache.getCurrentUser().toggleShowWeekOfYear();
},
'click .js-close-sidebar'() { 'click .js-close-sidebar'() {
Sidebar.toggle() Sidebar.toggle()
}, },
@ -155,7 +136,7 @@ BlazeComponent.extendComponent({
Blaze.registerHelper('Sidebar', () => Sidebar); Blaze.registerHelper('Sidebar', () => Sidebar);
BlazeComponent.extendComponent({ Template.homeSidebar.helpers({
hiddenMinicardLabelText() { hiddenMinicardLabelText() {
currentUser = ReactiveCache.getCurrentUser(); currentUser = ReactiveCache.getCurrentUser();
if (currentUser) { if (currentUser) {
@ -166,28 +147,7 @@ BlazeComponent.extendComponent({
return false; return false;
} }
}, },
isVerticalScrollbars() { });
const user = ReactiveCache.getCurrentUser();
return user && user.isVerticalScrollbars();
},
isShowWeekOfYear() {
const user = ReactiveCache.getCurrentUser();
return user && user.isShowWeekOfYear();
},
showActivities() {
let ret = Utils.getCurrentBoard().showActivities ?? false;
return ret;
},
events() {
return [
{
'click #toggleShowActivitiesBoard'() {
Utils.getCurrentBoard().toggleShowActivities();
},
},
];
},
}).register('homeSidebar');
Template.boardInfoOnMyBoardsPopup.helpers({ Template.boardInfoOnMyBoardsPopup.helpers({
hideCardCounterList() { hideCardCounterList() {
@ -276,6 +236,7 @@ Template.boardMenuPopup.events({
'click .js-import-board': Popup.open('chooseBoardSource'), 'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-subtask-settings': Popup.open('boardSubtaskSettings'), 'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
'click .js-card-settings': Popup.open('boardCardSettings'), 'click .js-card-settings': Popup.open('boardCardSettings'),
'click .js-minicard-settings': Popup.open('boardMinicardSettings'),
'click .js-export-board': Popup.open('exportBoard'), 'click .js-export-board': Popup.open('exportBoard'),
}); });
@ -953,11 +914,11 @@ BlazeComponent.extendComponent({
}, },
allowsCreator() { allowsCreator() {
return this.currentBoard.allowsCreator ?? false; return (
}, this.currentBoard.allowsCreator === null ||
this.currentBoard.allowsCreator === undefined ||
allowsCreatorOnMinicard() { this.currentBoard.allowsCreator
return this.currentBoard.allowsCreatorOnMinicard ?? false; );
}, },
allowsMembers() { allowsMembers() {
@ -1023,22 +984,6 @@ BlazeComponent.extendComponent({
); );
}, },
allowsDescriptionTextOnMinicard() {
return this.currentBoard.allowsDescriptionTextOnMinicard;
},
allowsCoverAttachmentOnMinicard() {
return this.currentBoard.allowsCoverAttachmentOnMinicard;
},
allowsBadgeAttachmentOnMinicard() {
return this.currentBoard.allowsBadgeAttachmentOnMinicard;
},
allowsCardSortingByNumberOnMinicard() {
return this.currentBoard.allowsCardSortingByNumberOnMinicard;
},
boards() { boards() {
const ret = ReactiveCache.getBoards( const ret = ReactiveCache.getBoards(
{ {
@ -1161,19 +1106,6 @@ BlazeComponent.extendComponent({
this.currentBoard.allowsCreator, this.currentBoard.allowsCreator,
); );
}, },
'click .js-field-has-creator-on-minicard'(evt) {
evt.preventDefault();
this.currentBoard.allowsCreatorOnMinicard = !this.currentBoard.allowsCreatorOnMinicard;
this.currentBoard.setAllowsCreatorOnMinicard(this.currentBoard.allowsCreatorOnMinicard);
$(`.js-field-has-creator-on-minicard ${MCB}`).toggleClass(
CKCLS,
this.currentBoard.allowsCreatorOnMinicard,
);
$('.js-field-has-creator-on-minicard').toggleClass(
CKCLS,
this.currentBoard.allowsCreatorOnMinicard,
);
},
'click .js-field-has-members'(evt) { 'click .js-field-has-members'(evt) {
evt.preventDefault(); evt.preventDefault();
this.currentBoard.allowsMembers = !this.currentBoard.allowsMembers; this.currentBoard.allowsMembers = !this.currentBoard.allowsMembers;
@ -1309,22 +1241,6 @@ BlazeComponent.extendComponent({
this.currentBoard.allowsCardNumber, this.currentBoard.allowsCardNumber,
); );
}, },
'click .js-field-has-description-text-on-minicard'(evt) {
evt.preventDefault();
this.currentBoard.allowsDescriptionTextOnMinicard = !this.currentBoard
.allowsDescriptionTextOnMinicard;
this.currentBoard.setallowsDescriptionTextOnMinicard(
this.currentBoard.allowsDescriptionTextOnMinicard,
);
$(`.js-field-has-description-text-on-minicard ${MCB}`).toggleClass(
CKCLS,
this.currentBoard.allowsDescriptionTextOnMinicard,
);
$('.js-field-has-description-text-on-minicard').toggleClass(
CKCLS,
this.currentBoard.allowsDescriptionTextOnMinicard,
);
},
'click .js-field-has-description-text'(evt) { 'click .js-field-has-description-text'(evt) {
evt.preventDefault(); evt.preventDefault();
this.currentBoard.allowsDescriptionText = !this.currentBoard this.currentBoard.allowsDescriptionText = !this.currentBoard
@ -1402,6 +1318,74 @@ BlazeComponent.extendComponent({
this.currentBoard.allowsActivities, this.currentBoard.allowsActivities,
); );
}, },
},
];
},
}).register('boardCardSettingsPopup');
BlazeComponent.extendComponent({
onCreated() {
this.currentBoard = Utils.getCurrentBoard();
},
allowsDescriptionTextOnMinicard() {
return this.currentBoard.allowsDescriptionTextOnMinicard;
},
allowsCoverAttachmentOnMinicard() {
return this.currentBoard.allowsCoverAttachmentOnMinicard;
},
allowsBadgeAttachmentOnMinicard() {
return this.currentBoard.allowsBadgeAttachmentOnMinicard;
},
allowsCardSortingByNumberOnMinicard() {
return this.currentBoard.allowsCardSortingByNumberOnMinicard;
},
lists() {
return ReactiveCache.getLists(
{
boardId: this.currentBoard._id,
archived: false,
},
{
sort: ['title'],
},
);
},
hasLists() {
return this.lists().length > 0;
},
isListSelected() {
return (
this.currentBoard.dateSettingsDefaultBoardId === this.currentData()._id
);
},
events() {
return [
{
'click .js-field-has-description-text-on-minicard'(evt) {
evt.preventDefault();
this.currentBoard.allowsDescriptionTextOnMinicard = !this.currentBoard
.allowsDescriptionTextOnMinicard;
this.currentBoard.setallowsDescriptionTextOnMinicard(
this.currentBoard.allowsDescriptionTextOnMinicard,
);
$(`.js-field-has-description-text-on-minicard ${MCB}`).toggleClass(
CKCLS,
this.currentBoard.allowsDescriptionTextOnMinicard,
);
$('.js-field-has-description-text-on-minicard').toggleClass(
CKCLS,
this.currentBoard.allowsDescriptionTextOnMinicard,
);
},
'click .js-field-has-cover-attachment-on-minicard'(evt) { 'click .js-field-has-cover-attachment-on-minicard'(evt) {
evt.preventDefault(); evt.preventDefault();
this.currentBoard.allowsCoverAttachmentOnMinicard = !this.currentBoard this.currentBoard.allowsCoverAttachmentOnMinicard = !this.currentBoard
@ -1453,7 +1437,7 @@ BlazeComponent.extendComponent({
}, },
]; ];
}, },
}).register('boardCardSettingsPopup'); }).register('boardMinicardSettingsPopup');
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
onCreated() { onCreated() {

View file

@ -17,24 +17,14 @@ template(name="swimlaneFixedHeader")
| {{_ 'list-templates-swimlane'}} | {{_ 'list-templates-swimlane'}}
else if $eq title 'Board Templates' else if $eq title 'Board Templates'
| {{_ 'board-templates-swimlane'}} | {{_ 'board-templates-swimlane'}}
else if $eq title 'Default'
| {{_ 'defaultdefault'}}
else else
+viewer +viewer
| {{isTitleDefault title}} = title
.swimlane-header-menu .swimlane-header-menu
unless currentUser.isCommentOnly unless currentUser.isCommentOnly
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}") if currentUser.isBoardAdmin
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'}}")
//// TODO: Collapse Swimlane: make button working, etc.
//unless collapsed
// a.js-collapse-swimlane(title="{{_ 'collapse'}}")
// i.fa.fa-arrow-down.swimlane-header-collapse-down
// i.fa.fa-arrow-up.swimlane-header-collapse-up
//if collapsed
// a.js-collapse-swimlane(title="{{_ 'uncollapse'}}")
// i.fa.fa-arrow-up.swimlane-header-collapse-up
// i.fa.fa-arrow-down.swimlane-header-collapse-down
unless isTouchScreen unless isTouchScreen
if isShowDesktopDragHandles 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
@ -43,7 +33,7 @@ template(name="swimlaneFixedHeader")
template(name="editSwimlaneTitleForm") template(name="editSwimlaneTitleForm")
.list-composer .list-composer
input.list-name-input.full-line(type="text" value="{{isTitleDefault title}}" autofocus) input.list-name-input.full-line(type="text" value=title autofocus)
.edit-controls.clearfix .edit-controls.clearfix
button.primary.confirm(type="submit") {{_ 'save'}} button.primary.confirm(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form a.fa.fa-times-thin.js-close-inlined-form

View file

@ -1,4 +1,3 @@
import { TAPi18n } from '/imports/i18n';
import { ReactiveCache } from '/imports/reactiveCache'; import { ReactiveCache } from '/imports/reactiveCache';
const { calculateIndexData } = Utils; const { calculateIndexData } = Utils;
@ -18,25 +17,10 @@ BlazeComponent.extendComponent({
swimlane.rename(newTitle.trim()); swimlane.rename(newTitle.trim());
} }
}, },
collapsed(check = undefined) {
const swimlane = Template.currentData();
const status = swimlane.isCollapsed();
if (check === undefined) {
// just check
return status;
} else {
swimlane.collapse(!status);
return !status;
}
},
events() { events() {
return [ return [
{ {
'click .js-collapse-swimlane'(event) {
event.preventDefault();
this.collapsed(!this.collapsed());
},
'click .js-open-swimlane-menu': Popup.open('swimlaneAction'), 'click .js-open-swimlane-menu': Popup.open('swimlaneAction'),
'click .js-open-add-swimlane-menu': Popup.open('swimlaneAdd'), 'click .js-open-add-swimlane-menu': Popup.open('swimlaneAdd'),
submit: this.editTitle, submit: this.editTitle,
@ -49,53 +33,6 @@ Template.swimlaneFixedHeader.helpers({
isBoardAdmin() { isBoardAdmin() {
return ReactiveCache.getCurrentUser().isBoardAdmin(); return ReactiveCache.getCurrentUser().isBoardAdmin();
}, },
isTitleDefault(title) {
// https://github.com/wekan/wekan/issues/4763
// https://github.com/wekan/wekan/issues/4742
// Translation text for "default" does not work, it returns an object.
// When that happens, try use translation "defaultdefault" that has same content of default, or return text "Default".
// This can happen, if swimlane does not have name.
// Yes, this is fixing the symptom (Swimlane title does not have title)
// instead of fixing the problem (Add Swimlane title when creating swimlane)
// because there could be thousands of swimlanes, adding name Default to all of them
// would be very slow.
if (title.startsWith("key 'default") && title.endsWith('returned an object instead of string.')) {
if (`${TAPi18n.__('defaultdefault')}`.startsWith("key 'default") && `${TAPi18n.__('defaultdefault')}`.endsWith('returned an object instead of string.')) {
return 'Default';
} else {
return `${TAPi18n.__('defaultdefault')}`;
}
} else if (title === 'Default') {
return `${TAPi18n.__('defaultdefault')}`;
} else {
return title;
}
},
});
Template.editSwimlaneTitleForm.helpers({
isTitleDefault(title) {
// https://github.com/wekan/wekan/issues/4763
// https://github.com/wekan/wekan/issues/4742
// Translation text for "default" does not work, it returns an object.
// When that happens, try use translation "defaultdefault" that has same content of default, or return text "Default".
// This can happen, if swimlane does not have name.
// Yes, this is fixing the symptom (Swimlane title does not have title)
// instead of fixing the problem (Add Swimlane title when creating swimlane)
// because there could be thousands of swimlanes, adding name Default to all of them
// would be very slow.
if (title.startsWith("key 'default") && title.endsWith('returned an object instead of string.')) {
if (`${TAPi18n.__('defaultdefault')}`.startsWith("key 'default") && `${TAPi18n.__('defaultdefault')}`.endsWith('returned an object instead of string.')) {
return 'Default';
} else {
return `${TAPi18n.__('defaultdefault')}`;
}
} else if (title === 'Default') {
return `${TAPi18n.__('defaultdefault')}`;
} else {
return title;
}
},
}); });
Template.swimlaneActionPopup.events({ Template.swimlaneActionPopup.events({
@ -143,7 +80,7 @@ BlazeComponent.extendComponent({
Swimlanes.insert({ Swimlanes.insert({
title, title,
boardId: Session.get('currentBoard'), boardId: Session.get('currentBoard'),
sort: sortValue.base || 0, sort: sortValue.base,
type: swimlaneType, type: swimlaneType,
}); });
@ -224,7 +161,7 @@ BlazeComponent.extendComponent({
swimlaneHeightValue() { swimlaneHeightValue() {
const swimlane = this.currentData(); const swimlane = this.currentData();
const board = swimlane.boardId; const board = swimlane.boardId;
return ReactiveCache.getCurrentUser().getSwimlaneHeight(board, swimlane._id); return Meteor.user().getSwimlaneHeight(board, swimlane._id);
}, },
events() { events() {

View file

@ -1,3 +1,43 @@
/*
// Minimize swimlanes start https://www.w3schools.com/howto/howto_js_accordion.asp
.accordion
cursor: pointer
width: 30px
height: 20px
border: none
outline: none
font-size: 18px
transition: 0.4s
padding-top: 0px
margin-top: 0px
.accordion:after
// Unicode triagle right:
content: '\25B6'
color: #777
font-weight: bold
float: left
.active:after
// Unicode triangle down:
content: '\25BC'
.panel
width: 100%
max-height: 0
overflow: hidden
transition: max-height 0.2s ease-out
margin: 0px
padding: 0px
// Minimize swimlanes end https://www.w3schools.com/howto/howto_js_accordion.asp
*/
@media screen and (min-width: 801px) {
.swimlane.ui-sortable {
width: max-content;
}
}
[class=swimlane] { [class=swimlane] {
position: sticky; position: sticky;
left: 0; left: 0;
@ -6,30 +46,8 @@
background: #dedede; background: #dedede;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
overflow: auto; overflow: 0;
max-height: 100%; max-height: calc(100% - 26px);
}
.swimlane-header-menu .swimlane-header-collapse-down {
font-size: 50%;
color: #a6a6a6;
position: absolute;
top: 5px;
left: 100px;
}
.swimlane-header-menu .swimlane-header-collapse-up {
font-size: 50%;
color: #a6a6a6;
position: absolute;
bottom: 5px;
left: 100px;
}
.swimlane-header-menu .swimlane-header-uncollapse-up {
font-size: 50%;
color: #a6a6a6;
}
.swimlane-header-menu .swimlane-header-uncollapse-down {
font-size: 50%;
color: #a6a6a6;
} }
.swimlane.placeholder { .swimlane.placeholder {
background-color: rgba(0,0,0,0.2); background-color: rgba(0,0,0,0.2);
@ -153,7 +171,7 @@
color: #4d4d4d !important; color: #4d4d4d !important;
} }
.swimlane-silver { .swimlane-silver {
background: #ccc !important; background: unset !important;
color: #4d4d4d !important; color: #4d4d4d !important;
} }
.swimlane-peachpuff { .swimlane-peachpuff {

View file

@ -1,8 +1,8 @@
template(name="swimlane") template(name="swimlane")
.swimlane.nodragscroll .swimlane
+swimlaneHeader +swimlaneHeader
unless collapseSwimlane unless collapseSwimlane
.swimlane.js-lists.js-swimlane.dragscroll(id="swimlane-{{_id}}" .swimlane.js-lists.js-swimlane(id="swimlane-{{_id}}"
style="height:{{swimlaneHeight}};") style="height:{{swimlaneHeight}};")
if isMiniScreen if isMiniScreen
if currentListIsInThisSwimlane _id if currentListIsInThisSwimlane _id
@ -24,7 +24,7 @@ template(name="swimlane")
+cardDetails(currentCard) +cardDetails(currentCard)
template(name="listsGroup") template(name="listsGroup")
.swimlane.list-group.js-lists.dragscroll .swimlane.list-group.js-lists
if isMiniScreen if isMiniScreen
if currentList if currentList
+list(currentList) +list(currentList)
@ -46,8 +46,8 @@ template(name="listsGroup")
template(name="addListForm") template(name="addListForm")
unless currentUser.isWorker unless currentUser.isWorker
unless currentUser.isCommentOnly .list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
.list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}") if currentUser.isBoardAdmin
.list-header-add .list-header-add
+inlinedForm(autoclose=false) +inlinedForm(autoclose=false)
input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}" input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"

View file

@ -225,7 +225,7 @@ BlazeComponent.extendComponent({
}, },
swimlaneHeight() { swimlaneHeight() {
const user = ReactiveCache.getCurrentUser(); const user = Meteor.user();
const swimlane = Template.currentData(); const swimlane = Template.currentData();
const height = user.getSwimlaneHeight(swimlane.boardId, swimlane._id); const height = user.getSwimlaneHeight(swimlane.boardId, swimlane._id);
return height == -1 ? "auto" : (height + "px"); return height == -1 ? "auto" : (height + "px");
@ -288,7 +288,7 @@ BlazeComponent.extendComponent({
Template.swimlane.helpers({ Template.swimlane.helpers({
canSeeAddList() { canSeeAddList() {
return ReactiveCache.getCurrentUser().isBoardAdmin(); return Meteor.user().isBoardAdmin();
}, },
}); });

View file

@ -5,7 +5,6 @@
float: left; float: left;
height: 30px; height: 30px;
width: 30px; width: 30px;
margin: .3vh;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
z-index: 1; z-index: 1;

View file

@ -26,9 +26,9 @@ template(name="orgAvatar")
template(name="boardOrgRow") template(name="boardOrgRow")
tr tr
if orgData.orgIsActive if orgData.orgIsActive
td {{ orgData.orgDisplayName }}
else
td <s>{{ orgData.orgDisplayName }}</s> td <s>{{ orgData.orgDisplayName }}</s>
else
td {{ orgData.orgDisplayName }}
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'}}")
@ -39,9 +39,9 @@ template(name="boardOrgRow")
template(name="boardTeamRow") template(name="boardTeamRow")
tr tr
if teamData.teamIsActive if teamData.teamIsActive
td {{ teamData.teamDisplayName }}
else
td <s>{{ teamData.teamDisplayName }}</s> td <s>{{ teamData.teamDisplayName }}</s>
else
td {{ teamData.teamDisplayName }}
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'}}")

View file

@ -77,10 +77,6 @@ template(name="memberMenuPopup")
a.js-change-language a.js-change-language
i.fa.fa-flag i.fa.fa-flag
| {{_ 'changeLanguagePopup-title'}} | {{_ 'changeLanguagePopup-title'}}
//li
// a.js-support
// i.fa.fa-question-circle
// | {{_ 'support'}}
unless isSandstorm unless isSandstorm
hr hr
ul.pop-over-list ul.pop-over-list
@ -143,12 +139,6 @@ template(name="editProfilePopup")
div div
input#deleteButton.primary.wide(type="button" value="{{_ 'delete'}}") input#deleteButton.primary.wide(type="button" value="{{_ 'delete'}}")
template(name="supportPopup")
ul.pop-over-list
li
| Support popup text will be editable later.
template(name="changePasswordPopup") template(name="changePasswordPopup")
+atForm(state='changePwd') +atForm(state='changePwd')
@ -163,6 +153,12 @@ template(name="changeLanguagePopup")
template(name="changeSettingsPopup") template(name="changeSettingsPopup")
ul.pop-over-list ul.pop-over-list
//li
// a.js-toggle-system-messages
// i.fa.fa-comments-o
// | {{_ 'hide-system-messages'}}
// if hiddenSystemMessages
// i.fa.fa-check
//li //li
// a.js-toggle-desktop-drag-handles // a.js-toggle-desktop-drag-handles
// i.fa.fa-arrows // i.fa.fa-arrows

View file

@ -77,7 +77,6 @@ Template.memberMenuPopup.events({
'click .js-change-avatar': Popup.open('changeAvatar'), 'click .js-change-avatar': Popup.open('changeAvatar'),
'click .js-change-password': Popup.open('changePassword'), 'click .js-change-password': Popup.open('changePassword'),
'click .js-change-language': Popup.open('changeLanguage'), 'click .js-change-language': Popup.open('changeLanguage'),
'click .js-support': Popup.open('support'),
'click .js-logout'(event) { 'click .js-logout'(event) {
event.preventDefault(); event.preventDefault();
@ -249,7 +248,6 @@ Template.editProfilePopup.events({
// XXX For some reason the useraccounts autofocus isnt working in this case. // XXX For some reason the useraccounts autofocus isnt working in this case.
// See https://github.com/meteor-useraccounts/core/issues/384 // See https://github.com/meteor-useraccounts/core/issues/384
Template.changePasswordPopup.onRendered(function() { Template.changePasswordPopup.onRendered(function() {
$('.at-pwd-form').show();
this.find('#at-field-current_password').focus(); this.find('#at-field-current_password').focus();
}); });
@ -284,6 +282,16 @@ Template.changeLanguagePopup.events({
}); });
Template.changeSettingsPopup.helpers({ Template.changeSettingsPopup.helpers({
hiddenSystemMessages() {
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) {
return (currentUser.profile || {}).hasHiddenSystemMessages;
} else if (window.localStorage.getItem('hasHiddenSystemMessages')) {
return true;
} else {
return false;
}
},
rescueCardDescription() { rescueCardDescription() {
const currentUser = ReactiveCache.getCurrentUser(); const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) { if (currentUser) {
@ -316,7 +324,7 @@ Template.changeSettingsPopup.helpers({
}); });
}, },
startDayOfWeek() { startDayOfWeek() {
currentUser = ReactiveCache.getCurrentUser(); currentUser = Meteor.user();
if (currentUser) { if (currentUser) {
return currentUser.getStartDayOfWeek(); return currentUser.getStartDayOfWeek();
} else { } else {
@ -334,7 +342,7 @@ Template.changeSettingsPopup.events({
return ret; return ret;
}, },
'click .js-toggle-desktop-drag-handles'() { 'click .js-toggle-desktop-drag-handles'() {
const currentUser = ReactiveCache.getCurrentUser(); currentUser = Meteor.user();
if (currentUser) { if (currentUser) {
Meteor.call('toggleDesktopDragHandles'); Meteor.call('toggleDesktopDragHandles');
} else if (window.localStorage.getItem('showDesktopDragHandles')) { } else if (window.localStorage.getItem('showDesktopDragHandles')) {
@ -343,6 +351,16 @@ Template.changeSettingsPopup.events({
window.localStorage.setItem('showDesktopDragHandles', 'true'); window.localStorage.setItem('showDesktopDragHandles', 'true');
} }
}, },
'click .js-toggle-system-messages'() {
currentUser = Meteor.user();
if (currentUser) {
Meteor.call('toggleSystemMessages');
} else if (window.localStorage.getItem('hasHiddenSystemMessages')) {
window.localStorage.removeItem('hasHiddenSystemMessages');
} else {
window.localStorage.setItem('hasHiddenSystemMessages', 'true');
}
},
'click .js-rescue-card-description'() { 'click .js-rescue-card-description'() {
Meteor.call('toggleRescueCardDescription') Meteor.call('toggleRescueCardDescription')
}, },
@ -356,7 +374,7 @@ Template.changeSettingsPopup.events({
templateInstance.$('#start-day-of-week').val(), templateInstance.$('#start-day-of-week').val(),
10, 10,
); );
const currentUser = ReactiveCache.getCurrentUser(); const currentUser = Meteor.user();
if (isNaN(minLimit) || minLimit < -1) { if (isNaN(minLimit) || minLimit < -1) {
minLimit = -1; minLimit = -1;
} }

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